├── README.md ├── build └── Release │ └── oracledb.node ├── index.js ├── lambda-lib.zip ├── lib ├── connection.js ├── lob.js ├── oracledb.js ├── pool.js ├── querystream.js ├── resultset.js └── util.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # node-oracledb-for-lambda 2 | 3 | This module is a fork of [node-oracledb](https://github.com/oracle/node-oracledb) v1.9.3, precompiled for AWS Lambda, Node.js 8.x 4 | 5 | The scripts to reproduce the build process can be found at [node-oracledb-lambda-test](https://github.com/nalbion/node-oracledb-lambda-test). 6 | 7 | # Usage 8 | 9 | In addition to the usual package.json and *.js files, you also need to include the 10 | Oracle Instant Client libraries provided in `lambda-lib` - they should be in your zip file's `lib/` directory. 11 | 12 | ```bash 13 | npm install --save oracledb-for-lambda 14 | 15 | zip app.zip index.js package.json \ 16 | your-other-dependencies... \ 17 | node_modules/oracledb-for-lambda/package.json \ 18 | node_modules/oracledb-for-lambda/index.js \ 19 | node_modules/oracledb-for-lambda/lib/*.js \ 20 | node_modules/oracledb-for-lambda/build/Release/oracledb.node \ 21 | lib/*.so* \ 22 | ``` 23 | 24 | Note: There is an [issue with `gulp-zip` modifying the structure of these binaries](https://github.com/thejoshwolfe/yazl/issues/25), so just use the native `zip`. 25 | 26 | 27 | ## Instant Client Light 28 | To keep the zip and application size small, the [Instant Client Light (English) version](https://docs.oracle.com/database/121/LNOCI/oci01int.htm#LNOCI13309) is provided by default. 29 | If you require error messages in languages other than English, you can swap the Instant Client Data library: 30 | 31 | ```bash 32 | # after npm install, and before creating your zip: 33 | cp node_modules/oracle-for-lambda/lib/libociei.so lib/ 34 | rm lib/libociicus.so 35 | ``` 36 | 37 | Due to the size of the Oracle libraries, you may need to deploy your zip file to S3 and get Lambda to download from the S3 URL. 38 | -------------------------------------------------------------------------------- /build/Release/oracledb.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nalbion/node-oracledb-for-lambda/b55151ff4117af8e6462ed17b269cefff1bd04fe/build/Release/oracledb.node -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/oracledb.js'); 2 | -------------------------------------------------------------------------------- /lambda-lib.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nalbion/node-oracledb-for-lambda/b55151ff4117af8e6462ed17b269cefff1bd04fe/lambda-lib.zip -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var resultset = require('./resultset.js'); 23 | var QueryStream = require('./querystream.js'); 24 | var nodbUtil = require('./util.js'); 25 | var executePromisified; 26 | var commitPromisified; 27 | var createLobPromisified; 28 | var rollbackPromisified; 29 | var releasePromisified; 30 | var breakPromisified; 31 | 32 | // The queryStream function is similar to execute except that it immediately 33 | // returns a QueryStream. 34 | function queryStream(sql, binding, options) { 35 | var self = this; 36 | var stream; 37 | 38 | nodbUtil.assert(arguments.length > 0 && arguments.length < 4, 'NJS-009'); 39 | nodbUtil.assert(typeof sql === 'string', 'NJS-006', 1); 40 | 41 | if (binding) { 42 | nodbUtil.assert(nodbUtil.isObjectOrArray(binding), 'NJS-006', 2); 43 | } 44 | 45 | if (options) { 46 | nodbUtil.assert(nodbUtil.isObject(options), 'NJS-006', 3); 47 | } 48 | 49 | binding = binding || []; 50 | options = options || {}; 51 | 52 | options.resultSet = true; 53 | 54 | stream = new QueryStream(null, self._oracledb); 55 | 56 | self._execute(sql, binding, options, function(err, result) { 57 | if (err) { 58 | stream._open(err, null); 59 | } else { 60 | resultset.extend(result.resultSet, self._oracledb); 61 | stream._open(null, result.resultSet); 62 | } 63 | }); 64 | 65 | return stream; 66 | } 67 | 68 | // This execute function is used to override the execute method of the Connection 69 | // class, which is defined in the C layer. The override allows us to do things 70 | // like extend out the resultSet instance prior to passing it to the caller. 71 | function execute(a1, a2, a3, a4) { 72 | var self = this; 73 | var executeCb; 74 | var custExecuteCb; 75 | 76 | nodbUtil.assert(arguments.length > 1 && arguments.length < 5, 'NJS-009'); 77 | nodbUtil.assert(typeof a1 === 'string', 'NJS-006', 1); 78 | 79 | switch (arguments.length) { 80 | case 2: 81 | nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2); 82 | break; 83 | case 3: 84 | nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2); 85 | nodbUtil.assert(typeof a3 === 'function', 'NJS-006', 3); 86 | break; 87 | case 4: 88 | nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2); 89 | nodbUtil.assert(nodbUtil.isObject(a3), 'NJS-006', 3); 90 | nodbUtil.assert(typeof a4 === 'function', 'NJS-006', 4); 91 | break; 92 | } 93 | 94 | custExecuteCb = function(err, result) { 95 | var outBindsKeys; 96 | var outBindsIdx; 97 | 98 | if (err) { 99 | executeCb(err); 100 | return; 101 | } 102 | 103 | // Need to extend resultsets which may come from either the query results 104 | // or outBinds. 105 | if (result.resultSet) { 106 | resultset.extend(result.resultSet, self._oracledb); 107 | } else if (result.outBinds) { 108 | outBindsKeys = Object.keys(result.outBinds); 109 | 110 | for (outBindsIdx = 0; outBindsIdx < outBindsKeys.length; outBindsIdx += 1) { 111 | if (result.outBinds[outBindsKeys[outBindsIdx]] instanceof self._oracledb.ResultSet) { 112 | resultset.extend(result.outBinds[outBindsKeys[outBindsIdx]], self._oracledb); 113 | } 114 | } 115 | } 116 | 117 | executeCb(null, result); 118 | }; 119 | 120 | switch (arguments.length) { 121 | case 4: 122 | executeCb = a4; 123 | self._execute.call(self, a1, a2, a3, custExecuteCb); 124 | break; 125 | case 3: 126 | executeCb = a3; 127 | self._execute.call(self, a1, a2, custExecuteCb); 128 | break; 129 | case 2: 130 | executeCb = a2; 131 | self._execute.call(self, a1, custExecuteCb); 132 | break; 133 | } 134 | } 135 | 136 | executePromisified = nodbUtil.promisify(execute); 137 | 138 | // This commit function is just a place holder to allow for easier extension later. 139 | function commit(commitCb) { 140 | var self = this; 141 | 142 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 143 | nodbUtil.assert(typeof commitCb === 'function', 'NJS-006', 1); 144 | 145 | self._commit.apply(self, arguments); 146 | } 147 | 148 | commitPromisified = nodbUtil.promisify(commit); 149 | 150 | // This createLob function is just a place holder to allow for easier extension later. 151 | function createLob(type, createLobCb) { 152 | var self = this; 153 | 154 | nodbUtil.assert(arguments.length === 2, 'NJS-009'); 155 | nodbUtil.assert(typeof createLobCb === 'function', 'NJS-006', 2); 156 | 157 | self._createLob.apply(self, arguments); 158 | } 159 | 160 | createLobPromisified = nodbUtil.promisify(createLob); 161 | 162 | // This rollback function is just a place holder to allow for easier extension later. 163 | function rollback(rollbackCb) { 164 | var self = this; 165 | 166 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 167 | nodbUtil.assert(typeof rollbackCb === 'function', 'NJS-006', 1); 168 | 169 | self._rollback.apply(self, arguments); 170 | } 171 | 172 | rollbackPromisified = nodbUtil.promisify(rollback); 173 | 174 | // This release function is used to override the release method of the Connection 175 | // class, which is defined in the C layer. 176 | function release(releaseCb) { 177 | var self = this; 178 | 179 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 180 | nodbUtil.assert(typeof releaseCb === 'function', 'NJS-006', 1); 181 | 182 | self._release(function(err) { 183 | if (!err) { 184 | self.emit('_after_close'); 185 | } 186 | 187 | releaseCb(err); 188 | }); 189 | } 190 | 191 | releasePromisified = nodbUtil.promisify(release); 192 | 193 | // This release function is just a place holder to allow for easier extension later. 194 | // It's attached to the module as break is a reserved word. 195 | module.break = function(breakCb) { 196 | var self = this; 197 | 198 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 199 | nodbUtil.assert(typeof breakCb === 'function', 'NJS-006', 1); 200 | 201 | self._break.apply(self, arguments); 202 | }; 203 | 204 | breakPromisified = nodbUtil.promisify(module.break); 205 | 206 | // The extend method is used to extend the Connection instance from the C layer with 207 | // custom properties and method overrides. References to the original methods are 208 | // maintained so they can be invoked by the overriding method at the right time. 209 | function extend(conn, oracledb, pool) { 210 | nodbUtil.makeEventEmitter(conn); 211 | 212 | // Using Object.defineProperties to add properties to the Connection instance with 213 | // special properties, such as enumerable but not writable. 214 | Object.defineProperties( 215 | conn, 216 | { 217 | _oracledb: { // storing a reference to the base instance to avoid circular references with require 218 | value: oracledb 219 | }, 220 | _pool: { // storing a reference to the pool, if any, from which the connection was obtained 221 | value: pool 222 | }, 223 | _execute: { 224 | value: conn.execute 225 | }, 226 | queryStream: { 227 | value: queryStream, 228 | enumerable: true, 229 | writable: true 230 | }, 231 | execute: { 232 | value: executePromisified, 233 | enumerable: true, 234 | writable: true 235 | }, 236 | _commit: { 237 | value: conn.commit 238 | }, 239 | commit: { 240 | value: commitPromisified, 241 | enumerable: true, 242 | writable: true 243 | }, 244 | _createLob: { 245 | value: conn.createLob 246 | }, 247 | createLob: { 248 | value: createLobPromisified, 249 | enumerable: true, 250 | writable: true 251 | }, 252 | _rollback: { 253 | value: conn.rollback 254 | }, 255 | rollback: { 256 | value: rollbackPromisified, 257 | enumerable: true, 258 | writable: true 259 | }, 260 | _release: { 261 | value: conn.release 262 | }, 263 | release: { 264 | value: releasePromisified, 265 | enumerable: true, 266 | writable: true 267 | }, 268 | close: { // alias for release 269 | value: releasePromisified, 270 | enumerable: true, 271 | writable: true 272 | }, 273 | _break: { 274 | value: conn.break 275 | }, 276 | break: { 277 | value: breakPromisified, 278 | enumerable: true, 279 | writable: true 280 | } 281 | } 282 | ); 283 | } 284 | 285 | module.exports.extend = extend; 286 | -------------------------------------------------------------------------------- /lib/lob.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var Duplex = require('stream').Duplex; 23 | var util = require('util'); 24 | var nodbUtil = require('./util.js'); 25 | var closePromisified; 26 | 27 | util.inherits(Lob, Duplex); 28 | 29 | // The Lob class is used to support the streaming of LOB data to/from the database. 30 | function Lob(iLob, opt, oracledb) { 31 | Duplex.call(this, opt); 32 | 33 | this.iLob = iLob; 34 | 35 | Object.defineProperties( 36 | this, 37 | { 38 | _oracledb: { // _oracledb property used by promisify () in util.js 39 | value: oracledb 40 | }, 41 | _autoCloseLob: { // Tells whether to close at the end of stream or not 42 | value: iLob.autoCloseLob, 43 | writable: false 44 | }, 45 | chunkSize: { 46 | value: iLob.chunkSize, 47 | writable: false 48 | }, 49 | length: { 50 | get: function() { 51 | return iLob.length; 52 | } 53 | }, 54 | pieceSize: { 55 | get: function() { 56 | return iLob.pieceSize; 57 | }, 58 | set: function(newPieceSize) { 59 | iLob.pieceSize = newPieceSize; 60 | } 61 | }, 62 | type: { 63 | get: function() { 64 | return iLob.type; 65 | } 66 | }, 67 | close: { 68 | value: closePromisified, 69 | enumerable: true, 70 | writable: true 71 | } 72 | }); 73 | 74 | if (this._autoCloseLob) { 75 | this.once('finish', this._closeSync); 76 | } 77 | } 78 | 79 | Lob.prototype._read = function() { 80 | var self = this; 81 | 82 | self.iLob.read( 83 | function(err, str) { 84 | if (err) { 85 | if (self._autoCloseLob) { 86 | // Ignore if any error occurs during close 87 | // Emits 'close' event after closing LOB 88 | self._closeSync(); 89 | } 90 | 91 | self.emit('error', err); 92 | return; 93 | } 94 | 95 | self.push(str); 96 | 97 | if (self._autoCloseLob) { 98 | if (!str) { 99 | process.nextTick(function() { 100 | err = self._closeSync(); // Emits 'close' event after closing LOB 101 | 102 | if (err) { 103 | self.emit('error', err); 104 | return; 105 | } 106 | }); 107 | } 108 | } 109 | } 110 | ); 111 | }; 112 | 113 | Lob.prototype._write = function(data, encoding, cb) { 114 | var self = this; 115 | 116 | self.iLob.write( 117 | data, 118 | function(err) { 119 | if (err) { 120 | self._closeSync(); // Ignore if any error occurs during close 121 | cb(err); 122 | return; 123 | } 124 | 125 | cb(); 126 | } 127 | ); 128 | }; 129 | 130 | // This function will be deprecated in the future 131 | // This internal function used to close the LOB at the end of writable 132 | // stream in synchronus way to avoid race condition between this function and 133 | // application's listener function on 'finish' event. 134 | Lob.prototype._closeSync = function() { 135 | var self = this; 136 | 137 | if (self.iLob !== null) { 138 | try { 139 | self.iLob.release(); 140 | } catch(err) { 141 | self.iLob = null; 142 | return err; 143 | } 144 | 145 | self.emit('close'); 146 | } 147 | }; 148 | 149 | Lob.prototype.close = function(closeCb) { 150 | var self = this; 151 | 152 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 153 | nodbUtil.assert(typeof closeCb === 'function', 'NJS-006', 1); 154 | 155 | // Return if LOB already closed to support multiple close() calls should be 156 | // no-op 157 | if (!self.iLob.valid) { 158 | closeCb(null); 159 | return; 160 | } 161 | 162 | self.iLob.close(function(err) { 163 | if (!err) { 164 | self.emit('close'); 165 | } 166 | 167 | closeCb(err); 168 | }); 169 | }; 170 | 171 | closePromisified = nodbUtil.promisify(Lob.prototype.close); 172 | 173 | module.exports.Lob = Lob; 174 | -------------------------------------------------------------------------------- /lib/oracledb.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var oracledbCLib; 23 | var oracledbInst; 24 | var Lob = require('./lob.js').Lob; 25 | var pool = require('./pool.js'); 26 | var connection = require('./connection.js'); 27 | var nodbUtil = require('./util.js'); 28 | var createPoolPromisified; 29 | var getConnectionPromisified; 30 | var poolCache = {}; 31 | var tempUsedPoolAliases = {}; 32 | var defaultPoolAlias = 'default'; 33 | 34 | try { 35 | oracledbCLib = require('../build/Release/oracledb'); 36 | } catch (err) { 37 | if (err.code !== 'MODULE_NOT_FOUND') { 38 | throw err; 39 | } else { 40 | try { 41 | oracledbCLib = require('../build/Debug/oracledb'); 42 | } catch (err) { 43 | if (err.code !== 'MODULE_NOT_FOUND') { 44 | throw err; 45 | } else { 46 | throw new Error(nodbUtil.getErrorMessage('NJS-045')); 47 | } 48 | } 49 | } 50 | } 51 | 52 | oracledbCLib.Oracledb.prototype.newLob = function(iLob) { 53 | return new Lob(iLob, null, oracledbInst); 54 | }; 55 | 56 | // This createPool function is used the override the createPool method of the 57 | // Oracledb class, which is defined in the C layer. The override allows us to do 58 | // things like extend out the pool instance prior to passing it to the caller. 59 | function createPool(poolAttrs, createPoolCb) { 60 | var self = this; 61 | var poolAlias; 62 | 63 | // Initial argument count and type checks are done first and throw in the same 64 | // call stack. 65 | nodbUtil.assert(arguments.length === 2, 'NJS-009'); 66 | nodbUtil.assert(nodbUtil.isObject(poolAttrs), 'NJS-006', 1); 67 | nodbUtil.assert(typeof createPoolCb === 'function', 'NJS-006', 2); 68 | 69 | // Additional validations should pass errors via the callback. Need to ensure 70 | // that errors are raised prior to actually creating the pool via _createPool. 71 | if (poolAttrs.poolAlias !== undefined) { 72 | if (typeof poolAttrs.poolAlias !== 'string' || poolAttrs.poolAlias.length === 0) { 73 | createPoolCb(new Error(nodbUtil.getErrorMessage('NJS-004', 'poolAttrs.poolAlias'))); 74 | return; 75 | } 76 | 77 | poolAlias = poolAttrs.poolAlias; 78 | } else if (poolAttrs.poolAlias === undefined 79 | && !poolCache[defaultPoolAlias] 80 | && !tempUsedPoolAliases[defaultPoolAlias] 81 | ) { 82 | poolAlias = defaultPoolAlias; 83 | } 84 | 85 | if (poolCache[poolAlias] || tempUsedPoolAliases[poolAlias]) { 86 | createPoolCb(new Error(nodbUtil.getErrorMessage('NJS-046', poolAlias))); 87 | return; 88 | } 89 | 90 | // Need to prevent another call in the same stack from succeeding, otherwise 91 | // two pools could be created with the same poolAlias and the second one that 92 | // comes back would overwrite the first in the cache. 93 | if (poolAlias) { 94 | tempUsedPoolAliases[poolAlias] = true; 95 | } 96 | 97 | self._createPool(poolAttrs, function(err, poolInst) { 98 | if (err) { 99 | // We need to free this up since the creation of the pool failed. 100 | if (poolAlias) { 101 | delete tempUsedPoolAliases[poolAlias]; 102 | } 103 | 104 | createPoolCb(err); 105 | 106 | return; 107 | } 108 | 109 | if (poolAlias) { 110 | poolCache[poolAlias] = poolInst; 111 | 112 | // It's now safe to remove this alias from the tempUsedPoolAliases. 113 | delete tempUsedPoolAliases[poolAlias]; 114 | } 115 | 116 | pool.extend(poolInst, poolAttrs, poolAlias, self); 117 | 118 | poolInst.on('_after_close', function() { 119 | var pool = this; 120 | 121 | if (pool.poolAlias) { 122 | delete poolCache[pool.poolAlias]; 123 | } 124 | }); 125 | 126 | createPoolCb(null, poolInst); 127 | }); 128 | } 129 | 130 | createPoolPromisified = nodbUtil.promisify(createPool); 131 | 132 | // The getPool function is a synchronous method used to retrieve pools from the 133 | // pool cache. 134 | function getPool(poolAlias) { 135 | var pool; 136 | 137 | nodbUtil.assert(arguments.length < 2, 'NJS-009'); 138 | 139 | if (poolAlias) { 140 | nodbUtil.assert(typeof poolAlias === 'string' || typeof poolAlias === 'number', 'NJS-006', 1); 141 | } 142 | 143 | poolAlias = poolAlias || defaultPoolAlias; 144 | 145 | pool = poolCache[poolAlias]; 146 | 147 | if (!pool) { 148 | throw new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias)); 149 | } 150 | 151 | return pool; 152 | } 153 | 154 | // This getConnection function is used the override the getConnection method of the 155 | // Oracledb class, which is defined in the C layer. The override allows us to do 156 | // things like extend out the connection instance prior to passing it to the caller. 157 | function getConnection(a1, a2) { 158 | var self = this; 159 | var pool; 160 | var poolAlias; 161 | var connAttrs; 162 | var getConnectionCb; 163 | 164 | nodbUtil.assert(arguments.length < 3, 'NJS-009'); 165 | 166 | // Verify the number and types of arguments, then initialize the local poolAlias, 167 | // connAttrs, and getConnectionCb variables based on the arguments. 168 | switch (arguments.length) { 169 | case 1: 170 | nodbUtil.assert(typeof a1 === 'function', 'NJS-006', 1); 171 | 172 | poolAlias = defaultPoolAlias; 173 | getConnectionCb = a1; 174 | 175 | break; 176 | case 2: 177 | nodbUtil.assert(typeof a1 === 'string' || nodbUtil.isObject(a1), 'NJS-006', 1); 178 | nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2); 179 | 180 | if (typeof a1 === 'string') { 181 | poolAlias = a1; 182 | } else if (nodbUtil.isObject(a1)) { 183 | connAttrs = a1; 184 | 185 | if (connAttrs.poolAlias) { 186 | poolAlias = connAttrs.poolAlias; 187 | } 188 | } 189 | 190 | getConnectionCb = a2; 191 | 192 | break; 193 | } 194 | 195 | // Proceed to execution based on values in local variables. Look for the poolAlias 196 | // first and only attempt to use connAttrs if the poolAlias isn't set. 197 | if (poolAlias) { 198 | pool = poolCache[poolAlias]; 199 | 200 | if (!pool) { 201 | getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias))); 202 | return; 203 | } 204 | 205 | pool.getConnection(getConnectionCb); 206 | } else { 207 | self._getConnection(connAttrs, function(err, connInst) { 208 | if (err) { 209 | getConnectionCb(err); 210 | return; 211 | } 212 | 213 | connection.extend(connInst, self); 214 | 215 | getConnectionCb(null, connInst); 216 | }); 217 | } 218 | } 219 | 220 | getConnectionPromisified = nodbUtil.promisify(getConnection); 221 | 222 | // The extend method is used to extend the Oracledb instance from the C layer with 223 | // custom properties and method overrides. References to the original methods are 224 | // maintained so they can be invoked by the overriding method at the right time. 225 | function extend(oracledb) { 226 | // Using Object.defineProperties to add properties to the Oracledb instance with 227 | // special properties, such as enumerable but not writable. A number of constants 228 | // (uppercase names) are added for use in various method calls. 229 | Object.defineProperties( 230 | oracledb, 231 | { 232 | _oracledb: { // Known to be used in util.js' promisify function. 233 | value: oracledb 234 | }, 235 | DEFAULT: { 236 | value: 0, 237 | enumerable: true 238 | }, 239 | DB_TYPE_VARCHAR: { 240 | value: 1, 241 | enumerable: true 242 | }, 243 | DB_TYPE_NUMBER: { 244 | value: 2, 245 | enumerable: true 246 | }, 247 | DB_TYPE_DATE: { 248 | value: 12, 249 | enumerable: true 250 | }, 251 | DB_TYPE_RAW: { 252 | value: 23, 253 | enumerable: true 254 | }, 255 | DB_TYPE_CHAR: { 256 | value: 96, 257 | enumerable: true 258 | }, 259 | DB_TYPE_BINARY_FLOAT: { 260 | value: 100, 261 | enumerable: true 262 | }, 263 | DB_TYPE_BINARY_DOUBLE: { 264 | value: 101, 265 | enumerable: true 266 | }, 267 | DB_TYPE_ROWID: { 268 | value: 104, 269 | enumerable: true 270 | }, 271 | DB_TYPE_CLOB: { 272 | value: 112, 273 | enumerable: true 274 | }, 275 | DB_TYPE_BLOB: { 276 | value: 113, 277 | enumerable: true 278 | }, 279 | DB_TYPE_TIMESTAMP: { 280 | value: 187, 281 | enumerable: true 282 | }, 283 | DB_TYPE_TIMESTAMP_TZ: { 284 | value: 188, 285 | enumerable: true 286 | }, 287 | DB_TYPE_TIMESTAMP_LTZ: { 288 | value: 232, 289 | enumerable: true 290 | }, 291 | STRING: { 292 | value: 2001, 293 | enumerable: true 294 | }, 295 | NUMBER: { 296 | value: 2002, 297 | enumerable: true 298 | }, 299 | DATE: { 300 | value: 2003, 301 | enumerable: true 302 | }, 303 | CURSOR: { 304 | value: 2004, 305 | enumerable: true 306 | }, 307 | BUFFER: { 308 | value: 2005, 309 | enumerable: true 310 | }, 311 | CLOB: { 312 | value: 2006, 313 | enumerable: true 314 | }, 315 | BLOB: { 316 | value: 2007, 317 | enumerable: true 318 | }, 319 | BIND_IN: { 320 | value: 3001, 321 | enumerable: true 322 | }, 323 | BIND_INOUT: { 324 | value: 3002, 325 | enumerable: true 326 | }, 327 | BIND_OUT: { 328 | value: 3003, 329 | enumerable: true 330 | }, 331 | ARRAY: { 332 | value: 4001, 333 | enumerable: true 334 | }, 335 | OBJECT: { 336 | value: 4002, 337 | enumerable: true 338 | }, 339 | Promise: { 340 | value: global.Promise, 341 | enumerable: true, 342 | writable: true 343 | }, 344 | Oracledb: { 345 | value: oracledbCLib.Oracledb, 346 | enumerable: true 347 | }, 348 | Connection: { 349 | value: oracledbCLib.Connection, 350 | enumerable: true 351 | }, 352 | Lob: { 353 | value: Lob, 354 | enumerable: true 355 | }, 356 | Pool: { 357 | value: oracledbCLib.Pool, 358 | enumerable: true 359 | }, 360 | ResultSet: { 361 | value: oracledbCLib.ResultSet, 362 | enumerable: true 363 | }, 364 | queueRequests: { 365 | value: true, 366 | enumerable: true, 367 | writable: true 368 | }, 369 | queueTimeout: { 370 | value: 60000, 371 | enumerable: true, 372 | writable: true 373 | }, 374 | _createPool: { 375 | value: oracledb.createPool 376 | }, 377 | createPool: { 378 | value: createPoolPromisified, 379 | enumerable: true, 380 | writable: true 381 | }, 382 | getPool: { 383 | value: getPool, 384 | enumerable: true, 385 | writable: true 386 | }, 387 | _getConnection: { 388 | value: oracledb.getConnection 389 | }, 390 | getConnection: { 391 | value: getConnectionPromisified, 392 | enumerable: true, 393 | writable: true 394 | } 395 | } 396 | ); 397 | } 398 | 399 | oracledbInst = new oracledbCLib.Oracledb(); 400 | 401 | extend(oracledbInst); 402 | 403 | module.exports = oracledbInst; 404 | -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var connection = require('./connection.js'); 23 | var nodbUtil = require('./util.js'); 24 | var getConnectionPromisified; 25 | var terminatePromisified; 26 | 27 | // completeConnectionRequest does the actual work of getting a connection from a 28 | // pool when queuing is enabled. It's abstracted out so it can be called from 29 | // getConnection and checkRequestQueue constently. 30 | function completeConnectionRequest(getConnectionCb) { 31 | var self = this; 32 | 33 | // Incrementing _connectionsOut prior to making the async call to get a connection 34 | // to prevent other connection requests from exceeding the poolMax. 35 | self._connectionsOut += 1; 36 | 37 | self._getConnection(function(err, connInst) { 38 | if (err) { 39 | // Decrementing _connectionsOut if we didn't actually get a connection 40 | // and then rechecking the queue. 41 | self._connectionsOut -= 1; 42 | 43 | if (self._enableStats) { 44 | self._totalFailedRequests += 1; 45 | } 46 | 47 | process.nextTick(function() { 48 | checkRequestQueue.call(self); 49 | }); 50 | 51 | getConnectionCb(err); 52 | 53 | return; 54 | } 55 | 56 | connection.extend(connInst, self._oracledb, self); 57 | 58 | connInst.on('_after_close', function() { 59 | self._connectionsOut -= 1; 60 | 61 | checkRequestQueue.call(self); 62 | }); 63 | 64 | getConnectionCb(null, connInst); 65 | }); 66 | } 67 | 68 | // Requests for connections from pools are queued by default (can be overridden 69 | // by setting the poolAttrs property queueRequests to false). checkRequestQueue 70 | // determines when requests for connections should be completed and cancels any 71 | // timeout that may have been associated with the request. 72 | function checkRequestQueue() { 73 | var self = this; 74 | var payload; 75 | var waitTime; 76 | 77 | if (self._connRequestQueue.length === 0 || self._connectionsOut === self.poolMax) { 78 | return; // no need to do any work 79 | } 80 | 81 | payload = self._connRequestQueue.shift(); 82 | 83 | if (self._enableStats) { 84 | self._totalRequestsDequeued += 1; 85 | 86 | waitTime = Date.now() - payload.enqueuedTime; 87 | 88 | self._totalTimeInQueue += waitTime; 89 | self._minTimeInQueue = Math.min(self._minTimeInQueue, waitTime); 90 | self._maxTimeInQueue = Math.max(self._maxTimeInQueue, waitTime); 91 | } 92 | 93 | if (self._usingQueueTimeout) { 94 | clearTimeout(payload.timeoutHandle); 95 | 96 | delete self._connRequestTimersMap[payload.timerIdx]; 97 | payload.timeoutHandle = null; 98 | payload.timerIdx = null; 99 | } 100 | 101 | completeConnectionRequest.call(self, payload.getConnectionCb); 102 | } 103 | 104 | // onRequestTimeout is used to prevent requests for connections from sitting in the 105 | // queue for too long. The number of milliseconds can be set via queueTimeout 106 | // property of the poolAttrs used when creating a pool. 107 | function onRequestTimeout(timerIdx) { 108 | var self = this; 109 | var payloadToDequeue = self._connRequestTimersMap[timerIdx]; 110 | var requestIndex; 111 | 112 | if (payloadToDequeue) { 113 | if (self._enableStats) { 114 | self._totalRequestTimeouts += 1; 115 | self._totalTimeInQueue += Date.now() - payloadToDequeue.enqueuedTime; 116 | } 117 | 118 | requestIndex = self._connRequestQueue.indexOf(payloadToDequeue); 119 | 120 | self._connRequestQueue.splice(requestIndex, 1); 121 | delete self._connRequestTimersMap[timerIdx]; 122 | 123 | payloadToDequeue.getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-040'))); 124 | } 125 | } 126 | 127 | // This getConnection function is used the override the getConnection method of the 128 | // Pool class, which is defined in the C layer. The method will proxy requests 129 | // directly to the C layer if queueing is disabled. If queueing is enabled and the 130 | // connections out is under the poolMax then the request will be completed immediately. 131 | // Otherwise the request will be queued and completed when a connection is avaialble. 132 | function getConnection(getConnectionCb) { 133 | var self = this; 134 | var payload; 135 | var timeoutHandle; 136 | var timerIdx; 137 | 138 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 139 | nodbUtil.assert(typeof getConnectionCb === 'function', 'NJS-006', 1); 140 | 141 | // Added this check because if the pool isn't valid and we reference self.poolMax 142 | // (which is a C layer getter) an error will be thrown. 143 | if (!self._isValid) { 144 | if (getConnectionCb && typeof getConnectionCb === 'function') { 145 | getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-002'))); 146 | return; 147 | } else { 148 | throw new Error(nodbUtil.getErrorMessage('NJS-002')); 149 | } 150 | } 151 | 152 | if (self._enableStats) { 153 | self._totalConnectionRequests += 1; 154 | } 155 | 156 | if (self.queueRequests === false) { // queueing is disabled for pool 157 | self._getConnection(function(err, connInst) { 158 | if (err) { 159 | if (self._enableStats) { 160 | self._totalFailedRequests += 1; 161 | } 162 | 163 | getConnectionCb(err); 164 | 165 | return; 166 | } 167 | 168 | connection.extend(connInst, self._oracledb, self); 169 | 170 | getConnectionCb(null, connInst); 171 | }); 172 | } else if (self._connectionsOut < self.poolMax) { // queueing enabled, but not needed 173 | completeConnectionRequest.call(self, getConnectionCb); 174 | } else { // need to queue the request 175 | if (self._usingQueueTimeout) { 176 | self._connRequestTimersIdx += 1; 177 | timerIdx = self._connRequestTimersIdx; 178 | 179 | timeoutHandle = setTimeout( 180 | function() { 181 | onRequestTimeout.call(self, timerIdx); 182 | }, 183 | self.queueTimeout 184 | ); 185 | } 186 | 187 | payload = { 188 | timerIdx: timerIdx, 189 | timeoutHandle: timeoutHandle, 190 | getConnectionCb: getConnectionCb 191 | }; 192 | 193 | if (self._usingQueueTimeout) { 194 | self._connRequestTimersMap[timerIdx] = payload; 195 | } 196 | 197 | self._connRequestQueue.push(payload); 198 | 199 | if (self._enableStats) { 200 | payload.enqueuedTime = Date.now(); 201 | self._totalRequestsEnqueued += 1; 202 | self._maxQueueLength = Math.max(self._maxQueueLength, self._connRequestQueue.length); 203 | } 204 | } 205 | } 206 | 207 | getConnectionPromisified = nodbUtil.promisify(getConnection); 208 | 209 | function terminate(terminateCb) { 210 | var self = this; 211 | 212 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 213 | nodbUtil.assert(typeof terminateCb === 'function', 'NJS-006', 1); 214 | 215 | self._terminate(function(err) { 216 | if (!err) { 217 | self._isValid = false; 218 | 219 | self.emit('_after_close', self); 220 | } 221 | 222 | terminateCb(err); 223 | }); 224 | } 225 | 226 | terminatePromisified = nodbUtil.promisify(terminate); 227 | 228 | // logStats is used to add a hidden method (_logStats) to each pool instance. This 229 | // provides an easy way to log out the statistics related information that's collected 230 | // when _enableStats is set to true when creating a pool. This functionality may 231 | // be altered or enhanced in the future. 232 | function logStats() { 233 | var self = this; 234 | var averageTimeInQueue; 235 | 236 | if (!self._isValid) { 237 | throw new Error(nodbUtil.getErrorMessage('NJS-002')); 238 | } 239 | 240 | if (self._enableStats !== true) { 241 | console.log('Pool statistics not enabled'); 242 | return; 243 | } 244 | 245 | averageTimeInQueue = 0; 246 | 247 | if (self.queueRequests && self._totalRequestsEnqueued !== 0) { 248 | averageTimeInQueue = Math.round(self._totalTimeInQueue/self._totalRequestsEnqueued); 249 | } 250 | 251 | console.log('\nPool statistics:'); 252 | console.log('...total up time (milliseconds):', Date.now() - self._createdDate); 253 | console.log('...total connection requests:', self._totalConnectionRequests); 254 | console.log('...total requests enqueued:', self._totalRequestsEnqueued); 255 | console.log('...total requests dequeued:', self._totalRequestsDequeued); 256 | console.log('...total requests failed:', self._totalFailedRequests); 257 | console.log('...total request timeouts:', self._totalRequestTimeouts); 258 | console.log('...max queue length:', self._maxQueueLength); 259 | console.log('...sum of time in queue (milliseconds):', self._totalTimeInQueue); 260 | console.log('...min time in queue (milliseconds):', self._minTimeInQueue); 261 | console.log('...max time in queue (milliseconds):', self._maxTimeInQueue); 262 | console.log('...avg time in queue (milliseconds):', averageTimeInQueue); 263 | console.log('...pool connections in use:', self.connectionsInUse); 264 | console.log('...pool connections open:', self.connectionsOpen); 265 | console.log('Related pool attributes:'); 266 | console.log('...poolAlias:', self.poolAlias); 267 | console.log('...queueRequests:', self.queueRequests); 268 | console.log('...queueTimeout (milliseconds):', self.queueTimeout); 269 | console.log('...poolMin:', self.poolMin); 270 | console.log('...poolMax:', self.poolMax); 271 | console.log('...poolIncrement:', self.poolIncrement); 272 | console.log('...poolTimeout (seconds):', self.poolTimeout); 273 | console.log('...poolPingInterval:', self.poolPingInterval); 274 | console.log('...stmtCacheSize:', self.stmtCacheSize); 275 | console.log('Related environment variables:'); 276 | console.log('...process.env.UV_THREADPOOL_SIZE:', process.env.UV_THREADPOOL_SIZE); 277 | } 278 | 279 | // The extend method is used to extend Pool instances from the C layer with custom 280 | // properties, methods, and method overrides. References to the original methods are 281 | // maintained so they can be invoked by the overriding method at the right time. 282 | function extend(pool, poolAttrs, poolAlias, oracledb) { 283 | var queueRequests; 284 | var queueTimeout; 285 | 286 | if (typeof poolAttrs.queueRequests !== 'undefined') { 287 | queueRequests = poolAttrs.queueRequests; 288 | } else { 289 | queueRequests = oracledb.queueRequests; 290 | } 291 | 292 | if (typeof poolAttrs.queueTimeout !== 'undefined') { 293 | queueTimeout = poolAttrs.queueTimeout; 294 | } else { 295 | queueTimeout = oracledb.queueTimeout; 296 | } 297 | 298 | nodbUtil.makeEventEmitter(pool); 299 | 300 | // Using Object.defineProperties to add properties to the Pool instance with special 301 | // properties, such as enumerable but not writable. 302 | Object.defineProperties( 303 | pool, 304 | { 305 | _oracledb: { // storing a reference to the base instance to avoid circular references with require 306 | value: oracledb 307 | }, 308 | queueRequests: { // true will queue requests when conn pool is maxed out 309 | enumerable: true, 310 | get: function() { 311 | return queueRequests; 312 | }, 313 | set: function() { 314 | throw new Error(nodbUtil.getErrorMessage('NJS-014', 'queueRequests')); 315 | } 316 | }, 317 | queueTimeout: { // milliseconds a connection request can spend in queue before being failed 318 | enumerable: true, 319 | get: function() { 320 | return queueTimeout; 321 | }, 322 | set: function() { 323 | throw new Error(nodbUtil.getErrorMessage('NJS-014', 'queueTimeout')); 324 | } 325 | }, 326 | _isValid: { // used to ensure operations are not done after terminate 327 | value: true, 328 | writable: true 329 | }, 330 | _enableStats: { // true means pool stats will be recorded 331 | value: poolAttrs._enableStats === true 332 | }, 333 | _logStats: { // output pool stats 334 | value: logStats 335 | }, 336 | _createdDate: { 337 | value: Date.now() 338 | }, 339 | _totalConnectionRequests: { // total number of pool.getConnection requests 340 | value: 0, 341 | writable: true 342 | }, 343 | _totalRequestsEnqueued: { // number of pool.getConnection requests added to queue 344 | value: 0, 345 | writable: true 346 | }, 347 | _totalRequestsDequeued: { // number of pool.getConnection requests removed from queue because a pool connection became available 348 | value: 0, 349 | writable: true 350 | }, 351 | _totalFailedRequests: { // number of pool.getConnection requests that failed at the C layer 352 | value: 0, 353 | writable: true 354 | }, 355 | _totalRequestTimeouts: { // number of queued pool.getConnection requests that timed out without being satisfied 356 | value: 0, 357 | writable: true 358 | }, 359 | _totalTimeInQueue: { // sum of time in milliseconds that all pool.getConnection requests spent in the queue 360 | value: 0, 361 | writable: true 362 | }, 363 | _maxQueueLength: { // maximum length of pool queue 364 | value: 0, 365 | writable: true 366 | }, 367 | _minTimeInQueue: { // shortest amount of time (milliseconds) that any pool.getConnection request spent in queue 368 | value: 0, 369 | writable: true 370 | }, 371 | _maxTimeInQueue: { // longest amount of time (milliseconds) that any pool.getConnection request spent in queue 372 | value: 0, 373 | writable: true 374 | }, 375 | _usingQueueTimeout: { 376 | value: queueTimeout !== 0 377 | }, 378 | _connectionsOut: { // number of connections checked out from the pool. Must be inc/dec in the main thread in JS 379 | value: 0, 380 | writable: true 381 | }, 382 | _connRequestQueue: { 383 | value: [], 384 | writable: true 385 | }, 386 | _connRequestTimersIdx: { 387 | value: 0, 388 | writable: true 389 | }, 390 | _connRequestTimersMap: { 391 | value: {}, 392 | writable: true 393 | }, 394 | _getConnection: { 395 | value: pool.getConnection 396 | }, 397 | poolAlias: { 398 | enumerable: true, 399 | get: function() { 400 | return poolAlias; 401 | }, 402 | set: function() { 403 | throw new Error(nodbUtil.getErrorMessage('NJS-014', 'poolAlias')); 404 | } 405 | }, 406 | getConnection: { 407 | value: getConnectionPromisified, 408 | enumerable: true, 409 | writable: true 410 | }, 411 | _terminate: { 412 | value: pool.terminate 413 | }, 414 | terminate: { 415 | value: terminatePromisified, 416 | enumerable: true, 417 | writable: true 418 | }, 419 | close: { // alias for terminate 420 | value: terminatePromisified, 421 | enumerable: true, 422 | writable: true 423 | } 424 | } 425 | ); 426 | } 427 | 428 | module.exports.extend = extend; 429 | -------------------------------------------------------------------------------- /lib/querystream.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var util = require('util'); 23 | var Readable = require('stream').Readable; 24 | 25 | // This class was originally based on https://github.com/sagiegurari/simple-oracledb/blob/master/lib/resultset-read-stream.js 26 | function QueryStream(resultSet, oracledb) { 27 | var self = this; 28 | 29 | Object.defineProperties( 30 | self, 31 | { 32 | _oracledb: { // storing a reference to the base instance to avoid circular references with require 33 | value: oracledb 34 | }, 35 | _resultSet: { 36 | value: resultSet, 37 | writable: true 38 | }, 39 | _fetchedRows: { // a local cache of rows fetched from a call to resultSet.getRows 40 | value: [], 41 | writable: true 42 | }, 43 | _fetchedAllRows: { // used to avoid an unnecessary call to resultSet.getRows 44 | value: false, 45 | writable: true 46 | }, 47 | _fetching: { // used to serialize method calls on the resultset 48 | value: false, 49 | writable: true 50 | }, 51 | _closed: { // used to track that the stream is closed 52 | value: false, 53 | writable: true 54 | } 55 | } 56 | ); 57 | 58 | Readable.call(self, { 59 | objectMode: true 60 | }); 61 | 62 | if (self._resultSet) { // If true, no need to invoke _open, we are ready to go. 63 | self.emit('metadata', self._resultSet.metaData); 64 | 65 | self.emit('open'); 66 | } 67 | } 68 | 69 | util.inherits(QueryStream, Readable); 70 | 71 | // The _open method is only meant to be called when a QueryStream is created 72 | // but not passed in the resultSet during initialization. In those cases the 73 | // QueryStream object will have been returned immediately and the _open method 74 | // will be called later to pass the resultset (or error getting the resultset) 75 | // along. 76 | QueryStream.prototype._open = function(err, rs) { 77 | var self = this; 78 | 79 | if (err) { 80 | self.emit('error', err); 81 | return; 82 | } 83 | 84 | self._resultSet = rs; 85 | 86 | self.emit('metadata', self._resultSet.metaData); 87 | 88 | // Trigger the event listener that may have been added in _read now that the 89 | // resultset is ready. 90 | self.emit('open'); 91 | }; 92 | 93 | // The stream _read implementation which fetches the next row from the resultset. 94 | QueryStream.prototype._read = function () { 95 | var self = this; 96 | var fetchCount; 97 | 98 | if (!self._resultSet) { 99 | // Still waiting on the resultset, add an event listener to retry when ready 100 | return self.once('open', function() { 101 | self._read(); 102 | }); 103 | } 104 | 105 | if (self._closed) { 106 | return; 107 | } 108 | 109 | if (self._fetchedRows.length) { 110 | // We have rows already fetched that need to be pushed 111 | self.push(self._fetchedRows.shift()); 112 | } else if (self._fetchedAllRows) { 113 | // Calling the C layer close directly to avoid assertions on the public method 114 | self._resultSet._close(function(err) { 115 | if (err) { 116 | self.emit('error', err); 117 | return; 118 | } 119 | 120 | // Signal the end of the stream 121 | self.push(null); 122 | }); 123 | } else { 124 | // Using _fetching to indicate that the resultset is working to avoid potential 125 | // errors related to close w/conncurrent operations on resultsets 126 | self._fetching = true; 127 | 128 | fetchCount = self._oracledb.maxRows || 100; 129 | 130 | // Calling the C layer getRows directly to avoid assertions on the public method 131 | self._resultSet._getRows(fetchCount, function(err, rows) { 132 | if (err) { 133 | // We'll return the error from getRows, but first try to close the resultSet. 134 | // Calling the C layer close directly to avoid assertions on the public method 135 | self._resultSet._close(function() { 136 | self.emit('error', err); 137 | }); 138 | 139 | return; 140 | } 141 | 142 | self._fetching = false; 143 | 144 | // Close may have been called while the resultset was fetching. 145 | if (self._closed) { 146 | // Trigger the event listener that may have been added in close now that 147 | // the resultset has finished working. 148 | self.emit('_doneFetching'); 149 | return; 150 | } 151 | 152 | self._fetchedRows = rows; 153 | 154 | if (self._fetchedRows.length < fetchCount) { 155 | self._fetchedAllRows = true; 156 | } 157 | 158 | if (self._fetchedRows.length) { 159 | self.push(self._fetchedRows.shift()); 160 | } else { // No more rows to fetch 161 | // Calling the C layer close directly to avoid assertions on the public method 162 | self._resultSet._close(function(err) { 163 | if (err) { 164 | self.emit('error', err); 165 | return; 166 | } 167 | 168 | // Signal the end of the stream 169 | self.push(null); 170 | }); 171 | } 172 | }); 173 | } 174 | }; 175 | 176 | // The close method is not a standard method on stream instances in Node.js but 177 | // it was added to provide developers with a means of stopping the flow of data 178 | // and closing the stream without having to allow the entire resultset to finish 179 | // streaming. 180 | function close(callback) { 181 | var self = this; 182 | 183 | // Setting _closed early to prevent _read invocations from being processed and 184 | // to allow _doneFetching to be emitted if needed. 185 | self._closed = true; 186 | 187 | // Node.js 0.10 didn't have an isPaused method that could be used to prevent 188 | // an unnecessary pause event from being emitted (added in 0.11.14). We'll 189 | // check for the existence of such a method and use it if possible, otherwise 190 | // we'll just call pause. This could be simplified a little when support for 191 | // Node.js 0.10 is dropped. 192 | if (typeof self.isPaused === 'function' && !self.isPaused()) { 193 | self.pause(); 194 | } else { 195 | self.pause(); 196 | } 197 | 198 | // We can't close the resultset if it's currently fetching. Add a listener 199 | // to call close when the resulset is done fetching. 200 | if (self._fetching) { 201 | self.once('_doneFetching', function() { 202 | self._close(callback); 203 | }); 204 | 205 | return; 206 | } 207 | 208 | if (callback) { 209 | self.once('close', callback); 210 | } 211 | 212 | // It's possible for close to be called very early, even before the resultset 213 | // has been set via _open (if needed). 214 | if (!self._resultSet) { 215 | self.emit('close'); 216 | } else { 217 | // Calling the C layer close directly to avoid assertions on the public method 218 | self._resultSet._close(function(err) { 219 | if (err) { 220 | self.emit('error', err); 221 | return; 222 | } 223 | 224 | self.emit('close'); 225 | }); 226 | } 227 | } 228 | 229 | // Exposing close as a private method for now. 230 | Object.defineProperty( 231 | QueryStream.prototype, 232 | '_close', 233 | { 234 | value: close, 235 | writable: true 236 | } 237 | ); 238 | 239 | module.exports = QueryStream; 240 | -------------------------------------------------------------------------------- /lib/resultset.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var QueryStream = require('./querystream.js'); 23 | var nodbUtil = require('./util.js'); 24 | var closePromisified; 25 | var getRowPromisified; 26 | var getRowsPromisified; 27 | 28 | // This close function is just a place holder to allow for easier extension later. 29 | function close(closeCb) { 30 | var self = this; 31 | 32 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 33 | nodbUtil.assert(typeof closeCb === 'function', 'NJS-006', 1); 34 | 35 | if (self._convertedToStream) { 36 | closeCb(new Error(nodbUtil.getErrorMessage('NJS-042'))); 37 | return; 38 | } 39 | 40 | self._processingStarted = true; 41 | 42 | self._close(function(err) { 43 | 44 | closeCb(err); 45 | }); 46 | } 47 | 48 | closePromisified = nodbUtil.promisify(close); 49 | 50 | // This getRow function is just a place holder to allow for easier extension later. 51 | function getRow(getRowCb) { 52 | var self = this; 53 | 54 | nodbUtil.assert(arguments.length === 1, 'NJS-009'); 55 | nodbUtil.assert(typeof getRowCb === 'function', 'NJS-006', 1); 56 | 57 | if (self._convertedToStream) { 58 | getRowCb(new Error(nodbUtil.getErrorMessage('NJS-042'))); 59 | return; 60 | } 61 | 62 | self._processingStarted = true; 63 | 64 | self._getRow.apply(self, arguments); 65 | } 66 | 67 | getRowPromisified = nodbUtil.promisify(getRow); 68 | 69 | // This getRows function is just a place holder to allow for easier extension later. 70 | function getRows(numRows, getRowsCb) { 71 | var self = this; 72 | 73 | nodbUtil.assert(arguments.length === 2, 'NJS-009'); 74 | nodbUtil.assert(typeof numRows === 'number', 'NJS-006', 1); 75 | nodbUtil.assert(typeof getRowsCb === 'function', 'NJS-006', 2); 76 | 77 | if (self._convertedToStream) { 78 | getRowsCb(new Error(nodbUtil.getErrorMessage('NJS-042'))); 79 | return; 80 | } 81 | 82 | self._processingStarted = true; 83 | 84 | self._getRows.apply(self, arguments); 85 | } 86 | 87 | getRowsPromisified = nodbUtil.promisify(getRows); 88 | 89 | function toQueryStream() { 90 | var self = this; 91 | var stream; 92 | 93 | nodbUtil.assert(arguments.length === 0, 'NJS-009'); 94 | 95 | if (self._processingStarted) { 96 | throw new Error(nodbUtil.getErrorMessage('NJS-041')); 97 | } 98 | 99 | if (self._convertedToStream) { 100 | throw new Error(nodbUtil.getErrorMessage('NJS-043')); 101 | } 102 | 103 | self._convertedToStream = true; 104 | 105 | stream = new QueryStream(self, self._oracledb); 106 | 107 | return stream; 108 | } 109 | 110 | // The extend method is used to extend the ResultSet instance from the C layer with 111 | // custom properties and method overrides. References to the original methods are 112 | // maintained so they can be invoked by the overriding method at the right time. 113 | function extend(resultSet, oracledb) { 114 | // Using Object.defineProperties to add properties to the ResultSet instance with 115 | // special properties, such as enumerable but not writable. 116 | Object.defineProperties( 117 | resultSet, 118 | { 119 | _oracledb: { // storing a reference to the base instance to avoid circular references with require 120 | value: oracledb 121 | }, 122 | _processingStarted: { // used to prevent conversion to stream after invoking methods 123 | value: false, 124 | writable: true 125 | }, 126 | _convertedToStream: { // used to prevent invoking methods after conversion to stream 127 | value: false, 128 | writable: true 129 | }, 130 | _close: { 131 | value: resultSet.close 132 | }, 133 | close: { 134 | value: closePromisified, 135 | enumerable: true, 136 | writable: true 137 | }, 138 | _getRow: { 139 | value: resultSet.getRow 140 | }, 141 | getRow: { 142 | value: getRowPromisified, 143 | enumerable: true, 144 | writable: true 145 | }, 146 | _getRows: { 147 | value: resultSet.getRows 148 | }, 149 | getRows: { 150 | value: getRowsPromisified, 151 | enumerable: true, 152 | writable: true 153 | }, 154 | toQueryStream: { 155 | value: toQueryStream, 156 | enumerable: true, 157 | writable: true 158 | } 159 | } 160 | ); 161 | } 162 | 163 | module.exports.extend = extend; 164 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | /****************************************************************************** 4 | * 5 | * You may not use the identified files except in compliance with the Apache 6 | * License, Version 2.0 (the "License.") 7 | * 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | *****************************************************************************/ 19 | 20 | 'use strict'; 21 | 22 | var util = require('util'); 23 | 24 | var EventEmitter = require('events').EventEmitter; 25 | var eventEmitterKeys = Object.keys(EventEmitter.prototype); 26 | var eventEmitterFuncKeys = eventEmitterKeys.filter(function(key) { 27 | return typeof EventEmitter.prototype[key] === 'function'; 28 | }); 29 | 30 | // errorMessages is a temporary duplication of error messages defined in the C 31 | // layer that will be removed once a function to fetch from the C layer is added. 32 | var errorMessages = { 33 | 'NJS-002': 'NJS-002: invalid pool', 34 | 'NJS-004': 'NJS-004: invalid value for property %s', 35 | 'NJS-005': 'NJS-005: invalid value for parameter %d', 36 | 'NJS-006': 'NJS-006: invalid type for parameter %d', 37 | 'NJS-009': 'NJS-009: invalid number of parameters', 38 | 'NJS-014': 'NJS-014: %s is a read-only property', 39 | 'NJS-037': 'NJS-037: incompatible type of value provided', 40 | 'NJS-040': 'NJS-040: connection request timeout', 41 | 'NJS-041': 'NJS-041: cannot convert ResultSet to QueryStream after invoking methods', 42 | 'NJS-042': 'NJS-042: cannot invoke ResultSet methods after converting to QueryStream', 43 | 'NJS-043': 'NJS-043: ResultSet already converted to QueryStream', 44 | 'NJS-045': 'NJS-045: cannot load the oracledb add-on binary', 45 | 'NJS-046': 'NJS-046: poolAlias "%s" already exists in the connection pool cache', 46 | 'NJS-047': 'NJS-047: poolAlias "%s" not found in the connection pool cache' 47 | }; 48 | 49 | // makeEventEmitter is used to make class instances inherit from the EventEmitter 50 | // class. This is needed because we extend instances from the C layer and thus 51 | // don't have JavaScript constructor functions we can use for more traditional 52 | // inheritance. 53 | function makeEventEmitter(instance){ 54 | eventEmitterFuncKeys.forEach(function(key) { 55 | instance[key] = EventEmitter.prototype[key]; 56 | }); 57 | 58 | EventEmitter.call(instance); 59 | } 60 | 61 | module.exports.makeEventEmitter = makeEventEmitter; 62 | 63 | // getErrorMessage is used to get and format error messages to make throwing errors 64 | // a little more convenient. 65 | function getErrorMessage(errorCode, messageArg1) { 66 | if (messageArg1) { 67 | return util.format(errorMessages[errorCode], messageArg1); 68 | } else { 69 | return util.format(errorMessages[errorCode]); 70 | } 71 | } 72 | 73 | module.exports.getErrorMessage = getErrorMessage; 74 | 75 | // assert it typically used in the beginning of public functions to assert preconditions 76 | // for the function to execute. Most commonly it's used to validate arguments lenght 77 | // and types and throw an error if they don't match what is expected. 78 | function assert(condition, errorCode, messageArg1) { 79 | if (!condition) { 80 | throw new Error(getErrorMessage(errorCode, messageArg1)); 81 | } 82 | } 83 | 84 | module.exports.assert = assert; 85 | 86 | // The promisify function is used to wrap async methods to add optional promise 87 | // support. If the last parameter passed to a method is a function, then it is 88 | // assumed that the callback pattern is being used and the method is invoked as 89 | // usual. Otherwise a promise is returned and later resolved or rejected based on 90 | // the return of the method. 91 | function promisify(func) { 92 | return function() { 93 | var self = this; 94 | var args; 95 | 96 | // This/self could refer to the base class instance, pool, connection, etc. All 97 | // class instances have a private reference to the base class for convenience. 98 | if (!self._oracledb.Promise || typeof arguments[arguments.length - 1] === 'function') { 99 | return func.apply(self, arguments); 100 | } else { 101 | // Converting to an array so we can extend it later with a custom callback 102 | args = Array.prototype.slice.call(arguments); 103 | 104 | return new self._oracledb.Promise(function(resolve, reject) { 105 | var errorCode; 106 | 107 | try { 108 | args[args.length] = function(err, result) { 109 | if (err) { 110 | reject(err); 111 | } else { 112 | resolve(result); 113 | } 114 | }; 115 | 116 | func.apply(self, args); 117 | } catch (err) { 118 | errorCode = err.message.substr(0, 7); 119 | 120 | // Check for invalid number or type of parameter(s) as they should be 121 | // eagerly thrown. 122 | if (errorCode === 'NJS-009' || errorCode === 'NJS-006') { 123 | // Throwing the error outside of the promise wrapper so that its not 124 | // swallowed up as a rejection. 125 | process.nextTick(function() { 126 | throw err; 127 | }); 128 | } else { 129 | reject(err); 130 | } 131 | } 132 | }); 133 | } 134 | }; 135 | } 136 | 137 | module.exports.promisify = promisify; 138 | 139 | function isObject(value) { 140 | return value !== null && typeof value === 'object'; 141 | } 142 | 143 | module.exports.isObject = isObject; 144 | 145 | function isObjectOrArray(value) { 146 | return (value !== null && typeof value === 'object') || Array.isArray(value); 147 | } 148 | 149 | module.exports.isObjectOrArray = isObjectOrArray; 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracledb-for-lambda", 3 | "version": "1.9.3-8a", 4 | "description": "oracledb, precompiled for AWS Lambda", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "unzip -u lambda-lib.zip -d ../../lib && mv ../../lib/libociei.so lib/libociei.so && rm lambda-lib.zip" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/nalbion/node-oracledb-for-lambda.git" 12 | }, 13 | "keywords": [ 14 | "oracledb", 15 | "Node.js", 16 | "Oracle", 17 | "Database", 18 | "OCI", 19 | "Lambda" 20 | ], 21 | "author": "Nicholas Albion ", 22 | "license": "Apache-2.0", 23 | "bugs": { 24 | "url": "https://github.com/nalbion/node-oracledb-for-lambda/issues" 25 | }, 26 | "homepage": "https://github.com/nalbion/node-oracledb-for-lambda#readme", 27 | "dependencies": { 28 | "nan": "~2.3.0" 29 | } 30 | } 31 | --------------------------------------------------------------------------------