├── .gitattributes ├── .stylelintrc.json ├── test ├── helpers │ ├── test.sql │ ├── utils.js │ ├── scripts │ │ └── setup.sh │ ├── test-oracledb.js │ └── integration-helper.js └── spec │ ├── index-spec.js │ ├── emitter-spec.js │ ├── constants-spec.js │ ├── extensions-spec.js │ ├── monitor-spec.js │ ├── resultset-read-stream-spec.js │ ├── stream-spec.js │ ├── stability-spec.js │ ├── simple-oracledb-spec.js │ ├── promise-helper-spec.js │ ├── record-writer-spec.js │ ├── record-reader-spec.js │ └── rows-reader-spec.js ├── index.js ├── .gitignore ├── .npmignore ├── .editorconfig ├── inch.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── workflows │ └── ci.yml └── CONTRIBUTING.md ├── lib ├── emitter.js ├── extensions.js ├── resultset-read-stream.js ├── record-writer.js ├── stream.js ├── constants.js ├── record-reader.js ├── rows-reader.js ├── monitor.js ├── promise-helper.js ├── resultset-reader.js ├── oracledb.js └── simple-oracledb.js ├── .eslintrc.js ├── .jsbeautifyrc ├── package.json ├── docs └── CHANGELOG.md └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /test/helpers/test.sql: -------------------------------------------------------------------------------- 1 | SELECT COL1, COL2 FROM MY_TABLE1 WHERE COL1 IS NOT NULL; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/simple-oracledb'); 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .temp 3 | node_modules 4 | bower_components 5 | npm-debug.log 6 | package-lock.json 7 | .nyc_output 8 | coverage 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | target 2 | .temp 3 | .nyc_output 4 | .config 5 | coverage 6 | bower_components 7 | .github 8 | test 9 | example 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.json] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /inch.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "included": [ 4 | "*.js", 5 | "lib/**/*.js", 6 | "tasks/**/*.js" 7 | ], 8 | "excluded": [ 9 | "**/Gruntfile.js", 10 | "**/.eslintrc.js", 11 | "**/stylelint.config.js" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/spec/index-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const simpleOracleDB = require('../../'); 6 | 7 | describe('Index Tests', function () { 8 | it('extend test', function () { 9 | const oracledb = require('../helpers/test-oracledb').create(); 10 | simpleOracleDB.extend(oracledb); 11 | assert.isTrue(oracledb.simplified); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: sagiegurari 7 | 8 | --- 9 | 10 | ### Feature Description 11 | 12 | 13 | ### Describe The Solution You'd Like 14 | 15 | 16 | ### Code Sample 17 | 18 | ```js 19 | // paste code here 20 | ``` 21 | -------------------------------------------------------------------------------- /test/helpers/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | createBuffer(value) { 5 | if ((typeof value === 'string') && Buffer.from) { 6 | return Buffer.from(value, 'utf8'); 7 | } else if ((typeof value === 'number') && Buffer.alloc) { 8 | return Buffer.alloc(value); 9 | } 10 | 11 | /*eslint-disable no-buffer-constructor*/ 12 | return new Buffer(value); 13 | /*eslint-enable no-buffer-constructor*/ 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: sagiegurari 7 | 8 | --- 9 | 10 | ### Describe The Bug 11 | 12 | 13 | ### To Reproduce 14 | 15 | 16 | ### Error Stack 17 | 18 | ```console 19 | The error stack trace 20 | ``` 21 | 22 | ### Code Sample 23 | 24 | ```js 25 | // paste code here 26 | ``` 27 | -------------------------------------------------------------------------------- /lib/emitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const events = require('events'); 4 | const EventEmitter = events.EventEmitter; 5 | 6 | /** 7 | * Extends the provided object with event emitter capabilities. 8 | * 9 | * @function 10 | * @memberof! Emitter 11 | * @private 12 | * @param {Object} object - The object to extend 13 | */ 14 | module.exports = function extend(object) { 15 | if (object && (!object.emit) && (!object.on)) { 16 | const functions = Object.keys(EventEmitter.prototype); 17 | 18 | functions.forEach(function addProperty(property) { 19 | object[property] = EventEmitter.prototype[property]; 20 | }); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'commonjs': true, 5 | 'es2021': true, 6 | 'mocha': true 7 | }, 8 | 'extends': 'eslint:recommended', 9 | 'parserOptions': { 10 | 'ecmaVersion': 13 11 | }, 12 | 'rules': { 13 | 'indent': [ 14 | 'error', 15 | 4 16 | ], 17 | 'linebreak-style': [ 18 | 'error', 19 | 'unix' 20 | ], 21 | 'quotes': [ 22 | 'error', 23 | 'single' 24 | ], 25 | 'semi': [ 26 | 'error', 27 | 'always' 28 | ] 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | env: 4 | CLICOLOR_FORCE: 1 5 | jobs: 6 | ci: 7 | name: CI 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: ['18.x'] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Install node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install Dependencies 21 | run: npm install 22 | - name: Run CI 23 | run: npm test 24 | - name: Coveralls 25 | uses: coverallsapp/github-action@master 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | path-to-lcov: './coverage/lcov.info' 29 | -------------------------------------------------------------------------------- /test/spec/emitter-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const emitter = require('../../lib/emitter'); 6 | 7 | describe('emitter Tests', function () { 8 | describe('extend', function () { 9 | it('undefined object', function () { 10 | emitter(); 11 | }); 12 | 13 | it('null object', function () { 14 | emitter(null); 15 | }); 16 | 17 | it('has on', function () { 18 | const object = { 19 | on: true 20 | }; 21 | emitter(object); 22 | 23 | assert.isUndefined(object.emit); 24 | }); 25 | 26 | it('has emit', function () { 27 | const object = { 28 | emit: true 29 | }; 30 | emitter(object); 31 | 32 | assert.isUndefined(object.on); 33 | }); 34 | 35 | it('valid', function () { 36 | const object = {}; 37 | emitter(object); 38 | 39 | assert.isFunction(object.on); 40 | assert.isFunction(object.emit); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/helpers/scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # this script should be invoked from the project root as follows: 4 | # . ./test/helpers/scripts/setup.sh 5 | 6 | # run oracle DB 7 | docker run -d -p 1521:1521 -e ORACLE_ALLOW_REMOTE=true oracleinanutshell/oracle-xe-11g 8 | 9 | # install oracle client 10 | mkdir -p ./target/oracleclient 11 | cd ./target/oracleclient 12 | wget https://download.oracle.com/otn_software/linux/instantclient/213000/instantclient-basiclite-linux.x64-21.3.0.0.0.zip . 13 | unzip ./*.zip 14 | cd ./instantclient_* 15 | export NODE_ORACLE_CLIENT_PATH=$(pwd) 16 | echo node lib path: ${NODE_ORACLE_CLIENT_PATH} 17 | export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${NODE_ORACLE_CLIENT_PATH} 18 | export PATH=${PATH}:${NODE_ORACLE_CLIENT_PATH} 19 | cd ../../../ 20 | 21 | # install dependencies 22 | sudo apt-get install -y libaio1 23 | 24 | npm install 25 | npm install --no-save oracledb 26 | 27 | export DPI_DEBUG_LEVEL=93 28 | export NODE_DEBUG=simple-oracledb 29 | 30 | export TEST_ORACLE_USER=system 31 | export TEST_ORACLE_PASSWORD=oracle 32 | export TEST_ORACLE_CONNECTION_STRING='(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SID=xe)))' 33 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | 4 | ## Issues 5 | 6 | Found a bug? Got a question? Want some enhancement?
7 | First place to go is the repository issues section, and I'll try to help as much as possible. 8 | 9 | ## Pull Requests 10 | 11 | Fixed a bug or just want to provided additional functionality?
12 | Simply fork this repository, implement your changes and create a pull request.
13 | Few guidelines regarding pull requests: 14 | 15 | * This repository is integrated with github actions for continuous integration.
16 | 17 | Your pull request build must pass (the build will run automatically).
18 | You can run the following command locally to ensure the build will pass: 19 | 20 | ````sh 21 | npm test 22 | ```` 23 | 24 | * This library is using multiple code inspection tools to validate certain level of standards.
The configuration is part of the repository and you can set your favorite IDE using that configuration.
You can run the following command locally to ensure the code inspection passes: 25 | 26 | ````sh 27 | npm run lint 28 | ```` 29 | 30 | * There are many automatic unit tests as part of the library which provide full coverage of the functionality.
Any fix/enhancement must come with a set of tests to ensure it's working well. 31 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "js": { 3 | "indent_size": 4, 4 | "indent_char": " ", 5 | "eol": "\n", 6 | "indent_level": 0, 7 | "indent_with_tabs": false, 8 | "preserve_newlines": true, 9 | "max_preserve_newlines": 2, 10 | "space_in_paren": false, 11 | "jslint_happy": true, 12 | "space_after_anon_function": true, 13 | "brace_style": "collapse", 14 | "break_chained_methods": false, 15 | "keep_array_indentation": true, 16 | "unescape_strings": false, 17 | "wrap_line_length": 0, 18 | "end_with_newline": true, 19 | "comma_first": false, 20 | "eval_code": false, 21 | "keep_function_indentation": false, 22 | "space_before_conditional": true, 23 | "good_stuff": true 24 | }, 25 | "css": { 26 | "indent_size": 2, 27 | "indent_char": " ", 28 | "indent_with_tabs": false, 29 | "eol": "\n", 30 | "end_with_newline": true, 31 | "selector_separator_newline": false, 32 | "newline_between_rules": true 33 | }, 34 | "html": { 35 | "indent_size": 4, 36 | "indent_char": " ", 37 | "indent_with_tabs": false, 38 | "eol": "\n", 39 | "end_with_newline": true, 40 | "preserve_newlines": true, 41 | "max_preserve_newlines": 2, 42 | "indent_inner_html": true, 43 | "brace_style": "collapse", 44 | "indent_scripts": "normal", 45 | "wrap_line_length": 0, 46 | "wrap_attributes": "auto", 47 | "wrap_attributes_indent_size": 4 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/spec/constants-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const constants = require('../../lib/constants'); 6 | 7 | describe('constants Tests', function () { 8 | describe('getParallelLimit', function () { 9 | it('undefined', function () { 10 | delete process.env.UV_THREADPOOL_SIZE; 11 | const output = constants.getParallelLimit(); 12 | 13 | assert.equal(output, 2); 14 | }); 15 | 16 | it('defined even number', function () { 17 | process.env.UV_THREADPOOL_SIZE = '6'; 18 | const output = constants.getParallelLimit(); 19 | 20 | assert.equal(output, 3); 21 | }); 22 | 23 | it('defined uneven number', function () { 24 | process.env.UV_THREADPOOL_SIZE = '9'; 25 | const output = constants.getParallelLimit(); 26 | 27 | assert.equal(output, 4); 28 | }); 29 | 30 | it('below min', function () { 31 | process.env.UV_THREADPOOL_SIZE = '0'; 32 | const output = constants.getParallelLimit(); 33 | 34 | assert.equal(output, 1); 35 | }); 36 | 37 | it('negative number', function () { 38 | process.env.UV_THREADPOOL_SIZE = '-8'; 39 | const output = constants.getParallelLimit(); 40 | 41 | assert.equal(output, 1); 42 | }); 43 | 44 | it('not a number', function () { 45 | process.env.UV_THREADPOOL_SIZE = 'abc'; 46 | const output = constants.getParallelLimit(); 47 | 48 | assert.equal(output, 2); 49 | }); 50 | }); 51 | 52 | describe('stringMaxSize', function () { 53 | it('no env override', function () { 54 | assert.strictEqual(constants.stringMaxSize, 100000); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-oracledb", 3 | "version": "3.0.0", 4 | "description": "Extend capabilities of oracledb with simplified API for quicker development.", 5 | "author": { 6 | "name": "Sagie Gur-Ari", 7 | "email": "sagiegurari@gmail.com" 8 | }, 9 | "license": "Apache-2.0", 10 | "homepage": "http://github.com/sagiegurari/simple-oracledb", 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/sagiegurari/simple-oracledb.git" 14 | }, 15 | "bugs": { 16 | "url": "http://github.com/sagiegurari/simple-oracledb/issues" 17 | }, 18 | "keywords": [ 19 | "oracle", 20 | "oracledb", 21 | "database" 22 | ], 23 | "main": "index.js", 24 | "directories": { 25 | "lib": "lib", 26 | "test": "test/spec" 27 | }, 28 | "scripts": { 29 | "clean": "rm -Rf ./.nyc_output ./coverage", 30 | "format": "js-beautify --config ./.jsbeautifyrc --file ./*.js ./lib/**/*.js ./test/**/*.js", 31 | "lint-js": "eslint ./*.js ./lib/**/*.js ./test/**/*.js", 32 | "lint-css": "stylelint --allow-empty-input ./docs/**/*.css", 33 | "lint": "npm run lint-js && npm run lint-css", 34 | "jstest": "mocha --exit ./test/spec/**/*.js", 35 | "coverage": "nyc --reporter=html --reporter=text --reporter=lcovonly --check-coverage=true mocha --exit ./test/spec/**/*.js", 36 | "docs": "jsdoc2md lib/**/*.js > ./docs/api.md", 37 | "test": "npm run clean && npm run format && npm run lint && npm run docs && npm run coverage", 38 | "postpublish": "git fetch && git pull" 39 | }, 40 | "dependencies": { 41 | "async": "^3", 42 | "debuglog": "^1", 43 | "event-emitter-enhancer": "latest", 44 | "funcs-js": "latest", 45 | "node-later": "latest" 46 | }, 47 | "devDependencies": { 48 | "chai": "^4", 49 | "eslint": "^8", 50 | "js-beautify": "^1", 51 | "jsdoc-to-markdown": "^8", 52 | "mocha": "^10", 53 | "nyc": "^15", 54 | "promiscuous": "^0.7", 55 | "stylelint": "^13", 56 | "stylelint-config-standard": "^22" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/extensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const promiseHelper = require('./promise-helper'); 4 | 5 | /** 6 | * Holds all extensions of the pool/connection objects. 7 | * 8 | * @author Sagie Gur-Ari 9 | * @class Extensions 10 | * @private 11 | */ 12 | function Extensions() { 13 | this.extensions = { 14 | pool: {}, 15 | connection: {} 16 | }; 17 | } 18 | 19 | /** 20 | * Adds an extension to all newly created objects of the requested type.
21 | * An extension, is a function which will be added to any pool or connection instance created after the extension was added.
22 | * Extension functions automatically get promisified unless specified differently in the optional options. 23 | * 24 | * @function 25 | * @memberof! Extensions 26 | * @public 27 | * @param {String} type - Either 'connection' or 'pool' 28 | * @param {String} name - The function name which will be added to the object 29 | * @param {function} extension - The function to be added 30 | * @param {Object} [options] - Any extension options needed 31 | * @param {Object} [options.promise] - Promise options 32 | * @param {Boolean} [options.promise.noPromise=false] - If true, do not promisify function 33 | * @param {Boolean} [options.promise.force=false] - If true, do not check if promise is supported 34 | * @param {Boolean} [options.promise.defaultCallback=false] - If true and no callback provided, generate an empty callback 35 | * @param {Number} [options.promise.callbackMinIndex=0] - The minimum index in the arguments that the callback is found in 36 | * @returns {Boolean} True if added, false if ignored 37 | */ 38 | Extensions.prototype.add = function (type, name, extension, options) { 39 | let added = false; 40 | if (type && ((type === 'pool') || (type === 'connection')) && name && (typeof name === 'string') && extension && (typeof extension === 'function')) { 41 | this.extensions[type][name] = extension; 42 | added = true; 43 | 44 | options = options || {}; 45 | options.promise = options.promise || {}; 46 | 47 | if (!options.promise.noPromise) { 48 | //add promise support 49 | this.extensions[type][name] = promiseHelper.promisify(extension, options.promise); 50 | } 51 | } 52 | 53 | return added; 54 | }; 55 | 56 | /** 57 | * Returns all extensions for the requested typp (pool/connection) 58 | * 59 | * @function 60 | * @memberof! Extensions 61 | * @public 62 | * @param {String} type - Either 'connection' or 'pool' 63 | * @returns {Object} All extensions defined as name:function 64 | */ 65 | Extensions.prototype.get = function (type) { 66 | let output; 67 | if (type && ((type === 'pool') || (type === 'connection'))) { 68 | output = this.extensions[type]; 69 | } 70 | 71 | return output; 72 | }; 73 | 74 | module.exports = new Extensions(); 75 | -------------------------------------------------------------------------------- /lib/resultset-read-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const stream = require('stream'); 5 | const Readable = stream.Readable; 6 | 7 | /** 8 | * A node.js read stream for resultsets. 9 | * 10 | * @author Sagie Gur-Ari 11 | * @class ResultSetReadStream 12 | * @public 13 | */ 14 | function ResultSetReadStream() { 15 | const self = this; 16 | 17 | Readable.call(self, { 18 | objectMode: true 19 | }); 20 | 21 | Object.defineProperty(self, 'nextRow', { 22 | /** 23 | * Sets the nextRow value. 24 | * 25 | * @function 26 | * @memberof! ResultSetReadStream 27 | * @alias ResultSetReadStream.nextRow.set 28 | * @private 29 | * @param {function} nextRow - The next row callback function 30 | */ 31 | set(nextRow) { 32 | self.next = nextRow; 33 | 34 | if (self.inRead) { 35 | /*jslint nomen: true */ 36 | /*eslint-disable no-underscore-dangle*/ 37 | //jscs:disable disallowDanglingUnderscores 38 | self._read(); 39 | //jscs:enable disallowDanglingUnderscores 40 | /*eslint-enable no-underscore-dangle*/ 41 | /*jslint nomen: false */ 42 | } 43 | } 44 | }); 45 | } 46 | 47 | util.inherits(ResultSetReadStream, Readable); 48 | 49 | /*jslint nomen: true */ 50 | /*eslint-disable no-underscore-dangle*/ 51 | //jscs:disable disallowDanglingUnderscores 52 | /** 53 | * The stream _read implementation which fetches the next row from the resultset. 54 | * 55 | * @function 56 | * @memberof! ResultSetReadStream 57 | * @private 58 | */ 59 | ResultSetReadStream.prototype._read = function () { 60 | const self = this; 61 | 62 | self.inRead = false; 63 | 64 | if (self.metaData) { 65 | self.emit('metadata', self.metaData); 66 | delete self.metaData; 67 | } 68 | 69 | if (self.next) { 70 | self.next(function onNextRowRead(error, data) { 71 | /*istanbul ignore else*/ 72 | if (!self.closed) { 73 | if (error) { 74 | self.closed = true; 75 | self.emit('error', error); 76 | } else if (data) { 77 | self.push(data); 78 | } else { 79 | self.closed = true; 80 | self.push(null); 81 | } 82 | } 83 | }); 84 | } else { 85 | self.inRead = true; 86 | } 87 | }; 88 | //jscs:enable disallowDanglingUnderscores 89 | /*eslint-enable no-underscore-dangle*/ 90 | /*jslint nomen: false */ 91 | 92 | /** 93 | * Closes the stream and prevent any more data events from being invoked.
94 | * It will also free the connection to enable using it to invoke more operations. 95 | * 96 | * @function 97 | * @memberof! ResultSetReadStream 98 | * @public 99 | */ 100 | ResultSetReadStream.prototype.close = function () { 101 | const self = this; 102 | 103 | /*istanbul ignore else*/ 104 | if (!self.closed) { 105 | self.closed = true; 106 | 107 | process.nextTick(function emitEnd() { 108 | self.push(null); 109 | }); 110 | } 111 | }; 112 | 113 | module.exports = ResultSetReadStream; 114 | -------------------------------------------------------------------------------- /lib/record-writer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asyncLib = require('async'); 4 | const stream = require('./stream'); 5 | 6 | /*jslint debug: true */ 7 | /** 8 | * Record writing helper functions. 9 | * 10 | * @author Sagie Gur-Ari 11 | * @class RecordWriter 12 | * @private 13 | */ 14 | function RecordWriter() { 15 | //should not be called 16 | } 17 | /*jslint debug: false */ 18 | 19 | /** 20 | * Writes all LOBs columns via out bindings of the INSERT/UPDATE command. 21 | * 22 | * @function 23 | * @memberof! RecordWriter 24 | * @public 25 | * @param {Object} outBindings - The output bindings of the INSERT/UPDATE result 26 | * @param {Object} lobData - The LOB data holder (key column name, value column value) 27 | * @param {AsyncCallback} callback - called when the row is fully written to or in case of an error 28 | */ 29 | RecordWriter.prototype.write = function (outBindings, lobData, callback) { 30 | const functions = []; 31 | const bindconstiableNames = Object.keys(lobData); 32 | 33 | const createWriteFieldFunction = function (lobStream, value) { 34 | return function writeField(asyncCallback) { 35 | stream.write(lobStream, value, asyncCallback); 36 | }; 37 | }; 38 | 39 | for (let index = 0; index < bindconstiableNames.length; index++) { 40 | const bindconstiableName = bindconstiableNames[index]; 41 | 42 | const value = lobData[bindconstiableName]; 43 | 44 | if (value && bindconstiableName && outBindings[bindconstiableName] && (outBindings[bindconstiableName].length === 1)) { 45 | const lobStream = outBindings[bindconstiableName][0]; 46 | 47 | functions.push(createWriteFieldFunction(lobStream, value)); 48 | } 49 | } 50 | 51 | asyncLib.series(functions, callback); 52 | }; 53 | 54 | /** 55 | * Writes all LOBs columns via out bindings of the INSERT/UPDATE command with support of multiple rows. 56 | * 57 | * @function 58 | * @memberof! RecordWriter 59 | * @public 60 | * @param {Object} outBindings - The output bindings of the INSERT/UPDATE result 61 | * @param {Object} lobData - The LOB data holder (key column name, value column value) 62 | * @param {AsyncCallback} callback - called when the row is fully written to or in case of an error 63 | */ 64 | RecordWriter.prototype.writeMultiple = function (outBindings, lobData, callback) { 65 | const functions = []; 66 | const bindconstiableNames = Object.keys(lobData); 67 | 68 | const createWriteFieldFunction = function (lobStream, value) { 69 | return function writeField(asyncCallback) { 70 | stream.write(lobStream, value, asyncCallback); 71 | }; 72 | }; 73 | 74 | for (let bindIndex = 0; bindIndex < bindconstiableNames.length; bindIndex++) { 75 | const bindconstiableName = bindconstiableNames[bindIndex]; 76 | 77 | const value = lobData[bindconstiableName]; 78 | 79 | if (value && bindconstiableName && outBindings[bindconstiableName] && outBindings[bindconstiableName].length) { 80 | const lobStreams = outBindings[bindconstiableName]; 81 | 82 | for (let streamIndex = 0; streamIndex < lobStreams.length; streamIndex++) { 83 | const lobStream = lobStreams[streamIndex]; 84 | 85 | functions.push(createWriteFieldFunction(lobStream, value)); 86 | } 87 | } 88 | } 89 | 90 | asyncLib.series(functions, callback); 91 | }; 92 | 93 | module.exports = new RecordWriter(); 94 | -------------------------------------------------------------------------------- /lib/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const funcs = require('funcs-js'); 4 | 5 | /*jslint debug: true */ 6 | /** 7 | * Stream helper functions. 8 | * 9 | * @author Sagie Gur-Ari 10 | * @class Stream 11 | * @private 12 | */ 13 | function Stream() { 14 | //should not be called 15 | } 16 | /*jslint debug: false */ 17 | 18 | /** 19 | * Reads all data from the provided stream. 20 | * 21 | * @function 22 | * @memberof! Stream 23 | * @public 24 | * @param {Object} readableStream - The readable stream 25 | * @param {Boolean} binary - True for binary stream, else character stream 26 | * @param {AsyncCallback} callback - called when the stream is fully read. 27 | */ 28 | Stream.prototype.read = function (readableStream, binary, callback) { 29 | if (!binary) { 30 | readableStream.setEncoding('utf8'); 31 | } 32 | 33 | callback = funcs.once(callback, { 34 | callbackStyle: true 35 | }); 36 | 37 | let data = []; 38 | const listeners = []; 39 | const cleanup = function () { 40 | let unlisten; 41 | while (listeners.length) { 42 | unlisten = listeners.pop(); 43 | unlisten(); 44 | } 45 | }; 46 | const onData = function (partialData) { 47 | if (partialData) { 48 | data.push(partialData); 49 | } 50 | }; 51 | const onEnd = function () { 52 | cleanup(); 53 | 54 | if (binary) { 55 | data = Buffer.concat(data); 56 | } else { 57 | data = data.join(''); 58 | } 59 | 60 | callback(null, data); 61 | }; 62 | const onError = function (error) { 63 | cleanup(); 64 | 65 | callback(error); 66 | }; 67 | 68 | readableStream.on('data', onData); 69 | listeners.push(function removeDataListener() { 70 | readableStream.removeListener('data', onData); 71 | }); 72 | 73 | readableStream.once('end', onEnd); 74 | listeners.push(function removeEndListener() { 75 | readableStream.removeListener('end', onEnd); 76 | }); 77 | 78 | readableStream.once('error', onError); 79 | listeners.push(function removeErrorListener() { 80 | readableStream.removeListener('error', onError); 81 | }); 82 | }; 83 | 84 | /** 85 | * Writes the provided data to the stream. 86 | * 87 | * @function 88 | * @memberof! Stream 89 | * @public 90 | * @param {Object} writableStream - The writable stream 91 | * @param {Buffer|String} data - The text of binary data to write 92 | * @param {AsyncCallback} callback - called when the data is fully written to the provided stream 93 | */ 94 | Stream.prototype.write = function (writableStream, data, callback) { 95 | if (writableStream && data) { 96 | let errorDetected = false; 97 | const onError = function (error) { 98 | errorDetected = true; 99 | 100 | callback(error); 101 | }; 102 | 103 | writableStream.once('error', onError); 104 | 105 | const onWrite = function () { 106 | writableStream.removeListener('error', onError); 107 | 108 | if (!errorDetected) { 109 | callback(); 110 | } 111 | }; 112 | 113 | if (typeof data === 'string') { 114 | writableStream.end(data, 'utf8', onWrite); 115 | } else { //Buffer 116 | writableStream.end(data, onWrite); 117 | } 118 | } else { 119 | callback(); 120 | } 121 | }; 122 | 123 | module.exports = new Stream(); 124 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns the parallel limit used by this library when running async operations in parallel. 5 | * 6 | * @function 7 | * @memberof! Constants 8 | * @private 9 | * @returns {Number} The parallel limit 10 | */ 11 | function calculateParallelLimit() { 12 | let threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4; 13 | if (typeof threadPoolSize === 'string') { 14 | threadPoolSize = parseInt(threadPoolSize, 10); 15 | 16 | if (isNaN(threadPoolSize)) { 17 | threadPoolSize = 4; 18 | } 19 | } 20 | 21 | const floor = Math.floor(threadPoolSize / 2) || 1; 22 | 23 | return Math.max(floor, 1); 24 | } 25 | 26 | /** 27 | * Library constants. 28 | * 29 | * @author Sagie Gur-Ari 30 | * @namespace Constants 31 | * @private 32 | */ 33 | module.exports = { 34 | /** 35 | * Holds constious internal feature flags. 36 | * 37 | * @function 38 | * @memberof! Constants 39 | * @private 40 | * @returns {Object} Feature flags 41 | */ 42 | features: { 43 | /** 44 | * True to enable executeMany support for batch operations. 45 | * 46 | * @function 47 | * @memberof! Constants.features 48 | * @private 49 | * @returns {Boolean} True to enable executeMany support for batch operations 50 | */ 51 | executeManySupport: true 52 | }, 53 | /** 54 | * Returns the parallel limit used by this library when running async operations in parallel. 55 | * 56 | * @function 57 | * @memberof! Constants 58 | * @private 59 | * @returns {Number} The parallel limit 60 | */ 61 | getParallelLimit: calculateParallelLimit, 62 | /** 63 | * Holds the parallel limit calculated based on the current node.js thread pool size. 64 | * 65 | * @member {Number} 66 | * @alias Constants.parallelLimit 67 | * @memberof! Constants 68 | * @public 69 | */ 70 | parallelLimit: calculateParallelLimit(), 71 | /** 72 | * Holds the string max size for bind definitions. 73 | * 74 | * @member {Number} 75 | * @alias Constants.stringMaxSize 76 | * @memberof! Constants 77 | * @public 78 | */ 79 | stringMaxSize: parseInt(process.env.SIMPLE_ORACLEDB_MAX_STRING_SIZE || 100000, 10), 80 | /** 81 | * Holds the CLOB type. 82 | * 83 | * @member {Number} 84 | * @alias Constants.clobType 85 | * @memberof! Constants 86 | * @public 87 | */ 88 | clobType: 2017, 89 | /** 90 | * Holds the BLOB type. 91 | * 92 | * @member {Number} 93 | * @alias Constants.blobType 94 | * @memberof! Constants 95 | * @public 96 | */ 97 | blobType: 2019, 98 | /** 99 | * Holds the date type. 100 | * 101 | * @member {Number} 102 | * @alias Constants.dateType 103 | * @memberof! Constants 104 | * @public 105 | */ 106 | dateType: 2011, 107 | /** 108 | * Holds the number type. 109 | * 110 | * @member {Number} 111 | * @alias Constants.numberType 112 | * @memberof! Constants 113 | * @public 114 | */ 115 | numberType: 2010, 116 | /** 117 | * Holds the string type. 118 | * 119 | * @member {Number} 120 | * @alias Constants.stringType 121 | * @memberof! Constants 122 | * @public 123 | */ 124 | stringType: 2001, 125 | /** 126 | * Holds the object type. 127 | * 128 | * @member {Number} 129 | * @alias Constants.objectType 130 | * @memberof! Constants 131 | * @public 132 | */ 133 | objectType: 4002, 134 | /** 135 | * Holds the BIND_OUT value. 136 | * 137 | * @member {Number} 138 | * @alias Constants.bindOut 139 | * @memberof! Constants 140 | * @public 141 | */ 142 | bindOut: 3003 143 | }; 144 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | | Date | Version | Description | 2 | | ----------- | ------- | ----------- | 3 | | 2021-10-22 | v3.0.0 | Add null binding support, update constants with latest changes and do not break by default after run is done | 4 | | 2020-05-13 | v2.0.0 | Migrate to github actions and upgrade minimal node version | 5 | | 2019-05-25 | v1.4.2 | Maintenance | 6 | | 2019-01-25 | v1.4.0 | useExecuteMany=true by default | 7 | | 2018-09-23 | v1.3.0 | Added executeMany support for the batch APIs | 8 | | 2018-04-14 | v1.2.2 | Performance improvement for pooled connection fetching (#23) | 9 | | 2017-01-20 | v1.1.57 | connection.run, connection.transaction and oracledb.run actions can now return a promise instead of using a callback | 10 | | 2017-01-14 | v1.1.56 | pool.run actions now can return a promise instead of using a callback | 11 | | 2016-12-28 | v1.1.50 | Added pool.parallelQuery which enables parallel queries using multiple connections | 12 | | 2016-11-15 | v1.1.41 | Added connection.executeFile to read SQL statement from file and execute it | 13 | | 2016-10-07 | v1.1.26 | Added oracledb.run | 14 | | 2016-08-15 | v1.1.2 | Added 'metadata' event for connection.query with streaming | 15 | | 2016-08-10 | v1.1.0 | Breaking change connection.run and connection.transaction default is now sequence instead of parallel | 16 | | 2016-08-09 | v1.0.2 | Added connection.run | 17 | | 2016-08-07 | v0.1.98 | NODE_DEBUG=simple-oracledb will now also log all SQL statements and bind params for the connection.execute function | 18 | | 2016-08-05 | v0.1.96 | Extensions are now automatically promisified | 19 | | 2016-08-05 | v0.1.95 | Added promise support for all library APIs | 20 | | 2016-07-26 | v0.1.84 | Add integration test via docker | 21 | | 2016-07-24 | v0.1.83 | Add support for node-oracledb promise | 22 | | 2016-07-17 | v0.1.80 | Add support for node-oracledb promise | 23 | | 2016-07-14 | v0.1.79 | Fixed possible max stack size error | 24 | | 2016-05-01 | v0.1.57 | Added the new monitor (SimpleOracleDB.diagnosticInfo and SimpleOracleDB.enableDiagnosticInfo) and SimpleOracleDB is now an event emitter | 25 | | 2016-03-31 | v0.1.51 | Added new stream.close function to stop streaming data and free the connection for more operations | 26 | | 2016-03-03 | v0.1.40 | Connection and Pool are now event emitters | 27 | | 2016-03-02 | v0.1.38 | Added new force option for connection.release/close | 28 | | 2016-02-28 | v0.1.37 | Added SimpleOracleDB.addExtension which allows to further extend oracledb | 29 | | 2016-02-22 | v0.1.32 | Added new pool.run operation | 30 | | 2016-02-16 | v0.1.29 | new optional options.returningInfo to insert/update/batch to enable to modify the returning/into clause when using LOBs | 31 | | 2016-02-12 | v0.1.26 | Added sequence option for connection.transaction and added pool.close=pool.terminate, connection.close=connection.release aliases | 32 | | 2016-02-10 | v0.1.23 | Adding debug logs via NODE_DEBUG=simple-oracledb | 33 | | 2016-02-09 | v0.1.20 | connection.release now supports retry options | 34 | | 2016-01-22 | v0.1.18 | Fixed missing call to resultset.close after done reading | 35 | | 2016-01-12 | v0.1.8 | Avoid issues with oracledb stream option which is based on this library | 36 | | 2016-01-07 | v0.1.7 | connection.query with streamResults=true returns a readable stream | 37 | | 2015-12-30 | v0.1.6 | connection.transaction disables commit/rollback while running | 38 | | 2015-12-29 | v0.1.4 | Added connection.transaction | 39 | | 2015-12-29 | v0.1.3 | Added connection.batchUpdate | 40 | | 2015-12-22 | v0.1.2 | Added streaming of query results with new option streamResults=true | 41 | | 2015-12-21 | v0.1.1 | Rename streamResults to splitResults | 42 | | 2015-12-21 | v0.0.35 | New bulkRowsAmount option to manage query resultset behaviour | 43 | | 2015-12-21 | v0.0.34 | Added splitting of query results into bulks with new option splitResults=true | 44 | | 2015-12-08 | v0.0.24 | Added pool.getConnection connection validation via running SQL test command | 45 | | 2015-11-17 | v0.0.17 | Added pool.getConnection automatic retry | 46 | | 2015-11-15 | v0.0.16 | Added connection.batchInsert and connection.rollback | 47 | | 2015-10-20 | v0.0.10 | Added connection.queryJSON | 48 | | 2015-10-19 | v0.0.9 | autoCommit support when doing INSERT/UPDATE with LOBs | 49 | | 2015-10-19 | v0.0.7 | Added pool.terminate | 50 | | 2015-10-18 | v0.0.5 | Added connection.update | 51 | | 2015-10-18 | v0.0.4 | Added connection.insert | 52 | | 2015-10-15 | v0.0.1 | Initial release. | 53 | -------------------------------------------------------------------------------- /test/helpers/test-oracledb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const EventEmitter = require('events').EventEmitter; 6 | const Connection = require('../../lib/connection'); 7 | 8 | /*jslint debug: true */ 9 | function TestConnection() {} 10 | 11 | function TestPool() {} 12 | /*jslint debug: false */ 13 | 14 | TestConnection.prototype.execute = function () { 15 | const callback = arguments[arguments.length - 1]; 16 | 17 | if (this.throwError) { 18 | callback(new Error()); 19 | } else { 20 | callback(null, {}); 21 | } 22 | }; 23 | 24 | TestConnection.prototype.break = function (callback) { 25 | callback(); 26 | }; 27 | 28 | TestConnection.prototype.rollback = function (callback) { 29 | callback(); 30 | }; 31 | 32 | TestConnection.prototype.release = function () { 33 | const callback = arguments[arguments.length - 1]; 34 | callback(); 35 | }; 36 | 37 | TestPool.prototype.modifyTestConnection = function (connection) { 38 | return connection; 39 | }; 40 | 41 | TestPool.prototype.getConnection = function (callback) { 42 | if (this.throwError) { 43 | callback(new Error()); 44 | } else { 45 | let connection = new TestConnection(); 46 | 47 | if (this.pingSupport) { 48 | if (this.pingError) { 49 | connection.ping = function (cb) { 50 | setTimeout(function () { 51 | cb(new Error()); 52 | }, 0); 53 | }; 54 | } else { 55 | connection.ping = function (cb) { 56 | setTimeout(cb, 0); 57 | }; 58 | } 59 | } 60 | 61 | if (this.extendConnection) { 62 | Connection.extend(connection); 63 | } 64 | 65 | connection = this.modifyTestConnection(connection); 66 | 67 | callback(null, connection); 68 | } 69 | }; 70 | 71 | TestPool.prototype.terminate = function () { 72 | const callback = arguments[arguments.length - 1]; 73 | callback(); 74 | }; 75 | 76 | module.exports = { 77 | create() { 78 | return { 79 | createPool(invalid, callback) { 80 | if (callback === undefined) { 81 | callback = invalid; 82 | invalid = false; 83 | } 84 | 85 | if (invalid) { 86 | callback(new Error()); 87 | } else { 88 | callback(null, new TestPool()); 89 | } 90 | }, 91 | getConnection(connAttrs, callback) { 92 | if (this.throwError) { 93 | callback(new Error()); 94 | } else if (!arguments.length) { 95 | callback(new Error()); 96 | } else { 97 | assert.isObject(connAttrs); 98 | 99 | const connection = new TestConnection(); 100 | 101 | if (this.extendConnection) { 102 | Connection.extend(connection); 103 | } 104 | 105 | callback(null, connection); 106 | } 107 | }, 108 | execute: TestConnection.prototype.execute 109 | }; 110 | }, 111 | createPool() { 112 | return new TestPool(); 113 | }, 114 | createCLOB() { 115 | const testStream = new EventEmitter(); 116 | testStream.type = require('../../lib/constants').clobType; 117 | testStream.setEncoding = function (encoding) { 118 | assert.equal(encoding, 'utf8'); 119 | }; 120 | testStream.end = function (data, encoding, callback) { 121 | assert.deepEqual(data, testStream.testData); 122 | assert.equal(encoding, 'utf8'); 123 | assert.isFunction(callback); 124 | 125 | this.emit('end'); 126 | 127 | callback(); 128 | }; 129 | 130 | return testStream; 131 | }, 132 | createBLOB() { 133 | const testStream = new EventEmitter(); 134 | testStream.type = require('../../lib/constants').blobType; 135 | testStream.end = function (data, callback) { 136 | assert.deepEqual(data.toJSON(), (new Buffer(testStream.testData)).toJSON()); 137 | assert.isFunction(callback); 138 | 139 | this.emit('end'); 140 | 141 | callback(); 142 | }; 143 | 144 | return testStream; 145 | } 146 | }; 147 | -------------------------------------------------------------------------------- /lib/record-reader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asyncLib = require('async'); 4 | const stream = require('./stream'); 5 | const constants = require('./constants'); 6 | 7 | /*jslint debug: true */ 8 | /** 9 | * Record reading helper functions. 10 | * 11 | * @author Sagie Gur-Ari 12 | * @class RecordReader 13 | * @private 14 | */ 15 | function RecordReader() { 16 | //should not be called 17 | } 18 | /*jslint debug: false */ 19 | 20 | /** 21 | * Returns the value of the field from the row. 22 | * 23 | * @function 24 | * @memberof! RecordReader 25 | * @private 26 | * @param {Object} field - The field value 27 | * @param {Object} info - Internal info holder 28 | * @param {AsyncCallback} callback - called when the value is fully read or in case of an error 29 | */ 30 | RecordReader.prototype.getValue = function (field, info, callback) { 31 | if ((field === null) || (field === undefined)) { 32 | callback(null, field); 33 | } else { 34 | const type = typeof field; 35 | 36 | switch (type) { 37 | case 'number': 38 | case 'string': 39 | case 'boolean': 40 | callback(null, field); 41 | break; 42 | default: 43 | if (field instanceof Date) { 44 | callback(null, field); 45 | } else if (field.type !== undefined) { //LOB 46 | const binary = (field.type === constants.blobType); 47 | info.lobFound = true; 48 | stream.read(field, binary, callback); 49 | } else { 50 | callback(new Error('Unsupported type provided: ' + (typeof field) + ' value: ' + field)); 51 | } 52 | } 53 | } 54 | }; 55 | 56 | /** 57 | * Returns a handler function. 58 | * 59 | * @function 60 | * @memberof! RecordReader 61 | * @private 62 | * @param {Object} jsObject - The result object holder to populate 63 | * @param {String} columnName - The field name 64 | * @param {Object} value - The field value 65 | * @param {Object} info - Internal info holder 66 | * @returns {function} The handler function 67 | */ 68 | RecordReader.prototype.createFieldHandler = function (jsObject, columnName, value, info) { 69 | const self = this; 70 | 71 | return function handleField(asyncCallback) { 72 | self.getValue(value, info, function onValue(error, jsValue) { 73 | if (error) { 74 | asyncCallback(error); 75 | } else { 76 | jsObject[columnName] = jsValue; 77 | asyncCallback(null, jsObject); 78 | } 79 | }); 80 | }; 81 | }; 82 | 83 | /** 84 | * Reads all data from the provided oracle record. 85 | * 86 | * @function 87 | * @memberof! RecordReader 88 | * @public 89 | * @param {Array} columnNames - Array of strings holding the column names of the results 90 | * @param {Object|Array} row - The oracle row object 91 | * @param {Object} info - Internal info holder 92 | * @param {AsyncCallback} callback - called when the row is fully read or in case of an error 93 | */ 94 | RecordReader.prototype.read = function (columnNames, row, info, callback) { 95 | const self = this; 96 | 97 | const jsObject = {}; 98 | const functions = []; 99 | if (Array.isArray(row)) { 100 | for (let index = 0; index < columnNames.length; index++) { 101 | const columnName = columnNames[index]; 102 | 103 | const value = row[index]; 104 | 105 | functions.push(self.createFieldHandler(jsObject, columnName.name, value, info)); 106 | } 107 | } else { 108 | for (let index = 0; index < columnNames.length; index++) { 109 | const columnName = columnNames[index]; 110 | 111 | const value = row[columnName.name]; 112 | 113 | functions.push(self.createFieldHandler(jsObject, columnName.name, value, info)); 114 | } 115 | } 116 | 117 | asyncLib.parallelLimit(functions, constants.parallelLimit, function onAyncDone(error) { 118 | callback(error, jsObject); 119 | }); 120 | }; 121 | 122 | /** 123 | * Read a JSON record. 124 | * 125 | * @function 126 | * @memberof! RecordReader 127 | * @public 128 | * @param {Object} jsRow - The JS object holding the row data. 129 | * @param {String} column - The column name 130 | * @returns {Object} The JSON object 131 | */ 132 | RecordReader.prototype.readJSON = function (jsRow, column) { 133 | let json; 134 | if (jsRow && column) { 135 | const jsonStr = jsRow[column]; 136 | 137 | if (jsonStr) { 138 | json = JSON.parse(jsonStr); 139 | } else { 140 | json = {}; 141 | } 142 | } 143 | 144 | return json; 145 | }; 146 | 147 | module.exports = new RecordReader(); 148 | -------------------------------------------------------------------------------- /test/helpers/integration-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asyncLib = require('async'); 4 | const chai = require('chai'); 5 | const assert = chai.assert; 6 | 7 | module.exports = function (setup) { 8 | let integrated = true; 9 | const connAttrs = { 10 | user: process.env.TEST_ORACLE_USER, 11 | password: process.env.TEST_ORACLE_PASSWORD, 12 | connectString: process.env.TEST_ORACLE_CONNECTION_STRING 13 | }; 14 | 15 | if ((!connAttrs.user) || (!connAttrs.password) || (!connAttrs.connectString)) { 16 | integrated = false; 17 | } 18 | 19 | if (!integrated) { 20 | setup(); 21 | } else { 22 | const oracledb = require('oracledb'); 23 | 24 | oracledb.autoCommit = true; 25 | 26 | const simpleOracleDB = require('../../'); 27 | simpleOracleDB.extend(oracledb); 28 | 29 | const end = function (done, connection) { 30 | if (connection) { 31 | connection.release(); 32 | } 33 | 34 | setTimeout(done, 10); 35 | }; 36 | 37 | let testPool; 38 | const initDB = function (tableName, data, cb) { 39 | oracledb.getConnection(connAttrs, function (connErr, connection) { 40 | data = data || []; 41 | 42 | if (connErr) { 43 | console.error(connErr); 44 | setTimeout(function () { 45 | assert.fail('UNABLE TO OPEN DB CONNECTION.'); 46 | }, 100); 47 | } else { 48 | connection.execute('DROP TABLE ' + tableName, [], function () { 49 | connection.execute('CREATE TABLE ' + tableName + ' (COL1 VARCHAR2(250) PRIMARY KEY, COL2 NUMBER, COL3 NUMBER, COL4 VARCHAR2(250), LOB1 CLOB, LOB2 BLOB)', [], function (createError) { 50 | if (createError) { 51 | console.error(createError); 52 | assert.fail('UNABLE TO CREATE DB TABLE: ' + tableName); 53 | } else { 54 | const func = []; 55 | data.forEach(function (rowData) { 56 | func.push(function (asyncCB) { 57 | if (!rowData.COL4) { 58 | rowData.COL4 = undefined; 59 | } 60 | if (!rowData.LOB1) { 61 | rowData.LOB1 = undefined; 62 | } 63 | if (!rowData.LOB2) { 64 | rowData.LOB2 = undefined; 65 | } 66 | 67 | connection.execute('INSERT INTO ' + tableName + ' (COL1, COL2, COL3, COL4, LOB1, LOB2) VALUES (:COL1, :COL2, :COL3, :COL4, :LOB1, :LOB2)', rowData, function (insertErr) { 68 | if (insertErr) { 69 | asyncCB(insertErr); 70 | } else { 71 | asyncCB(null, rowData); 72 | } 73 | }); 74 | }); 75 | }); 76 | 77 | asyncLib.series(func, function (asynErr) { 78 | connection.release(function (rerr) { 79 | if (asynErr) { 80 | console.error(data, asynErr); 81 | assert.fail('UNABLE TO CREATE DB POOL.'); 82 | } else if (rerr) { 83 | console.error('release error: ', rerr); 84 | } else if (testPool) { 85 | cb(testPool); 86 | } else { 87 | oracledb.createPool(connAttrs, function (perr, newPool) { 88 | if (perr) { 89 | console.error(perr); 90 | assert.fail('UNABLE TO CREATE DB POOL.'); 91 | } else { 92 | testPool = newPool; 93 | cb(testPool); 94 | } 95 | }); 96 | } 97 | }); 98 | }); 99 | } 100 | }); 101 | }); 102 | } 103 | }); 104 | }; 105 | 106 | setup(oracledb, connAttrs, initDB, end); 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /test/spec/extensions-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const extensions = require('../../lib/extensions'); 6 | 7 | describe('Extensions Tests', function () { 8 | const noop = function () { 9 | return undefined; 10 | }; 11 | const noop2 = function () { 12 | return undefined; 13 | }; 14 | 15 | describe('add', function () { 16 | it('undefined', function () { 17 | const output = extensions.add(); 18 | assert.isFalse(output); 19 | }); 20 | 21 | it('missing type', function () { 22 | const output = extensions.add(undefined, 'myfunc', noop); 23 | assert.isFalse(output); 24 | }); 25 | 26 | it('type not string', function () { 27 | const output = extensions.add(123, 'myfunc', noop); 28 | assert.isFalse(output); 29 | }); 30 | 31 | it('invalid type', function () { 32 | const output = extensions.add('test', 'myfunc', noop); 33 | assert.isFalse(output); 34 | }); 35 | 36 | it('missing name', function () { 37 | const output = extensions.add('connection', undefined, noop); 38 | assert.isFalse(output); 39 | }); 40 | 41 | it('name not string', function () { 42 | const output = extensions.add('connection', 123, noop); 43 | assert.isFalse(output); 44 | }); 45 | 46 | it('missing function', function () { 47 | const output = extensions.add('connection', 'myfunc'); 48 | assert.isFalse(output); 49 | }); 50 | 51 | it('invalid function type', function () { 52 | const output = extensions.add('connection', 'myfunc', 123); 53 | assert.isFalse(output); 54 | }); 55 | 56 | it('valid', function () { 57 | let output = extensions.add('connection', 'myfunc1', noop); 58 | assert.isTrue(output); 59 | 60 | output = extensions.add('connection', 'myfunc2', noop2); 61 | assert.isTrue(output); 62 | 63 | output = extensions.add('pool', 'myfunc1', noop); 64 | assert.isTrue(output); 65 | 66 | assert.isFunction(extensions.extensions.connection.myfunc1); 67 | assert.isFunction(extensions.extensions.connection.myfunc2); 68 | 69 | output = extensions.add('connection', 'myfunc2', noop); 70 | assert.isTrue(output); 71 | 72 | assert.isFunction(extensions.extensions.connection.myfunc1); 73 | assert.isFunction(extensions.extensions.connection.myfunc2); 74 | 75 | assert.isFunction(extensions.get('connection').myfunc1); 76 | assert.isFunction(extensions.get('connection').myfunc2); 77 | 78 | assert.isFunction(extensions.get('pool').myfunc1); 79 | }); 80 | 81 | it('valid, no promise', function () { 82 | let output = extensions.add('connection', 'myfunc1', noop, { 83 | promise: { 84 | noPromise: true 85 | } 86 | }); 87 | assert.isTrue(output); 88 | 89 | output = extensions.add('connection', 'myfunc2', noop2, { 90 | promise: { 91 | noPromise: true 92 | } 93 | }); 94 | assert.isTrue(output); 95 | 96 | output = extensions.add('pool', 'myfunc1', noop, { 97 | promise: { 98 | noPromise: true 99 | } 100 | }); 101 | assert.isTrue(output); 102 | 103 | assert.deepEqual(extensions.extensions.connection, { 104 | myfunc1: noop, 105 | myfunc2: noop2 106 | }); 107 | 108 | output = extensions.add('connection', 'myfunc2', noop, { 109 | promise: { 110 | noPromise: true 111 | } 112 | }); 113 | assert.isTrue(output); 114 | 115 | assert.deepEqual(extensions.extensions.connection, { 116 | myfunc1: noop, 117 | myfunc2: noop 118 | }); 119 | 120 | assert.deepEqual(extensions.get('connection'), { 121 | myfunc1: noop, 122 | myfunc2: noop 123 | }); 124 | 125 | assert.deepEqual(extensions.get('pool'), { 126 | myfunc1: noop 127 | }); 128 | }); 129 | }); 130 | 131 | describe('get', function () { 132 | it('undefined', function () { 133 | const output = extensions.get(); 134 | assert.isUndefined(output); 135 | }); 136 | 137 | it('null', function () { 138 | const output = extensions.get(null); 139 | assert.isUndefined(output); 140 | }); 141 | 142 | it('empty', function () { 143 | extensions.extensions.connection = {}; 144 | const output = extensions.get('connection'); 145 | assert.deepEqual(output, {}); 146 | }); 147 | 148 | it('functions exist', function () { 149 | extensions.extensions.connection = { 150 | test: noop, 151 | test2: noop2 152 | }; 153 | const output = extensions.get('connection'); 154 | assert.deepEqual(output, { 155 | test: noop, 156 | test2: noop2 157 | }); 158 | }); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /lib/rows-reader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asyncLib = require('async'); 4 | const later = require('node-later'); 5 | const recordReader = require('./record-reader'); 6 | const constants = require('./constants'); 7 | 8 | /** 9 | * Rows array reading helper functions. 10 | * 11 | * @author Sagie Gur-Ari 12 | * @class RowsReader 13 | * @private 14 | */ 15 | function RowsReader() { 16 | this.deferCallback = later(); 17 | this.safeIODeferCallback = later(true); 18 | } 19 | 20 | /** 21 | * Reads all data from the provided oracle records array. 22 | * 23 | * @function 24 | * @memberof! RowsReader 25 | * @private 26 | * @param {Array} columnNames - Array of strings holding the column names of the results 27 | * @param {Object} [options] - Options holder 28 | * @param {Number} [options.flattenStackEveryRows=Math.max(1, Math.floor(100 / columnNames.length))] - The amount of rows after which the JS stack is flattened, low number can result in performance impact, high number can result in stack overflow error 29 | * @returns {Number} The rows count 30 | */ 31 | RowsReader.prototype.getFlattenRowsCount = function (columnNames, options) { 32 | return options.flattenStackEveryRows || Math.max(1, Math.floor(100 / columnNames.length)); 33 | }; 34 | 35 | /** 36 | * Reads all data from the provided oracle records array. 37 | * 38 | * @function 39 | * @memberof! RowsReader 40 | * @public 41 | * @param {Array} columnNames - Array of strings holding the column names of the results 42 | * @param {Array} row - The row to read 43 | * @param {Number} index - The row index 44 | * @param {Array} jsRows - The rows output holder 45 | * @param {Boolean} [flattenStack=false] - True to flatten the stack to prevent stack overflow issues 46 | * @returns {function} The handler function 47 | */ 48 | RowsReader.prototype.createRecordHandler = function (columnNames, row, index, jsRows, flattenStack) { 49 | const self = this; 50 | 51 | return function callRecordReader(asyncCallback) { 52 | const info = {}; 53 | 54 | recordReader.read(columnNames, row, info, function onRowRead(error, jsObject) { 55 | let deferFunction = self.deferCallback; 56 | if (info.lobFound) { 57 | flattenStack = true; 58 | deferFunction = self.safeIODeferCallback; 59 | } 60 | 61 | //ensure async to prevent max stack size error 62 | if (flattenStack) { 63 | const orgAsyncCallback = asyncCallback; 64 | 65 | /*eslint-disable func-name-matching*/ 66 | asyncCallback = function onCallback(rowError, data) { 67 | deferFunction(function invokeCallback() { 68 | orgAsyncCallback(rowError, data); 69 | }); 70 | }; 71 | /*eslint-enable func-name-matching*/ 72 | } 73 | 74 | if (error) { 75 | asyncCallback(error); 76 | } else { 77 | jsRows[index] = jsObject; 78 | asyncCallback(null, jsRows); 79 | } 80 | }); 81 | }; 82 | }; 83 | 84 | /** 85 | * Reads all data from the provided oracle records array. 86 | * 87 | * @function 88 | * @memberof! RowsReader 89 | * @public 90 | * @param {Array} columnNames - Array of strings holding the column names of the results 91 | * @param {Array} rows - The oracle rows array 92 | * @param {Object} [options] - Options holder 93 | * @param {Number} [options.flattenStackEveryRows] - The amount of rows after which the JS stack is flattened, low number can result in performance impact, high number can result in stack overflow error 94 | * @param {AsyncCallback} callback - called when all rows are fully read or in case of an error 95 | */ 96 | RowsReader.prototype.read = function (columnNames, rows, options, callback) { 97 | const jsRows = []; 98 | 99 | if ((!callback) && (typeof options === 'function')) { 100 | callback = options; 101 | options = null; 102 | } 103 | 104 | options = options || {}; 105 | const flattenStackEveryRows = this.getFlattenRowsCount(columnNames, options); 106 | 107 | if (rows && rows.length) { 108 | const functions = []; 109 | 110 | for (let index = 0; index < rows.length; index++) { 111 | functions.push(this.createRecordHandler(columnNames, rows[index], index, jsRows, (index % flattenStackEveryRows) === 0)); 112 | } 113 | 114 | asyncLib.parallelLimit(functions, constants.parallelLimit, function onAyncDone(error) { 115 | callback(error, jsRows); 116 | }); 117 | } else { 118 | callback(null, jsRows); 119 | } 120 | }; 121 | 122 | /** 123 | * Read a JSON rows. 124 | * 125 | * @function 126 | * @memberof! RowsReader 127 | * @public 128 | * @param {Array} jsRows - The JS objects holding the row data. 129 | * @returns {Object} The JSON object 130 | */ 131 | RowsReader.prototype.readJSON = function (jsRows) { 132 | let json; 133 | if (jsRows) { 134 | if (jsRows.length) { 135 | const keys = Object.keys(jsRows[0]); 136 | if (keys.length !== 1) { 137 | throw new Error('Expected exactly 1 column in response, found: ' + keys.length); 138 | } 139 | 140 | json = []; 141 | const column = keys[0]; 142 | 143 | for (let index = 0; index < jsRows.length; index++) { 144 | json.push(recordReader.readJSON(jsRows[index], column)); 145 | } 146 | } else { 147 | json = []; 148 | } 149 | } 150 | 151 | return json; 152 | }; 153 | 154 | module.exports = new RowsReader(); 155 | -------------------------------------------------------------------------------- /test/spec/monitor-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const events = require('events'); 6 | const EventEmitter = events.EventEmitter; 7 | const Monitor = require('../../lib/monitor'); 8 | const emitter = require('../../lib/emitter'); 9 | 10 | const eventEmitter = new EventEmitter(); 11 | const monitor = Monitor.create(eventEmitter); 12 | 13 | describe('monitor Tests', function () { 14 | const createConnection = function (id, sql) { 15 | const connection = { 16 | diagnosticInfo: { 17 | id: id || 10, 18 | lastSQL: sql || 'test sql' 19 | } 20 | }; 21 | 22 | emitter(connection); 23 | 24 | return connection; 25 | }; 26 | 27 | describe('monitorConnection', function () { 28 | it('monitorConnection no data', function () { 29 | monitor.enabled = true; 30 | monitor.monitorConnection(); 31 | 32 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 33 | }); 34 | 35 | it('monitorConnection no diagnosticInfo', function () { 36 | monitor.enabled = true; 37 | monitor.monitorConnection({}); 38 | 39 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 40 | }); 41 | 42 | it('monitorConnection disabled', function () { 43 | monitor.enabled = false; 44 | monitor.monitorConnection({ 45 | diagnosticInfo: { 46 | id: 10 47 | } 48 | }); 49 | 50 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 51 | }); 52 | 53 | it('monitorConnection valid no pool', function () { 54 | const connection = createConnection(15, 'my sql'); 55 | 56 | monitor.enabled = true; 57 | monitor.monitorConnection(connection); 58 | 59 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 1); 60 | assert.isDefined(monitor.diagnosticInfo.connection[connection.diagnosticInfo.id].createTime); 61 | assert.equal(monitor.diagnosticInfo.connection[connection.diagnosticInfo.id].sql, 'my sql'); 62 | assert.isDefined(monitor.diagnosticInfo.connection[connection.diagnosticInfo.id].liveTime); 63 | 64 | assert.deepEqual(monitor.diagnosticInfo.connection[connection.diagnosticInfo.id], { 65 | connection, 66 | createTime: monitor.diagnosticInfo.connection[connection.diagnosticInfo.id].createTime, 67 | pool: undefined 68 | }); 69 | 70 | connection.emit('release'); 71 | 72 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 73 | }); 74 | 75 | it('monitorConnection valid with pool', function () { 76 | const connection = createConnection(); 77 | 78 | const pool = { 79 | test: true 80 | }; 81 | 82 | monitor.enabled = true; 83 | monitor.monitorConnection(connection, pool); 84 | 85 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 1); 86 | assert.isDefined(monitor.diagnosticInfo.connection[connection.diagnosticInfo.id].createTime); 87 | 88 | assert.deepEqual(monitor.diagnosticInfo.connection[connection.diagnosticInfo.id], { 89 | connection, 90 | createTime: monitor.diagnosticInfo.connection[connection.diagnosticInfo.id].createTime, 91 | pool 92 | }); 93 | 94 | connection.emit('release'); 95 | 96 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 97 | }); 98 | }); 99 | 100 | describe('monitorPool', function () { 101 | it('monitorPool no data', function () { 102 | monitor.enabled = true; 103 | monitor.monitorPool(); 104 | 105 | assert.equal(Object.keys(monitor.diagnosticInfo.pool).length, 0); 106 | }); 107 | 108 | it('monitorPool no diagnosticInfo', function () { 109 | monitor.enabled = true; 110 | monitor.monitorPool({}); 111 | 112 | assert.equal(Object.keys(monitor.diagnosticInfo.pool).length, 0); 113 | }); 114 | 115 | it('monitorPool disabled', function () { 116 | monitor.enabled = false; 117 | monitor.monitorPool({ 118 | diagnosticInfo: { 119 | id: 10 120 | } 121 | }); 122 | 123 | assert.equal(Object.keys(monitor.diagnosticInfo.pool).length, 0); 124 | }); 125 | 126 | it('monitorPool valid', function () { 127 | const pool = { 128 | diagnosticInfo: { 129 | id: 10 130 | } 131 | }; 132 | emitter(pool); 133 | 134 | monitor.enabled = true; 135 | monitor.monitorPool(pool); 136 | 137 | assert.equal(Object.keys(monitor.diagnosticInfo.pool).length, 1); 138 | assert.isDefined(monitor.diagnosticInfo.pool[pool.diagnosticInfo.id].createTime); 139 | assert.isDefined(monitor.diagnosticInfo.pool[pool.diagnosticInfo.id].liveTime); 140 | 141 | assert.deepEqual(monitor.diagnosticInfo.pool[pool.diagnosticInfo.id], { 142 | pool, 143 | createTime: monitor.diagnosticInfo.pool[pool.diagnosticInfo.id].createTime 144 | }); 145 | 146 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 147 | const connection = createConnection(); 148 | pool.emit('connection-created', connection); 149 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 1); 150 | connection.emit('release'); 151 | assert.equal(Object.keys(monitor.diagnosticInfo.connection).length, 0); 152 | 153 | pool.emit('release'); 154 | 155 | assert.equal(Object.keys(monitor.diagnosticInfo.pool).length, 0); 156 | }); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /lib/monitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let enabled = false; 4 | 5 | /** 6 | * This class monitors the active connections and pools. 7 | * 8 | * @author Sagie Gur-Ari 9 | * @class Monitor 10 | * @private 11 | * @param {SimpleOracleDB} simpleOracleDB - The instance of the library 12 | */ 13 | function Monitor(simpleOracleDB) { 14 | const self = this; 15 | self.diagnosticInfo = {}; 16 | 17 | /*eslint-disable func-name-matching*/ 18 | /** 19 | * True if the monitor is enabled and it will listen and store pool/connection diagnostics information. 20 | * 21 | * @memberof! Monitor 22 | * @member {Boolean} 23 | * @alias Monitor.enabled 24 | * @public 25 | */ 26 | Object.defineProperty(self, 'enabled', { 27 | /** 28 | * Getter for the enabled flag. 29 | * 30 | * @function 31 | * @memberof! Monitor 32 | * @private 33 | * @returns {Boolean} The enabled flag value 34 | */ 35 | get: function isEnabled() { 36 | return enabled; 37 | }, 38 | /** 39 | * Setter for the enabled flag. 40 | * 41 | * @function 42 | * @memberof! Monitor 43 | * @private 44 | * @param {Boolean} value - The enabled flag value 45 | */ 46 | set: function setEnabled(value) { 47 | enabled = value; 48 | 49 | this.diagnosticInfo.pool = this.diagnosticInfo.pool || {}; 50 | this.diagnosticInfo.connection = this.diagnosticInfo.connection || {}; 51 | 52 | //when disabling, clear all stored data 53 | if (!enabled) { 54 | this.diagnosticInfo.pool = {}; 55 | this.diagnosticInfo.connection = {}; 56 | } 57 | } 58 | }); 59 | /*eslint-enable func-name-matching*/ 60 | self.enabled = false; 61 | 62 | simpleOracleDB.on('connection-created', function onConnectionCreated(connection) { 63 | self.monitorConnection(connection); 64 | }); 65 | 66 | simpleOracleDB.on('pool-created', function onPoolCreated(pool) { 67 | self.monitorPool(pool); 68 | }); 69 | } 70 | 71 | /** 72 | * Monitors the provided connection. 73 | * 74 | * @function 75 | * @memberof! Monitor 76 | * @private 77 | * @param {Connection} connection - The connection instance 78 | * @param {Pool} [pool] - The pool instance (if this connection was created via pool) 79 | */ 80 | Monitor.prototype.monitorConnection = function (connection, pool) { 81 | const self = this; 82 | 83 | if (connection && connection.diagnosticInfo && self.enabled) { 84 | const id = connection.diagnosticInfo.id; 85 | const statsInfo = { 86 | connection, 87 | createTime: Date.now(), 88 | pool 89 | }; 90 | 91 | /*eslint-disable func-name-matching*/ 92 | Object.defineProperties(statsInfo, { 93 | sql: { 94 | /** 95 | * Getter for the last SQL executed by the connection. 96 | * 97 | * @function 98 | * @memberof! Info 99 | * @private 100 | * @returns {String} The last SQL executed by the connection 101 | */ 102 | get: function getSQL() { 103 | return this.connection.diagnosticInfo.lastSQL; 104 | } 105 | }, 106 | liveTime: { 107 | /** 108 | * Getter for the connection live time in millies. 109 | * 110 | * @function 111 | * @memberof! Info 112 | * @private 113 | * @returns {Number} The connection live time in millies 114 | */ 115 | get: function getLiveTime() { 116 | return Date.now() - this.createTime; 117 | } 118 | } 119 | }); 120 | /*eslint-enable func-name-matching*/ 121 | 122 | self.diagnosticInfo.connection[id] = statsInfo; 123 | 124 | connection.once('release', function onRelease() { 125 | delete self.diagnosticInfo.connection[id]; 126 | }); 127 | } 128 | }; 129 | 130 | /** 131 | * Monitors the provided pool. 132 | * 133 | * @function 134 | * @memberof! Monitor 135 | * @private 136 | * @param {Pool} pool - The pool instance 137 | */ 138 | Monitor.prototype.monitorPool = function (pool) { 139 | const self = this; 140 | 141 | if (pool && pool.diagnosticInfo && self.enabled) { 142 | const statsInfo = { 143 | pool, 144 | createTime: Date.now() 145 | }; 146 | 147 | /*eslint-disable func-name-matching*/ 148 | Object.defineProperties(statsInfo, { 149 | liveTime: { 150 | /** 151 | * Getter for the pool live time in millies. 152 | * 153 | * @function 154 | * @memberof! Info 155 | * @private 156 | * @returns {Number} The pool live time in millies 157 | */ 158 | get: function getLiveTime() { 159 | return Date.now() - this.createTime; 160 | } 161 | } 162 | }); 163 | /*eslint-enable func-name-matching*/ 164 | 165 | const id = pool.diagnosticInfo.id; 166 | self.diagnosticInfo.pool[id] = statsInfo; 167 | 168 | const onCreate = function (connection) { 169 | self.monitorConnection(connection, pool); 170 | }; 171 | 172 | pool.on('connection-created', onCreate); 173 | 174 | pool.once('release', function onRelease() { 175 | delete self.diagnosticInfo.pool[id]; 176 | 177 | pool.removeListener('connection-created', onCreate); 178 | }); 179 | } 180 | }; 181 | 182 | module.exports = { 183 | /** 184 | * Creates and returns a new monitor instance. 185 | * 186 | * @function 187 | * @memberof! Monitor 188 | * @private 189 | * @param {SimpleOracleDB} simpleOracleDB - The instance of the library 190 | * @returns {Monitor} The monitor instance 191 | */ 192 | create(simpleOracleDB) { 193 | return new Monitor(simpleOracleDB); 194 | } 195 | }; 196 | -------------------------------------------------------------------------------- /test/spec/resultset-read-stream-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const EventEmitterEnhancer = require('event-emitter-enhancer'); 6 | const ResultSetReadStream = require('../../lib/resultset-read-stream'); 7 | 8 | describe('ResultSetReadStream Tests', function () { 9 | const failListener = function (eventName) { 10 | return function () { 11 | assert.fail(eventName); 12 | }; 13 | }; 14 | 15 | describe('read tests', function () { 16 | it('no data', function (done) { 17 | const stream = new ResultSetReadStream(); 18 | EventEmitterEnhancer.modifyInstance(stream); 19 | stream.nextRow = function (callback) { 20 | process.nextTick(function () { 21 | callback(); 22 | }); 23 | }; 24 | 25 | ['data', 'error'].forEach(function (eventName) { 26 | stream.on(eventName, failListener(eventName)); 27 | }); 28 | 29 | const remove = stream.onAny(['end', 'close'], function () { 30 | remove(); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('null data', function (done) { 36 | const stream = new ResultSetReadStream(); 37 | EventEmitterEnhancer.modifyInstance(stream); 38 | stream.nextRow = function (callback) { 39 | process.nextTick(function () { 40 | callback(null, null); 41 | }); 42 | }; 43 | 44 | ['data', 'error'].forEach(function (eventName) { 45 | stream.on(eventName, failListener(eventName)); 46 | }); 47 | 48 | const remove = stream.onAny(['end', 'close'], function () { 49 | remove(); 50 | done(); 51 | }); 52 | }); 53 | 54 | it('multiple rows', function (done) { 55 | const stream = new ResultSetReadStream(); 56 | EventEmitterEnhancer.modifyInstance(stream); 57 | let nextCounter = 0; 58 | stream.nextRow = function (callback) { 59 | if (nextCounter >= 5) { 60 | process.nextTick(function () { 61 | callback(); 62 | }); 63 | } else { 64 | process.nextTick(function () { 65 | nextCounter++; 66 | 67 | callback(null, { 68 | row: nextCounter 69 | }); 70 | }); 71 | } 72 | }; 73 | 74 | stream.on('error', failListener('error')); 75 | 76 | let dataFound = 0; 77 | stream.on('data', function (data) { 78 | dataFound++; 79 | 80 | assert.deepEqual(data, { 81 | row: dataFound 82 | }); 83 | }); 84 | 85 | const remove = stream.onAny(['end', 'close'], function () { 86 | assert.equal(dataFound, 5); 87 | 88 | remove(); 89 | done(); 90 | }); 91 | }); 92 | 93 | it('error on start', function (done) { 94 | const stream = new ResultSetReadStream(); 95 | stream.nextRow = function (callback) { 96 | process.nextTick(function () { 97 | callback(new Error('test')); 98 | }); 99 | }; 100 | 101 | stream.on('data', failListener('data')); 102 | 103 | stream.on('error', function (error) { 104 | assert.equal(error.message, 'test'); 105 | 106 | done(); 107 | }); 108 | }); 109 | 110 | it('error after few data events', function (done) { 111 | let counter = 0; 112 | const stream = new ResultSetReadStream(); 113 | stream.nextRow = function (callback) { 114 | counter++; 115 | 116 | if (counter < 5) { 117 | process.nextTick(function () { 118 | callback(null, { 119 | id: counter 120 | }); 121 | }); 122 | } else if (counter === 5) { 123 | process.nextTick(function () { 124 | callback(new Error('test')); 125 | }); 126 | } else { 127 | process.nextTick(function () { 128 | callback(new Error('fail')); 129 | }); 130 | } 131 | }; 132 | 133 | ['end', 'close'].forEach(function (eventName) { 134 | stream.on(eventName, failListener(eventName)); 135 | }); 136 | 137 | stream.on('data', function (data) { 138 | assert.isTrue(data.id < 5); 139 | }); 140 | 141 | stream.on('error', function (error) { 142 | assert.equal(error.message, 'test'); 143 | 144 | done(); 145 | }); 146 | }); 147 | 148 | it('all data read', function (done) { 149 | let counter = 0; 150 | const stream = new ResultSetReadStream(); 151 | EventEmitterEnhancer.modifyInstance(stream); 152 | stream.nextRow = function (callback) { 153 | counter++; 154 | 155 | if (counter < 5) { 156 | process.nextTick(function () { 157 | callback(null, { 158 | id: counter 159 | }); 160 | }); 161 | } else if (counter === 5) { 162 | process.nextTick(function () { 163 | callback(); 164 | }); 165 | } else { 166 | process.nextTick(function () { 167 | callback(new Error('fail')); 168 | }); 169 | } 170 | }; 171 | 172 | stream.on('error', failListener('error')); 173 | 174 | let dataFound = 0; 175 | stream.on('data', function (data) { 176 | dataFound++; 177 | 178 | assert.isTrue(data.id < 5); 179 | }); 180 | 181 | const remove = stream.onAny(['end', 'close'], function () { 182 | assert.equal(dataFound, 4); 183 | 184 | remove(); 185 | done(); 186 | }); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /lib/promise-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const funcs = require('funcs-js'); 4 | 5 | /** 6 | * Promise utility class. 7 | * 8 | * @author Sagie Gur-Ari 9 | * @class PromiseHelper 10 | * @private 11 | */ 12 | function PromiseHelper() { 13 | this.extensions = { 14 | pool: {}, 15 | connection: {} 16 | }; 17 | } 18 | 19 | /** 20 | * Empty function. 21 | * 22 | * @function 23 | * @memberof! PromiseHelper 24 | * @private 25 | * @returns {undefined} Empty return 26 | */ 27 | PromiseHelper.prototype.noop = function () { 28 | return undefined; 29 | }; 30 | 31 | /** 32 | * Returns true if the provided object supports the basic promise capabilities. 33 | * 34 | * @function 35 | * @memberof! PromiseHelper 36 | * @private 37 | * @param {Object} promise - The promise object to validate 38 | * @returns {Boolean} True if the provided object supports the basic promise capabilities 39 | */ 40 | PromiseHelper.prototype.isPromise = function (promise) { 41 | let valid = false; 42 | if (promise && promise.then && promise.catch && (typeof promise.then === 'function') && (typeof promise.catch === 'function')) { 43 | valid = true; 44 | } 45 | 46 | return valid; 47 | }; 48 | 49 | /** 50 | * Ensures the provided function is invoked as an async function even if returning a promise. 51 | * 52 | * @function 53 | * @memberof! PromiseHelper 54 | * @private 55 | * @param {Object} func - The function to invoke 56 | * @param {function} callback - The callback to provide to the invoked function 57 | */ 58 | PromiseHelper.prototype.runAsync = function (func, callback) { 59 | callback = funcs.once(callback, { 60 | callbackStyle: true 61 | }); 62 | 63 | const promise = func(callback); 64 | 65 | if (this.isPromise(promise)) { 66 | promise.then(function onDone(result) { 67 | callback(null, result); 68 | }).catch(callback); 69 | } 70 | }; 71 | 72 | /** 73 | * Calls the provided function with a callback. 74 | * 75 | * @function 76 | * @memberof! PromiseHelper 77 | * @private 78 | * @param {function} run - The async function to call (callback will be provided) 79 | * @returns {Promise} In case of no callback provided in input, this function will return a promise 80 | */ 81 | PromiseHelper.prototype.runPromise = function (run) { 82 | let promise; 83 | let promiseCallback; 84 | 85 | if (global.Promise) { 86 | promise = new global.Promise(function runViaPromise(resolve, reject) { 87 | promiseCallback = function (error, output) { 88 | if (error) { 89 | reject(error); 90 | } else { 91 | resolve(output); 92 | } 93 | }; 94 | 95 | run(promiseCallback); 96 | }); 97 | } else { 98 | throw new Error('No callback provided and promise not supported.'); 99 | } 100 | 101 | return promise; 102 | }; 103 | 104 | /** 105 | * Calls the provided function with a callback and if needed, return a promise. 106 | * 107 | * @function 108 | * @memberof! PromiseHelper 109 | * @private 110 | * @param {function} run - The async function to call (callback will be provided) 111 | * @param {function} [callback] - An optional callback function 112 | * @param {Boolean} [force=false] - If true, do not check if promise is supported 113 | * @returns {Promise} In case of no callback provided in input, this function will return a promise 114 | * @example 115 | * ```js 116 | * promiseHelper.run(function myFunc(promiseCallback) { 117 | * //do some async activity... 118 | * }, function onFuncDone(error, result) { 119 | * if (error) { 120 | * //function failed. 121 | * } else { 122 | * //function done 123 | * } 124 | * }); 125 | * ``` 126 | */ 127 | PromiseHelper.prototype.run = function (run, callback, force) { 128 | //if promise requested and supported by current platform 129 | let promise; 130 | if ((force || global.Promise) && (!callback)) { 131 | promise = this.runPromise(run); 132 | } else { 133 | run(callback); 134 | } 135 | 136 | return promise; 137 | }; 138 | 139 | /** 140 | * Adds promise support for the provided function. 141 | * 142 | * @function 143 | * @memberof! PromiseHelper 144 | * @private 145 | * @param {function} func - The function to promisify 146 | * @param {Object} [options] - Holds constious behaviour options 147 | * @param {Boolean} [options.force=false] - If true, do not check if promise is supported 148 | * @param {Boolean} [options.defaultCallback=false] - If true and no callback provided, generate an empty callback 149 | * @param {Number} [options.callbackMinIndex=0] - The minimum index in the arguments that the callback is found in 150 | * @returns {function} The wrapper function 151 | * @example 152 | * ```js 153 | * Connection.prototype.query = promiseHelper.promisify(Connection.prototype.query); 154 | * ``` 155 | */ 156 | PromiseHelper.prototype.promisify = function (func, options) { 157 | //jscs:disable safeContextKeyword 158 | const promiseHelper = this; 159 | //jscs:enable safeContextKeyword 160 | 161 | options = options || {}; 162 | 163 | return function promiseWrapper() { 164 | /*eslint-disable no-invalid-this*/ 165 | const self = this; 166 | /*eslint-enable no-invalid-this*/ 167 | 168 | const argumentsArray = Array.prototype.slice.call(arguments, 0); 169 | 170 | //if last element is callback but undefined was provided 171 | let continueRemove = true; 172 | do { 173 | if (argumentsArray.length && (argumentsArray[argumentsArray.length - 1] === undefined)) { 174 | argumentsArray.pop(); 175 | } else { 176 | continueRemove = false; 177 | } 178 | } while (continueRemove); 179 | 180 | const minArgs = (options.callbackMinIndex || 0) + 1; 181 | 182 | let callback; 183 | let output; 184 | if ((argumentsArray.length >= minArgs) && (typeof argumentsArray[argumentsArray.length - 1] === 'function')) { 185 | output = func.apply(self, argumentsArray); 186 | } else { //promise 187 | if ((!options.force) && (!options.defaultCallback) && (!global.Promise)) { 188 | throw new Error('No callback provided and promise not supported.'); 189 | } 190 | 191 | output = promiseHelper.run(function asyncFunction(promiseCallback) { 192 | callback = callback || promiseCallback; 193 | 194 | if ((!callback) && options.defaultCallback) { 195 | callback = promiseHelper.noop; 196 | } 197 | 198 | argumentsArray.push(callback); 199 | 200 | func.apply(self, argumentsArray); 201 | }, callback, options.force); 202 | } 203 | 204 | return output; 205 | }; 206 | }; 207 | 208 | module.exports = new PromiseHelper(); 209 | -------------------------------------------------------------------------------- /test/spec/stream-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const EventEmitter = require('events').EventEmitter; 6 | const stream = require('../../lib/stream'); 7 | const utils = require('../helpers/utils'); 8 | 9 | describe('stream Tests', function () { 10 | describe('read Tests', function () { 11 | it('read valid test', function (done) { 12 | const testStream = new EventEmitter(); 13 | testStream.setEncoding = function (encoding) { 14 | assert.equal(encoding, 'utf8'); 15 | }; 16 | 17 | stream.read(testStream, false, function (error, data) { 18 | assert.isNull(error); 19 | assert.equal(data, 'first line\nsecond line, second part.'); 20 | 21 | assert.equal(0, testStream.listeners('data').length); 22 | assert.equal(0, testStream.listeners('end').length); 23 | assert.equal(0, testStream.listeners('error').length); 24 | 25 | done(); 26 | }); 27 | 28 | assert.equal(1, testStream.listeners('data').length); 29 | assert.equal(1, testStream.listeners('end').length); 30 | assert.equal(1, testStream.listeners('error').length); 31 | 32 | testStream.emit('data', 'first line\n'); 33 | testStream.emit('data', 'second line'); 34 | testStream.emit('data', ', second part.'); 35 | testStream.emit('end'); 36 | testStream.emit('close'); 37 | }); 38 | 39 | it('read error test', function (done) { 40 | const testStream = new EventEmitter(); 41 | testStream.setEncoding = function (encoding) { 42 | assert.equal(encoding, 'utf8'); 43 | }; 44 | 45 | stream.read(testStream, false, function (error, data) { 46 | assert.isDefined(error); 47 | assert.isUndefined(data); 48 | 49 | assert.equal(0, testStream.listeners('data').length); 50 | assert.equal(0, testStream.listeners('end').length); 51 | assert.equal(0, testStream.listeners('error').length); 52 | 53 | done(); 54 | }); 55 | 56 | assert.equal(1, testStream.listeners('data').length); 57 | assert.equal(1, testStream.listeners('end').length); 58 | assert.equal(1, testStream.listeners('error').length); 59 | 60 | testStream.emit('data'); //empty data 61 | testStream.emit('data', 'first line\n'); 62 | testStream.emit('data', 'second line'); 63 | testStream.emit('data', ', second part.'); 64 | testStream.emit('error', new Error('test')); 65 | }); 66 | 67 | it('read close test', function (done) { 68 | const testStream = new EventEmitter(); 69 | testStream.setEncoding = function (encoding) { 70 | assert.equal(encoding, 'utf8'); 71 | }; 72 | 73 | stream.read(testStream, false, function (error, data) { 74 | assert.isNull(error); 75 | assert.equal(data, 'My Data.'); 76 | 77 | assert.equal(0, testStream.listeners('data').length); 78 | assert.equal(0, testStream.listeners('end').length); 79 | assert.equal(0, testStream.listeners('error').length); 80 | 81 | done(); 82 | }); 83 | 84 | assert.equal(1, testStream.listeners('data').length); 85 | assert.equal(1, testStream.listeners('end').length); 86 | assert.equal(1, testStream.listeners('error').length); 87 | 88 | testStream.emit('data', 'My Data.'); 89 | testStream.emit('close'); 90 | testStream.emit('end'); 91 | }); 92 | }); 93 | 94 | describe('write Tests', function () { 95 | it('write string test', function (done) { 96 | const writable = new EventEmitter(); 97 | writable.end = function (data, encoding, callback) { 98 | assert.equal(data, 'TEST STRING DATA'); 99 | assert.equal(encoding, 'utf8'); 100 | assert.isFunction(callback); 101 | 102 | callback(); 103 | }; 104 | 105 | stream.write(writable, 'TEST STRING DATA', function (error) { 106 | assert.isUndefined(error); 107 | assert.equal(0, writable.listeners('error').length); 108 | 109 | done(); 110 | }); 111 | }); 112 | 113 | it('write Buffer test', function (done) { 114 | const writable = new EventEmitter(); 115 | writable.end = function (data, callback) { 116 | assert.deepEqual(data.toJSON(), (utils.createBuffer('TEST STRING DATA')).toJSON()); 117 | assert.isFunction(callback); 118 | 119 | callback(); 120 | }; 121 | 122 | stream.write(writable, utils.createBuffer('TEST STRING DATA'), function (error) { 123 | assert.isUndefined(error); 124 | assert.equal(0, writable.listeners('error').length); 125 | 126 | done(); 127 | }); 128 | }); 129 | 130 | it('write null data test', function (done) { 131 | const writable = new EventEmitter(); 132 | writable.end = function () { 133 | assert.fail(); 134 | }; 135 | 136 | stream.write(writable, null, function (error) { 137 | assert.isUndefined(error); 138 | assert.equal(0, writable.listeners('error').length); 139 | 140 | done(); 141 | }); 142 | }); 143 | 144 | it('write undefined data test', function (done) { 145 | const writable = new EventEmitter(); 146 | writable.end = function () { 147 | assert.fail(); 148 | }; 149 | 150 | stream.write(writable, undefined, function (error) { 151 | assert.isUndefined(error); 152 | assert.equal(0, writable.listeners('error').length); 153 | 154 | done(); 155 | }); 156 | }); 157 | 158 | it('write null writable test', function (done) { 159 | stream.write(null, 'data', function (error) { 160 | assert.isUndefined(error); 161 | 162 | done(); 163 | }); 164 | }); 165 | 166 | it('write undefined writable test', function (done) { 167 | stream.write(undefined, 'data', function (error) { 168 | assert.isUndefined(error); 169 | 170 | done(); 171 | }); 172 | }); 173 | 174 | it('write error test', function (done) { 175 | const writable = new EventEmitter(); 176 | writable.end = function () { 177 | const callback = Array.prototype.pop.call(arguments); 178 | writable.emit('error', new Error('test')); 179 | 180 | setTimeout(callback, 10); 181 | }; 182 | 183 | stream.write(writable, 'TEST STRING DATA', function (error) { 184 | assert.isDefined(error); 185 | assert.equal(error.message, 'test'); 186 | assert.equal(0, writable.listeners('error').length); 187 | 188 | done(); 189 | }); 190 | }); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /test/spec/stability-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const integrationHelper = require('../helpers/integration-helper'); 6 | const utils = require('../helpers/utils'); 7 | 8 | if (process.env.TEST_STABILITY) { 9 | integrationHelper(function (oracledb, connAttrs, initDB, end) { 10 | if (oracledb && connAttrs && initDB && end) { 11 | describe('Stability Tests', function () { 12 | const self = this; 13 | 14 | self.timeout(600000); 15 | 16 | it('batchInsert and query - LOB data', function (done) { 17 | const table = 'TEST_ORA_STB1'; 18 | 19 | let longClobText = 'this is a really long line of test data\n'; 20 | const buffer = []; 21 | for (let index = 0; index < 1000; index++) { 22 | buffer.push(longClobText); 23 | } 24 | longClobText = buffer.join(''); 25 | 26 | initDB(table, [], function (pool) { 27 | pool.getConnection(function (err, connection) { 28 | assert.isNull(err); 29 | 30 | const rowData = []; 31 | for (let index = 0; index < 100; index++) { 32 | rowData.push({ 33 | value1: 'test' + index, 34 | value2: index, 35 | clob1: longClobText, 36 | blob2: utils.createBuffer('blob text here') 37 | }); 38 | } 39 | 40 | connection.batchInsert('INSERT INTO ' + table + ' (COL1, COL2, LOB1, LOB2) values (:value1, :value2, EMPTY_CLOB(), EMPTY_BLOB())', rowData, { 41 | autoCommit: true, 42 | lobMetaInfo: { 43 | LOB1: 'clob1', 44 | LOB2: 'blob2' 45 | } 46 | }, function (error, results) { 47 | assert.isNull(error); 48 | assert.equal(rowData.length, results.length); 49 | assert.equal(1, results[0].rowsAffected); 50 | 51 | connection.query('SELECT * FROM ' + table + ' ORDER BY COL2 ASC', function (queryError, jsRows) { 52 | assert.isNull(queryError); 53 | assert.equal(jsRows.length, rowData.length); 54 | 55 | for (let index = 0; index < 100; index++) { 56 | assert.deepEqual({ 57 | COL1: 'test' + index, 58 | COL2: index, 59 | COL3: null, 60 | COL4: null, 61 | LOB1: longClobText, 62 | LOB2: utils.createBuffer('blob text here') 63 | }, jsRows[index]); 64 | } 65 | 66 | end(done, connection); 67 | }); 68 | }); 69 | }); 70 | }); 71 | }); 72 | 73 | it('update - LOB data', function (done) { 74 | const table = 'TEST_ORA_STB2'; 75 | 76 | let longClobText = 'this is a really long line of test data\n'; 77 | const buffer = []; 78 | for (let index = 0; index < 1000; index++) { 79 | buffer.push(longClobText); 80 | } 81 | longClobText = buffer.join(''); 82 | 83 | initDB(table, [], function (pool) { 84 | pool.getConnection(function (err, connection) { 85 | assert.isNull(err); 86 | 87 | connection.insert(`INSERT INTO ${table} (COL1, COL2, LOB1, LOB2) values (:value1, :value2, EMPTY_CLOB(), EMPTY_BLOB())`, { 88 | value1: 'test', 89 | value2: 123, 90 | clob1: longClobText, 91 | blob2: utils.createBuffer('blob text here') 92 | }, { 93 | autoCommit: true, 94 | lobMetaInfo: { 95 | LOB1: 'clob1', 96 | LOB2: 'blob2' 97 | } 98 | }, function (error, results) { 99 | assert.isNull(error); 100 | assert.equal(1, results.rowsAffected); 101 | 102 | connection.release(() => { 103 | pool.run(function (runConnection, cb) { 104 | runConnection.update(`UPDATE ${table} SET LOB1 = EMPTY_CLOB() WHERE COL1 = :value1`, { 105 | newclob1: 'NEW CLOB TEXT VALUE', 106 | value1: 'test', 107 | }, { 108 | autoCommit: true, 109 | lobMetaInfo: { 110 | LOB1: 'newclob1' 111 | } 112 | }, cb); 113 | }, function onUpdate(updateError, updateResults) { 114 | assert.isNull(updateError); 115 | assert.isDefined(updateResults); 116 | 117 | pool.run(function (runConnection, cb) { 118 | runConnection.query(`SELECT * FROM ${table}`, [], { 119 | resultSet: false 120 | }, cb); 121 | }, function (queryError, jsRows) { 122 | assert.isNull(queryError); 123 | assert.deepEqual([ 124 | { 125 | COL1: 'test', 126 | COL2: 123, 127 | COL3: null, 128 | COL4: null, 129 | LOB1: 'NEW CLOB TEXT VALUE', 130 | LOB2: utils.createBuffer('blob text here') 131 | } 132 | ], jsRows); 133 | 134 | done(); 135 | }); 136 | }); 137 | }); 138 | }); 139 | }); 140 | }); 141 | }); 142 | }); 143 | } 144 | }); 145 | } 146 | -------------------------------------------------------------------------------- /lib/resultset-reader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debuglog')('simple-oracledb'); 4 | const rowsReader = require('./rows-reader'); 5 | 6 | /*jslint debug: true */ 7 | /** 8 | * ResultSet object reading helper functions. 9 | * 10 | * @author Sagie Gur-Ari 11 | * @class ResultSetReader 12 | * @private 13 | */ 14 | function ResultSetReader() { 15 | //should not be called 16 | } 17 | /*jslint debug: false */ 18 | 19 | /** 20 | * Releases the resultset. 21 | * 22 | * @function 23 | * @memberof! ResultSetReader 24 | * @private 25 | * @param {Array} resultSet - The oracle ResultSet object 26 | * @param {Boolean} ignoreErrors - True to ignore errors 27 | * @param {AsyncCallback} callback - called when the result set is released 28 | */ 29 | ResultSetReader.prototype.releaseResultSet = function (resultSet, ignoreErrors, callback) { 30 | if (resultSet) { 31 | resultSet.close(function onClose(error) { 32 | if (error && (!ignoreErrors)) { 33 | callback(error); 34 | } else { 35 | if (error) { 36 | debug('Unable to close resultset, ', error.stack); 37 | } 38 | 39 | callback(); 40 | } 41 | }); 42 | } else { 43 | callback(); 44 | } 45 | }; 46 | 47 | /** 48 | * Reads the next rows data from the provided oracle ResultSet object. 49 | * 50 | * @function 51 | * @memberof! ResultSetReader 52 | * @public 53 | * @param {Array} columnNames - Array of strings holding the column names of the results 54 | * @param {Array} resultSet - The oracle ResultSet object 55 | * @param {Object} [options] - Any options 56 | * @param {Number} [options.bulkRowsAmount=100] - The amount of rows to fetch 57 | * @param {Number} [options.flattenStackEveryRows=Math.max(1, Math.floor(100 / columnNames.length))] - The amount of rows after which the JS stack is flattened, low number can result in performance impact, high number can result in stack overflow error 58 | * @param {AsyncCallback} callback - called when the next rows have been read 59 | */ 60 | ResultSetReader.prototype.readNextRows = function (columnNames, resultSet, options, callback) { 61 | const self = this; 62 | let bulkRowsAmount = 100; 63 | 64 | if (arguments.length === 3) { 65 | callback = options; 66 | options = null; 67 | } else if (options) { 68 | bulkRowsAmount = options.bulkRowsAmount || bulkRowsAmount; 69 | } 70 | 71 | resultSet.getRows(bulkRowsAmount, function onRows(error, rows) { 72 | if (error) { 73 | self.releaseResultSet(resultSet, true, function onClose() { 74 | callback(error); 75 | }); 76 | } else if ((!rows) || (rows.length === 0)) { 77 | self.releaseResultSet(resultSet, false, function onClose(releaseError) { 78 | callback(releaseError, []); 79 | }); 80 | } else { 81 | rowsReader.read(columnNames, rows, callback); 82 | } 83 | }); 84 | }; 85 | 86 | /** 87 | * Reads all data from the provided oracle ResultSet object into the provided buffer. 88 | * 89 | * @function 90 | * @memberof! ResultSetReader 91 | * @private 92 | * @param {Array} columnNames - Array of strings holding the column names of the results 93 | * @param {Array} resultSet - The oracle ResultSet object 94 | * @param {Object} options - Any options 95 | * @param {Number} [options.bulkRowsAmount=100] - The amount of rows to fetch 96 | * @param {Number} [options.flattenStackEveryRows] - The amount of rows after which the JS stack is flattened, low number can result in performance impact, high number can result in stack overflow error 97 | * @param {AsyncCallback} callback - called when all rows are fully read or in case of an error 98 | * @param {Array} [jsRowsBuffer] - The result buffer, if not provided, the callback will be called for each bulk 99 | */ 100 | ResultSetReader.prototype.readAllRows = function (columnNames, resultSet, options, callback, jsRowsBuffer) { 101 | const self = this; 102 | 103 | self.readNextRows(columnNames, resultSet, options, function onNextRows(error, jsRows) { 104 | if (error) { 105 | callback(error); 106 | } else if (jsRows && jsRows.length) { 107 | if (jsRowsBuffer) { 108 | Array.prototype.push.apply(jsRowsBuffer, jsRows); 109 | } else { //split results 110 | callback(null, jsRows); 111 | } 112 | 113 | self.readAllRows(columnNames, resultSet, options, callback, jsRowsBuffer); 114 | } else { 115 | const lastResult = jsRowsBuffer || []; 116 | callback(null, lastResult); 117 | } 118 | }); 119 | }; 120 | 121 | /** 122 | * Reads all data from the provided oracle ResultSet object. 123 | * 124 | * @function 125 | * @memberof! ResultSetReader 126 | * @public 127 | * @param {Array} columnNames - Array of strings holding the column names of the results 128 | * @param {Array} resultSet - The oracle ResultSet object 129 | * @param {Object} options - Any options 130 | * @param {AsyncCallback} callback - called when all rows are fully read or in case of an error 131 | */ 132 | ResultSetReader.prototype.readFully = function (columnNames, resultSet, options, callback) { 133 | this.readAllRows(columnNames, resultSet, options, callback, []); 134 | }; 135 | 136 | /** 137 | * Reads all data from the provided oracle ResultSet object to the callback in bulks.
138 | * The last callback call will have an empty result. 139 | * 140 | * @function 141 | * @memberof! ResultSetReader 142 | * @public 143 | * @param {Array} columnNames - Array of strings holding the column names of the results 144 | * @param {Array} resultSet - The oracle ResultSet object 145 | * @param {Object} options - Any options 146 | * @param {AsyncCallback} callback - called for each read bulk of rows or in case of an error 147 | */ 148 | ResultSetReader.prototype.readBulks = function (columnNames, resultSet, options, callback) { 149 | this.readAllRows(columnNames, resultSet, options, callback); 150 | }; 151 | 152 | /** 153 | * Reads all data from the provided oracle ResultSet object to the callback in bulks.
154 | * The last callback call will have an empty result. 155 | * 156 | * @function 157 | * @memberof! ResultSetReader 158 | * @public 159 | * @param {Array} columnNames - Array of strings holding the column names of the results 160 | * @param {Array} resultSet - The oracle ResultSet object 161 | * @param {Object} [options] - Any options 162 | * @param {ResultSetReadStream} stream - The stream used to read the results from 163 | */ 164 | ResultSetReader.prototype.stream = function (columnNames, resultSet, options, stream) { 165 | const self = this; 166 | 167 | if (!stream) { 168 | stream = options; 169 | options = null; 170 | } 171 | 172 | options = options || {}; 173 | 174 | let rowsData; 175 | 176 | stream.metaData = columnNames; 177 | 178 | /*eslint-disable func-name-matching*/ 179 | /** 180 | * Reads the next rows from the resultset and pushes via events. 181 | * 182 | * @function 183 | * @memberof! ResultSetReadStream 184 | * @alias ResultSetReadStream.nextRow 185 | * @constiation 1 186 | * @private 187 | * @param {function} streamCallback - The callback function 188 | */ 189 | stream.nextRow = function readNextRow(streamCallback) { 190 | /*istanbul ignore else*/ 191 | if (!stream.closed) { 192 | if (rowsData && rowsData.length) { 193 | streamCallback(null, rowsData.shift()); 194 | } else { 195 | self.readNextRows(columnNames, resultSet, options, function onRead(error, rows) { 196 | let rowData; 197 | if (!error) { 198 | rowsData = rows; 199 | 200 | if (rowsData && rowsData.length) { 201 | rowData = rowsData.shift(); 202 | } 203 | } 204 | 205 | streamCallback(error, rowData); 206 | }); 207 | } 208 | } 209 | }; 210 | /*eslint-enable func-name-matching*/ 211 | }; 212 | 213 | module.exports = new ResultSetReader(); 214 | -------------------------------------------------------------------------------- /test/spec/simple-oracledb-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const PromiseLib = global.Promise || require('promiscuous'); 6 | 7 | describe('simple oracledb tests', function () { 8 | const oracledb = require('../helpers/test-oracledb').create(); 9 | oracledb.BLOB = 2019; 10 | oracledb.CLOB = 2017; 11 | oracledb.STRING = 2001; 12 | oracledb.NUMBER = 2010; 13 | oracledb.DATE = 2011; 14 | oracledb.BIND_OUT = 3003; 15 | const simpleOracleDB = require('../../'); 16 | simpleOracleDB.extend(oracledb); 17 | 18 | describe('extend tests', function () { 19 | it('extend oracledb', function () { 20 | const oracledbLib = { 21 | createPool() { 22 | return undefined; 23 | } 24 | }; 25 | assert.isUndefined(oracledbLib.on); 26 | 27 | simpleOracleDB.extend(oracledbLib); 28 | assert.isTrue(oracledbLib.simplified); 29 | assert.isDefined(oracledbLib.on); 30 | 31 | simpleOracleDB.extend(oracledbLib); 32 | assert.isTrue(oracledbLib.simplified); 33 | }); 34 | 35 | it('extend pool', function () { 36 | const pool = { 37 | getConnection() { 38 | return undefined; 39 | } 40 | }; 41 | simpleOracleDB.extend(pool); 42 | assert.isTrue(pool.simplified); 43 | }); 44 | 45 | it('extend connection', function () { 46 | const connection = { 47 | execute() { 48 | return undefined; 49 | } 50 | }; 51 | simpleOracleDB.extend(connection); 52 | assert.isTrue(connection.simplified); 53 | }); 54 | 55 | it('extend unsupported', function () { 56 | const obj = {}; 57 | 58 | let errorFound = false; 59 | try { 60 | simpleOracleDB.extend(obj); 61 | } catch (error) { 62 | errorFound = true; 63 | } 64 | 65 | assert.isTrue(errorFound); 66 | }); 67 | 68 | it('extend no input', function () { 69 | let errorFound = false; 70 | try { 71 | simpleOracleDB.extend(); 72 | } catch (error) { 73 | errorFound = true; 74 | } 75 | 76 | assert.isTrue(errorFound); 77 | }); 78 | }); 79 | 80 | describe('createPool tests', function () { 81 | it('createPool valid', function (done) { 82 | let eventTriggered = false; 83 | simpleOracleDB.once('pool-created', function (pool) { 84 | assert.isDefined(pool); 85 | assert.isTrue(pool.simplified); 86 | eventTriggered = true; 87 | }); 88 | 89 | oracledb.createPool(function (error, pool) { 90 | assert.isNull(error); 91 | assert.isDefined(pool); 92 | assert.isTrue(pool.simplified); 93 | 94 | assert.isTrue(eventTriggered); 95 | 96 | simpleOracleDB.once('pool-released', function (releasedPool) { 97 | assert.isTrue(releasedPool === pool); 98 | 99 | done(); 100 | }); 101 | 102 | pool.close(); 103 | }); 104 | }); 105 | 106 | it('createPool valid promise', function (done) { 107 | let eventTriggered = false; 108 | simpleOracleDB.once('pool-created', function (pool) { 109 | assert.isDefined(pool); 110 | assert.isTrue(pool.simplified); 111 | eventTriggered = true; 112 | }); 113 | 114 | global.Promise = PromiseLib; 115 | 116 | oracledb.createPool().then(function (pool) { 117 | assert.isDefined(pool); 118 | assert.isTrue(pool.simplified); 119 | 120 | assert.isTrue(eventTriggered); 121 | 122 | simpleOracleDB.once('pool-released', function (releasedPool) { 123 | assert.isTrue(releasedPool === pool); 124 | 125 | done(); 126 | }); 127 | 128 | pool.close(); 129 | }, function () { 130 | assert.fail(); 131 | }); 132 | }); 133 | 134 | it('createPool error', function (done) { 135 | oracledb.createPool(true, function (error, pool) { 136 | assert.isDefined(error); 137 | assert.isUndefined(pool); 138 | 139 | done(); 140 | }); 141 | }); 142 | 143 | it('createPool error promise', function (done) { 144 | global.Promise = PromiseLib; 145 | 146 | oracledb.createPool({}).then(function () { 147 | assert.fail(); 148 | }, function (error) { 149 | assert.isDefined(error); 150 | 151 | done(); 152 | }); 153 | }); 154 | 155 | it('createPool promise not supported', function () { 156 | delete global.Promise; 157 | 158 | let errorFound = false; 159 | try { 160 | oracledb.createPool(); 161 | } catch (error) { 162 | errorFound = true; 163 | } 164 | 165 | assert.isTrue(errorFound); 166 | 167 | global.Promise = PromiseLib; 168 | }); 169 | }); 170 | 171 | describe('getConnection tests', function () { 172 | it('getConnection valid', function (done) { 173 | let eventTriggered = false; 174 | simpleOracleDB.once('connection-created', function (connection) { 175 | assert.isDefined(connection); 176 | assert.isTrue(connection.simplified); 177 | eventTriggered = true; 178 | }); 179 | 180 | oracledb.getConnection({}, function (error, connection) { 181 | assert.isNull(error); 182 | assert.isDefined(connection); 183 | assert.isTrue(connection.simplified); 184 | 185 | assert.isTrue(eventTriggered); 186 | 187 | simpleOracleDB.once('connection-released', function (releasedConnection) { 188 | assert.isTrue(releasedConnection === connection); 189 | 190 | done(); 191 | }); 192 | 193 | connection.release(); 194 | }); 195 | }); 196 | 197 | it('getConnection valid promise', function (done) { 198 | let eventTriggered = false; 199 | simpleOracleDB.once('connection-created', function (connection) { 200 | assert.isDefined(connection); 201 | assert.isTrue(connection.simplified); 202 | eventTriggered = true; 203 | }); 204 | 205 | oracledb.getConnection({}).then(function (connection) { 206 | assert.isDefined(connection); 207 | assert.isTrue(connection.simplified); 208 | 209 | assert.isTrue(eventTriggered); 210 | 211 | simpleOracleDB.once('connection-released', function (releasedConnection) { 212 | assert.isTrue(releasedConnection === connection); 213 | 214 | done(); 215 | }); 216 | 217 | connection.release(); 218 | }, function () { 219 | assert.fail(); 220 | }); 221 | }); 222 | }); 223 | 224 | describe('diagnosticInfo tests', function () { 225 | it('diagnosticInfo', function () { 226 | assert.isDefined(simpleOracleDB.diagnosticInfo); 227 | assert.isDefined(simpleOracleDB.diagnosticInfo.pool); 228 | assert.isDefined(simpleOracleDB.diagnosticInfo.connection); 229 | }); 230 | }); 231 | 232 | describe('enableDiagnosticInfo tests', function () { 233 | it('enableDiagnosticInfo', function () { 234 | assert.isDefined(simpleOracleDB.enableDiagnosticInfo); 235 | 236 | simpleOracleDB.enableDiagnosticInfo = true; 237 | assert.isTrue(simpleOracleDB.enableDiagnosticInfo); 238 | 239 | simpleOracleDB.enableDiagnosticInfo = false; 240 | assert.isFalse(simpleOracleDB.enableDiagnosticInfo); 241 | assert.equal(Object.keys(simpleOracleDB.diagnosticInfo.pool).length, 0); 242 | assert.equal(Object.keys(simpleOracleDB.diagnosticInfo.connection).length, 0); 243 | }); 244 | }); 245 | }); 246 | -------------------------------------------------------------------------------- /test/spec/promise-helper-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const PromiseLib = global.Promise || require('promiscuous'); 6 | const promiseHelper = require('../../lib/promise-helper'); 7 | 8 | describe('PromiseHelper Tests', function () { 9 | describe('isPromise', function () { 10 | it('undefined', function () { 11 | const output = promiseHelper.isPromise(); 12 | assert.isFalse(output); 13 | }); 14 | 15 | it('null', function () { 16 | const output = promiseHelper.isPromise(null); 17 | assert.isFalse(output); 18 | }); 19 | 20 | it('not an object', function () { 21 | const output = promiseHelper.isPromise(1); 22 | assert.isFalse(output); 23 | }); 24 | 25 | it('missing then', function () { 26 | const output = promiseHelper.isPromise({ 27 | catch: promiseHelper.noop 28 | }); 29 | assert.isFalse(output); 30 | }); 31 | 32 | it('missing catch', function () { 33 | const output = promiseHelper.isPromise({ 34 | then: promiseHelper.noop 35 | }); 36 | assert.isFalse(output); 37 | }); 38 | 39 | it('then not a function', function () { 40 | const output = promiseHelper.isPromise({ 41 | then: true, 42 | catch: promiseHelper.noop 43 | }); 44 | assert.isFalse(output); 45 | }); 46 | 47 | it('catch not a function', function () { 48 | const output = promiseHelper.isPromise({ 49 | then: promiseHelper.noop, 50 | catch: true 51 | }); 52 | assert.isFalse(output); 53 | }); 54 | 55 | it('valid', function () { 56 | const output = promiseHelper.isPromise({ 57 | then: promiseHelper.noop, 58 | catch: promiseHelper.noop 59 | }); 60 | assert.isTrue(output); 61 | }); 62 | }); 63 | 64 | describe('runAsync', function () { 65 | it('async valid', function (done) { 66 | promiseHelper.runAsync(function (callback) { 67 | callback(null, 'test output'); 68 | }, function (error, output) { 69 | assert.isNull(error); 70 | assert.strictEqual(output, 'test output'); 71 | 72 | done(); 73 | }); 74 | }); 75 | 76 | it('async error', function (done) { 77 | promiseHelper.runAsync(function (callback) { 78 | callback(new Error('test')); 79 | }, function (error, output) { 80 | assert.isDefined(error); 81 | assert.strictEqual(error.message, 'test'); 82 | assert.isUndefined(output); 83 | 84 | done(); 85 | }); 86 | }); 87 | 88 | it('promise valid', function (done) { 89 | promiseHelper.runAsync(function () { 90 | return new PromiseLib(function (resolve) { 91 | resolve('test output'); 92 | }); 93 | }, function (error, output) { 94 | assert.isNull(error); 95 | assert.strictEqual(output, 'test output'); 96 | 97 | done(); 98 | }); 99 | }); 100 | 101 | it('promise error', function (done) { 102 | promiseHelper.runAsync(function () { 103 | return new PromiseLib(function (resolve, reject) { 104 | reject(new Error('test')); 105 | }); 106 | }, function (error, output) { 107 | assert.isDefined(error); 108 | assert.strictEqual(error.message, 'test'); 109 | assert.isUndefined(output); 110 | 111 | done(); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('runPromise', function () { 117 | it('Promise not supported', function () { 118 | delete global.Promise; 119 | 120 | let errorFound = false; 121 | 122 | try { 123 | promiseHelper.runPromise(function () { 124 | assert.fail(); 125 | }); 126 | } catch (error) { 127 | errorFound = true; 128 | } 129 | 130 | global.Promise = PromiseLib; 131 | 132 | assert.isTrue(errorFound); 133 | }); 134 | 135 | it('valid', function (done) { 136 | global.Promise = PromiseLib; 137 | 138 | const promise = promiseHelper.runPromise(function (callback) { 139 | assert.isFunction(callback); 140 | 141 | callback(null, { 142 | test: true 143 | }); 144 | }); 145 | 146 | promise.then(function (output) { 147 | assert.deepEqual(output, { 148 | test: true 149 | }); 150 | 151 | done(); 152 | }, function () { 153 | assert.fail(); 154 | }); 155 | }); 156 | 157 | it('error then', function (done) { 158 | global.Promise = PromiseLib; 159 | 160 | const promise = promiseHelper.runPromise(function (callback) { 161 | assert.isFunction(callback); 162 | 163 | callback(new Error('test')); 164 | }); 165 | 166 | promise.then(function () { 167 | assert.fail(); 168 | }, function (error) { 169 | assert.isDefined(error); 170 | 171 | done(); 172 | }); 173 | }); 174 | 175 | it('error catch', function (done) { 176 | global.Promise = PromiseLib; 177 | 178 | const promise = promiseHelper.runPromise(function (callback) { 179 | assert.isFunction(callback); 180 | 181 | callback(new Error('test')); 182 | }); 183 | 184 | promise.then(function () { 185 | assert.fail(); 186 | }).catch(function (error) { 187 | assert.isDefined(error); 188 | 189 | done(); 190 | }); 191 | }); 192 | }); 193 | 194 | describe('run', function () { 195 | const action = function (callback) { 196 | assert.isFunction(callback); 197 | 198 | setTimeout(callback, 0); 199 | }; 200 | 201 | it('callback provided', function (done) { 202 | const promise = promiseHelper.run(action, done); 203 | 204 | assert.isUndefined(promise); 205 | }); 206 | 207 | it('no callback, using promise', function (done) { 208 | delete global.Promise; 209 | 210 | global.Promise = PromiseLib; 211 | 212 | const promise = promiseHelper.run(action); 213 | 214 | promise.then(function () { 215 | done(); 216 | }).catch(function () { 217 | assert.fail(); 218 | }); 219 | }); 220 | 221 | it('no callback, no promise support', function () { 222 | delete global.Promise; 223 | 224 | let errorFound = false; 225 | 226 | try { 227 | promiseHelper.run(action); 228 | } catch (error) { 229 | errorFound = true; 230 | } 231 | 232 | global.Promise = PromiseLib; 233 | 234 | assert.isTrue(errorFound); 235 | }); 236 | 237 | it('no callback, no promise support with force', function () { 238 | delete global.Promise; 239 | 240 | let errorFound = false; 241 | 242 | try { 243 | promiseHelper.run(action, undefined, true); 244 | } catch (error) { 245 | errorFound = true; 246 | } 247 | 248 | global.Promise = PromiseLib; 249 | 250 | assert.isTrue(errorFound); 251 | }); 252 | }); 253 | 254 | describe('promisify', function () { 255 | it('no options', function () { 256 | global.Promise = PromiseLib; 257 | 258 | const func = promiseHelper.promisify(function (num, callback) { 259 | assert.equal(num, 15); 260 | 261 | callback(null, 20); 262 | }); 263 | 264 | func(15).then(function (result) { 265 | assert.equal(result, 20); 266 | }).catch(function () { 267 | assert.fail(); 268 | }); 269 | }); 270 | 271 | it('no promise support, no default, no force', function () { 272 | const func = promiseHelper.promisify(function (num, callback) { 273 | assert.equal(num, 15); 274 | 275 | callback(null, 20); 276 | }, { 277 | force: false, 278 | defaultCallback: false 279 | }); 280 | 281 | delete global.Promise; 282 | 283 | let errorFound = false; 284 | 285 | try { 286 | func(15); 287 | } catch (error) { 288 | errorFound = true; 289 | } 290 | 291 | global.Promise = PromiseLib; 292 | 293 | assert.isTrue(errorFound); 294 | }); 295 | }); 296 | }); 297 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /lib/oracledb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = require('./emitter'); 4 | const Pool = require('./pool'); 5 | const Connection = require('./connection'); 6 | const constants = require('./constants'); 7 | const promiseHelper = require('./promise-helper'); 8 | 9 | /** 10 | * This events is triggered when a pool is created. 11 | * 12 | * @event OracleDB#pool-created 13 | * @param {Pool} pool - The pool instance 14 | */ 15 | 16 | /** 17 | * This events is triggered after a pool is released. 18 | * 19 | * @event OracleDB#pool-released 20 | * @param {Pool} pool - The pool instance 21 | */ 22 | 23 | /** 24 | * This events is triggered when a connection is created via oracledb. 25 | * 26 | * @event OracleDB#connection-created 27 | * @param {Connection} connection - The connection instance 28 | */ 29 | 30 | /** 31 | * This events is triggered when a connection is released successfully. 32 | * 33 | * @event OracleDB#connection-released 34 | * @param {Connection} connection - The connection instance 35 | */ 36 | 37 | /** 38 | * An action requested by the pool to be invoked. 39 | * 40 | * @callback ConnectionAction 41 | * @param {Connection} connection - A valid connection to be used by the action 42 | * @param {AsyncCallback} callback - The callback to invoke at the end of the action 43 | */ 44 | 45 | /*jslint debug: true */ 46 | /*istanbul ignore next*/ 47 | /** 48 | * This class holds all the extended capabilities added the oracledb. 49 | * 50 | * @author Sagie Gur-Ari 51 | * @class OracleDB 52 | * @public 53 | */ 54 | function OracleDB() { 55 | //should not be called 56 | } 57 | /*jslint debug: false */ 58 | 59 | /** 60 | * Marker property. 61 | * 62 | * @member {Boolean} 63 | * @alias OracleDB.simplified 64 | * @memberof! OracleDB 65 | * @public 66 | */ 67 | OracleDB.prototype.simplified = true; 68 | 69 | /*eslint-disable valid-jsdoc*/ 70 | //jscs:disable jsDoc 71 | /** 72 | * Wraps the original oracledb getConnection in order to provide an extended connection object. 73 | * 74 | * @function 75 | * @memberof! OracleDB 76 | * @public 77 | * @param {Object} connectionAttributes - The connection attributes object 78 | * @param {AsyncCallback} [callback] - Invoked with an error or the oracle connection instance 79 | * @returns {Promise} In case of no callback provided in input, this function will return a promise 80 | */ 81 | OracleDB.prototype.getConnection = function (connectionAttributes, callback) { 82 | const self = this; 83 | 84 | const onWrapperConnection = function (error, connection) { 85 | /*istanbul ignore else*/ 86 | if ((!error) && connection) { 87 | self.emit('connection-created', connection); 88 | 89 | connection.once('release', function onRelease() { 90 | self.emit('connection-released', connection); 91 | }); 92 | } 93 | 94 | callback(error, connection); 95 | }; 96 | 97 | self.baseGetConnection(connectionAttributes, Connection.wrapOnConnection(onWrapperConnection)); 98 | }; 99 | //jscs:enable jsDoc 100 | /*eslint-enable valid-jsdoc*/ 101 | 102 | //add promise support 103 | OracleDB.prototype.getConnection = promiseHelper.promisify(OracleDB.prototype.getConnection); 104 | 105 | /*eslint-disable valid-jsdoc*/ 106 | //jscs:disable jsDoc 107 | /** 108 | * Wraps the original oracledb createPool in order to provide an extended pool object. 109 | * 110 | * @function 111 | * @memberof! OracleDB 112 | * @public 113 | * @param {Object} poolAttributes - The connection pool attributes object (see https://github.com/oracle/node-oracledb/blob/master/doc/api.md#createpool for more attributes) 114 | * @param {Number} [poolAttributes.retryCount=10] - The max amount of retries to get a connection from the pool in case of any error 115 | * @param {Number} [poolAttributes.retryInterval=250] - The interval in millies between get connection retry attempts 116 | * @param {Boolean} [poolAttributes.runValidationSQL=true] - True to ensure the connection returned is valid by running a test ping or validation SQL 117 | * @param {Boolean} [poolAttributes.usePingValidation=true] - If runValidationSQL, this flag will define if validation should first attempt to use connection.ping instead of running a SQL 118 | * @param {String} [poolAttributes.validationSQL=SELECT 1 FROM DUAL] - The test SQL to invoke before returning a connection to validate the connection is open 119 | * @param {AsyncCallback} [callback] - Invoked with an error or the oracle connection pool instance 120 | * @returns {Promise} In case of no callback provided in input, this function will return a promise 121 | */ 122 | OracleDB.prototype.createPool = function (poolAttributes, callback) { 123 | const self = this; 124 | 125 | if ((!callback) && poolAttributes && (typeof poolAttributes === 'function')) { 126 | callback = poolAttributes; 127 | poolAttributes = null; 128 | } 129 | 130 | self.baseCreatePool(poolAttributes, function onPool(error, pool) { 131 | if ((!error) && pool) { 132 | Pool.extend(pool, poolAttributes); 133 | 134 | self.emit('pool-created', pool); 135 | 136 | pool.once('release', function onRelease() { 137 | self.emit('pool-released', pool); 138 | }); 139 | } 140 | 141 | callback(error, pool); 142 | }); 143 | }; 144 | //jscs:enable jsDoc 145 | /*eslint-enable valid-jsdoc*/ 146 | 147 | //add promise support 148 | OracleDB.prototype.createPool = promiseHelper.promisify(OracleDB.prototype.createPool); 149 | 150 | /*eslint-disable valid-jsdoc*/ 151 | //jscs:disable jsDoc 152 | /** 153 | * This function invokes the provided action (function) with a valid connection object and a callback.
154 | * The action can use the provided connection to run any connection operation/s (execute/query/transaction/...) and after finishing it 155 | * must call the callback with an error (if any) and result.
156 | * For promise support, the action can simply return a promise instead of calling the provided callback.
157 | * This function will ensure the connection is released properly and only afterwards will call the provided callback with the action error/result.
158 | * This function basically will remove the need of caller code to get and release a connection and focus on the actual database operation logic.
159 | * It is recommanded to create a pool and use the pool.run instead of oracledb.run as this function will create a new connection (and release it) for each invocation, 160 | * on the other hand, pool.run will reuse pool managed connections which will result in improved performance. 161 | * 162 | * @function 163 | * @memberof! OracleDB 164 | * @public 165 | * @param {Object} connectionAttributes - The connection attributes object (see oracledb.getConnection for more details) 166 | * @param {Boolean} [connectionAttributes.ignoreReleaseErrors=false] - If true, errors during connection.release() invoked internally will be ignored 167 | * @param {Object} [connectionAttributes.releaseOptions={force: false}] - The connection.release options (see connection.release for more info) 168 | * @param {Boolean} [connectionAttributes.releaseOptions.force=false] - If force=true the connection.break will be called before trying to release to ensure all running activities are aborted 169 | * @param {ConnectionAction} action - An action requested to be invoked. 170 | * @param {AsyncCallback} [callback] - Invoked with an error or the oracle connection instance 171 | * @returns {Promise} In case of no callback provided in input, this function will return a promise 172 | * @example 173 | * ```js 174 | * oracledb.run({ 175 | * user: process.env.ORACLE_USER, 176 | * password: process.env.ORACLE_PASSWORD, 177 | * connectString: process.env.ORACLE_CONNECTION_STRING 178 | * }, function onConnection(connection, callback) { 179 | * //run some query and the output will be available in the 'run' callback 180 | * connection.query('SELECT department_id, department_name FROM departments WHERE manager_id < :id', [110], callback); 181 | * }, function onActionDone(error, result) { 182 | * //do something with the result/error 183 | * }); 184 | * 185 | * oracledb.run({ 186 | * user: process.env.ORACLE_USER, 187 | * password: process.env.ORACLE_PASSWORD, 188 | * connectString: process.env.ORACLE_CONNECTION_STRING 189 | * }, function (connection, callback) { 190 | * //run some database operations in a transaction 191 | * connection.transaction([ 192 | * function firstAction(callback) { 193 | * connection.insert(...., callback); 194 | * }, 195 | * function secondAction(callback) { 196 | * connection.update(...., callback); 197 | * } 198 | * ], { 199 | * sequence: true 200 | * }, callback); //at end of transaction, call the oracledb provided callback 201 | * }, function onActionDone(error, result) { 202 | * //do something with the result/error 203 | * }); 204 | * 205 | * //full promise support for both oracledb.run and the action 206 | * oracledb.run({ 207 | * user: process.env.ORACLE_USER, 208 | * password: process.env.ORACLE_PASSWORD, 209 | * connectString: process.env.ORACLE_CONNECTION_STRING 210 | * }, function (connection) { 211 | * //run some database operations in a transaction and return a promise 212 | * return connection.transaction([ 213 | * function firstAction() { 214 | * return connection.insert(....); //returns a promise 215 | * }, 216 | * function secondAction() { 217 | * return connection.update(....); //returns a promise 218 | * } 219 | * ]); 220 | * }).then(function (result) { 221 | * //do something with the result 222 | * }); 223 | * ``` 224 | */ 225 | OracleDB.prototype.run = function (connectionAttributes, action, callback) { 226 | if (connectionAttributes && (typeof connectionAttributes === 'object') && action && (typeof action === 'function')) { 227 | const releaseOptions = connectionAttributes.releaseOptions || {}; 228 | 229 | const simpleOracleDB = require('./simple-oracledb'); 230 | const actionRunner = simpleOracleDB.createOnConnectionCallback(action, connectionAttributes, releaseOptions, callback); 231 | this.getConnection(connectionAttributes, actionRunner); 232 | } else { 233 | throw new Error('Illegal input provided.'); 234 | } 235 | }; 236 | //jscs:enable jsDoc 237 | /*eslint-enable valid-jsdoc*/ 238 | 239 | //add promise support 240 | OracleDB.prototype.run = promiseHelper.promisify(OracleDB.prototype.run, { 241 | callbackMinIndex: 2 242 | }); 243 | 244 | module.exports = { 245 | /** 246 | * Extends the provided oracledb instance. 247 | * 248 | * @function 249 | * @memberof! OracleDB 250 | * @public 251 | * @param {Object} oracledb - The oracledb instance 252 | */ 253 | extend: function extend(oracledb) { 254 | if (oracledb && (!oracledb.simplified)) { 255 | //update type meta info 256 | if (oracledb.BLOB !== undefined) { 257 | constants.blobType = oracledb.BLOB; 258 | } 259 | if (oracledb.CLOB !== undefined) { 260 | constants.clobType = oracledb.CLOB; 261 | } 262 | if (oracledb.DATE !== undefined) { 263 | constants.dateType = oracledb.DATE; 264 | } 265 | if (oracledb.NUMBER !== undefined) { 266 | constants.numberType = oracledb.NUMBER; 267 | } 268 | if (oracledb.STRING !== undefined) { 269 | constants.stringType = oracledb.STRING; 270 | } 271 | if (oracledb.BIND_OUT !== undefined) { 272 | constants.bindOut = oracledb.BIND_OUT; 273 | } 274 | 275 | const properties = Object.keys(OracleDB.prototype); 276 | 277 | properties.forEach(function addProperty(property) { 278 | if (typeof oracledb[property] === 'function') { 279 | oracledb['base' + property.charAt(0).toUpperCase() + property.slice(1)] = oracledb[property]; 280 | } 281 | 282 | oracledb[property] = OracleDB.prototype[property]; 283 | }); 284 | 285 | emitter(oracledb); 286 | } 287 | } 288 | }; 289 | -------------------------------------------------------------------------------- /test/spec/record-writer-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const EventEmitter = require('events').EventEmitter; 6 | const recordWriter = require('../../lib/record-writer'); 7 | const utils = require('../helpers/utils'); 8 | 9 | describe('RecordWriter Tests', function () { 10 | describe('write Tests', function () { 11 | it('string test', function (done) { 12 | const writable = new EventEmitter(); 13 | writable.end = function (data, encoding, callback) { 14 | assert.equal(data, 'TEST STRING DATA'); 15 | assert.equal(encoding, 'utf8'); 16 | assert.isFunction(callback); 17 | 18 | callback(); 19 | }; 20 | 21 | recordWriter.write({ 22 | LOBCOL1: [ 23 | writable 24 | ] 25 | }, { 26 | BIND1: 'TEST STRING DATA' 27 | }, function (error) { 28 | assert.isNull(error); 29 | assert.equal(0, writable.listeners('error').length); 30 | 31 | done(); 32 | }); 33 | }); 34 | 35 | it('Buffer test', function (done) { 36 | const writable = new EventEmitter(); 37 | writable.end = function (data, callback) { 38 | assert.deepEqual(data, utils.createBuffer('TEST STRING DATA')); 39 | assert.isFunction(callback); 40 | 41 | callback(); 42 | }; 43 | 44 | recordWriter.write({ 45 | LOBCOL1: [ 46 | writable 47 | ] 48 | }, { 49 | BIND1: utils.createBuffer('TEST STRING DATA') 50 | }, function (error) { 51 | assert.isNull(error); 52 | assert.equal(0, writable.listeners('error').length); 53 | 54 | done(); 55 | }); 56 | }); 57 | 58 | it('null data test', function (done) { 59 | const writable = new EventEmitter(); 60 | writable.end = function () { 61 | assert.fail(); 62 | }; 63 | 64 | recordWriter.write({ 65 | LOBCOL1: [ 66 | writable 67 | ] 68 | }, { 69 | BIND1: null 70 | }, function (error) { 71 | assert.isNull(error); 72 | assert.equal(0, writable.listeners('error').length); 73 | 74 | done(); 75 | }); 76 | }); 77 | 78 | it('undefined data test', function (done) { 79 | const writable = new EventEmitter(); 80 | writable.end = function () { 81 | assert.fail(); 82 | }; 83 | 84 | recordWriter.write({ 85 | LOBCOL1: [ 86 | writable 87 | ] 88 | }, { 89 | BIND1: null 90 | }, function (error) { 91 | assert.isNull(error); 92 | assert.equal(0, writable.listeners('error').length); 93 | 94 | done(); 95 | }); 96 | }); 97 | 98 | it('empty array outbindings test', function (done) { 99 | recordWriter.write({ 100 | LOBCOL1: [] 101 | }, { 102 | BIND1: 'TEST STRING DATA' 103 | }, function (error) { 104 | assert.isNull(error); 105 | 106 | done(); 107 | }); 108 | }); 109 | 110 | it('undefined outbindings test', function (done) { 111 | recordWriter.write({}, { 112 | BIND1: 'TEST STRING DATA' 113 | }, function (error) { 114 | assert.isNull(error); 115 | 116 | done(); 117 | }); 118 | }); 119 | 120 | it('multiple array outbindings test', function (done) { 121 | recordWriter.write({ 122 | LOBCOL1: [ 123 | {}, 124 | {} 125 | ] 126 | }, { 127 | BIND1: 'TEST STRING DATA' 128 | }, function (error) { 129 | assert.isNull(error); 130 | 131 | done(); 132 | }); 133 | }); 134 | 135 | it('multiple columns test', function (done) { 136 | let lobsWritten = 0; 137 | 138 | const writable1 = new EventEmitter(); 139 | writable1.end = function (data, encoding, callback) { 140 | assert.equal(data, 'TEST STRING DATA'); 141 | assert.equal(encoding, 'utf8'); 142 | assert.isFunction(callback); 143 | 144 | lobsWritten++; 145 | 146 | callback(); 147 | }; 148 | 149 | const writable2 = new EventEmitter(); 150 | writable2.end = function (data, callback) { 151 | assert.equal(data, 'TEST STRING DATA'); 152 | assert.isFunction(callback); 153 | 154 | lobsWritten++; 155 | 156 | callback(); 157 | }; 158 | 159 | recordWriter.write({ 160 | BIND1: [writable1], 161 | BIND2: [writable1], 162 | BIND3: [writable2], 163 | BIND4: [writable2] 164 | }, { 165 | BIND1: 'TEST STRING DATA', 166 | BIND2: 'TEST STRING DATA', 167 | BIND3: utils.createBuffer('TEST STRING DATA'), 168 | BIND4: utils.createBuffer('TEST STRING DATA') 169 | }, function (error) { 170 | assert.isNull(error); 171 | assert.equal(0, writable1.listeners('error').length); 172 | assert.equal(lobsWritten, 4); 173 | 174 | done(); 175 | }); 176 | }); 177 | }); 178 | 179 | describe('writeMultiple Tests', function () { 180 | it('string test', function (done) { 181 | const writable = new EventEmitter(); 182 | writable.end = function (data, encoding, callback) { 183 | assert.equal(data, 'TEST STRING DATA'); 184 | assert.equal(encoding, 'utf8'); 185 | assert.isFunction(callback); 186 | 187 | callback(); 188 | }; 189 | 190 | recordWriter.writeMultiple({ 191 | LOBCOL1: [ 192 | writable, 193 | writable 194 | ] 195 | }, { 196 | BIND1: 'TEST STRING DATA' 197 | }, function (error) { 198 | assert.isNull(error); 199 | assert.equal(0, writable.listeners('error').length); 200 | 201 | done(); 202 | }); 203 | }); 204 | 205 | it('Buffer test', function (done) { 206 | const writable = new EventEmitter(); 207 | writable.end = function (data, callback) { 208 | assert.deepEqual(data, utils.createBuffer('TEST STRING DATA')); 209 | assert.isFunction(callback); 210 | 211 | callback(); 212 | }; 213 | 214 | recordWriter.writeMultiple({ 215 | LOBCOL1: [ 216 | writable, 217 | writable 218 | ] 219 | }, { 220 | BIND1: utils.createBuffer('TEST STRING DATA') 221 | }, function (error) { 222 | assert.isNull(error); 223 | assert.equal(0, writable.listeners('error').length); 224 | 225 | done(); 226 | }); 227 | }); 228 | 229 | it('null data test', function (done) { 230 | const writable = new EventEmitter(); 231 | writable.end = function () { 232 | assert.fail(); 233 | }; 234 | 235 | recordWriter.writeMultiple({ 236 | LOBCOL1: [ 237 | writable, 238 | writable 239 | ] 240 | }, { 241 | BIND1: null 242 | }, function (error) { 243 | assert.isNull(error); 244 | assert.equal(0, writable.listeners('error').length); 245 | 246 | done(); 247 | }); 248 | }); 249 | 250 | it('undefined data test', function (done) { 251 | const writable = new EventEmitter(); 252 | writable.end = function () { 253 | assert.fail(); 254 | }; 255 | 256 | recordWriter.writeMultiple({ 257 | LOBCOL1: [ 258 | writable, 259 | writable 260 | ] 261 | }, { 262 | BIND1: null 263 | }, function (error) { 264 | assert.isNull(error); 265 | assert.equal(0, writable.listeners('error').length); 266 | 267 | done(); 268 | }); 269 | }); 270 | 271 | it('empty array outbindings test', function (done) { 272 | recordWriter.writeMultiple({ 273 | LOBCOL1: [] 274 | }, { 275 | BIND1: 'TEST STRING DATA' 276 | }, function (error) { 277 | assert.isNull(error); 278 | 279 | done(); 280 | }); 281 | }); 282 | 283 | it('undefined outbindings test', function (done) { 284 | recordWriter.writeMultiple({}, { 285 | BIND1: 'TEST STRING DATA' 286 | }, function (error) { 287 | assert.isNull(error); 288 | 289 | done(); 290 | }); 291 | }); 292 | 293 | it('multiple array outbindings test', function (done) { 294 | recordWriter.writeMultiple({ 295 | LOBCOL1: [ 296 | {}, 297 | {} 298 | ] 299 | }, { 300 | BIND1: 'TEST STRING DATA' 301 | }, function (error) { 302 | assert.isNull(error); 303 | 304 | done(); 305 | }); 306 | }); 307 | 308 | it('multiple columns test', function (done) { 309 | let lobsWritten = 0; 310 | 311 | const writable1 = new EventEmitter(); 312 | writable1.end = function (data, encoding, callback) { 313 | assert.equal(data, 'TEST STRING DATA'); 314 | assert.equal(encoding, 'utf8'); 315 | assert.isFunction(callback); 316 | 317 | lobsWritten++; 318 | 319 | callback(); 320 | }; 321 | 322 | const writable2 = new EventEmitter(); 323 | writable2.end = function (data, callback) { 324 | assert.equal(data, 'TEST STRING DATA'); 325 | assert.isFunction(callback); 326 | 327 | lobsWritten++; 328 | 329 | callback(); 330 | }; 331 | 332 | recordWriter.writeMultiple({ 333 | BIND1: [ 334 | writable1, 335 | writable1, 336 | writable1, 337 | writable1 338 | ], 339 | BIND2: [ 340 | writable1, 341 | writable1, 342 | writable1, 343 | writable1 344 | ], 345 | BIND3: [ 346 | writable2, 347 | writable2, 348 | writable2, 349 | writable2 350 | ], 351 | BIND4: [ 352 | writable2, 353 | writable2, 354 | writable2, 355 | writable2 356 | ] 357 | }, { 358 | BIND1: 'TEST STRING DATA', 359 | BIND2: 'TEST STRING DATA', 360 | BIND3: utils.createBuffer('TEST STRING DATA'), 361 | BIND4: utils.createBuffer('TEST STRING DATA') 362 | }, function (error) { 363 | assert.isNull(error); 364 | assert.equal(0, writable1.listeners('error').length); 365 | assert.equal(lobsWritten, 16); 366 | 367 | done(); 368 | }); 369 | }); 370 | }); 371 | }); 372 | -------------------------------------------------------------------------------- /lib/simple-oracledb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This events is triggered when a pool is created. 5 | * 6 | * @event SimpleOracleDB#pool-created 7 | * @param {Pool} pool - The pool instance 8 | */ 9 | 10 | /** 11 | * This events is triggered after a pool is released. 12 | * 13 | * @event SimpleOracleDB#pool-released 14 | * @param {Pool} pool - The pool instance 15 | */ 16 | 17 | /** 18 | * This events is triggered when a connection is created via oracledb. 19 | * 20 | * @event SimpleOracleDB#connection-created 21 | * @param {Connection} connection - The connection instance 22 | */ 23 | 24 | /** 25 | * This events is triggered when a connection is released successfully. 26 | * 27 | * @event SimpleOracleDB#connection-released 28 | * @param {Connection} connection - The connection instance 29 | */ 30 | 31 | /** 32 | * Invoked when an async operation has finished. 33 | * 34 | * @callback AsyncCallback 35 | * @param {Error} [error] - Any possible error 36 | * @param {Object} [output] - The operation output 37 | */ 38 | 39 | const util = require('util'); 40 | const EventEmitterEnhancer = require('event-emitter-enhancer'); 41 | const OracleDB = require('./oracledb'); 42 | const Pool = require('./pool'); 43 | const Connection = require('./connection'); 44 | const extensions = require('./extensions'); 45 | const Monitor = require('./monitor'); 46 | const funcs = require('funcs-js'); 47 | const promiseHelper = require('./promise-helper'); 48 | 49 | /** 50 | * Simple oracledb enables to extend the oracledb main object, oracledb pool and oracledb connection.
51 | * See extend function for more info. 52 | * 53 | * @author Sagie Gur-Ari 54 | * @class SimpleOracleDB 55 | * @public 56 | */ 57 | function SimpleOracleDB() { 58 | //call super constructor 59 | EventEmitterEnhancer.EnhancedEventEmitter.call(this); 60 | 61 | const monitor = Monitor.create(this); 62 | 63 | /*eslint-disable func-name-matching*/ 64 | Object.defineProperty(this, 'diagnosticInfo', { 65 | /** 66 | * Getter for the diagnostic info data. 67 | * 68 | * @function 69 | * @memberof! Info 70 | * @private 71 | * @returns {Object} The diagnostic info data 72 | */ 73 | get: function getDiagnosticInfo() { 74 | /** 75 | * The pool/connection diagnostics info.
76 | * This includes info of all live pools (including live time and create time) and all live connections (including parent pool if any, live time, create time and last SQL) 77 | * 78 | * @memberof! SimpleOracleDB 79 | * @member {Object} 80 | * @alias SimpleOracleDB.diagnosticInfo 81 | * @public 82 | */ 83 | const diagnosticInfo = monitor.diagnosticInfo; 84 | 85 | return diagnosticInfo; 86 | } 87 | }); 88 | 89 | Object.defineProperty(this, 'enableDiagnosticInfo', { 90 | /** 91 | * Getter for the diagnostic info enabled flag. 92 | * 93 | * @function 94 | * @memberof! Info 95 | * @private 96 | * @returns {Boolean} The diagnostic info enabled flag 97 | */ 98 | get: function isEnabled() { 99 | /** 100 | * True if the monitoring is enabled and it will listen and store pool/connection diagnostics information.
101 | * By default this is set to false. 102 | * 103 | * @memberof! SimpleOracleDB 104 | * @member {Boolean} 105 | * @alias SimpleOracleDB.enableDiagnosticInfo 106 | * @public 107 | */ 108 | const enableDiagnosticInfo = monitor.enabled; 109 | 110 | return enableDiagnosticInfo; 111 | }, 112 | /** 113 | * Setter for the diagnostic info enabled flag. 114 | * 115 | * @function 116 | * @memberof! Info 117 | * @private 118 | * @param {Boolean} value - The diagnostic info enabled flag 119 | */ 120 | set: function setEnabled(value) { 121 | monitor.enabled = value; 122 | } 123 | }); 124 | /*eslint-enable func-name-matching*/ 125 | } 126 | 127 | //setup SimpleOracleDB as an event emitter 128 | util.inherits(SimpleOracleDB, EventEmitterEnhancer.EnhancedEventEmitter); 129 | 130 | /*eslint-disable valid-jsdoc*/ 131 | //jscs:disable jsDoc 132 | /** 133 | * Extends the oracledb library which from that point will allow fetching the modified 134 | * connection objects via oracledb.getConnection or via pool.getConnection 135 | * 136 | * @function 137 | * @memberof! SimpleOracleDB 138 | * @public 139 | * @param {oracledb} oracledb - The oracledb library 140 | * @example 141 | * ```js 142 | * //load the oracledb library 143 | * const oracledb = require('oracledb'); 144 | * 145 | * //load the simple oracledb 146 | * const SimpleOracleDB = require('simple-oracledb'); 147 | * 148 | * //modify the original oracledb library 149 | * SimpleOracleDB.extend(oracledb); 150 | * ``` 151 | * 152 | * @also 153 | * 154 | * Extends the oracledb pool instance which from that point will allow fetching the modified 155 | * connection objects via pool.getConnection 156 | * 157 | * @function 158 | * @memberof! SimpleOracleDB 159 | * @public 160 | * @param {Pool} pool - The oracledb pool instance 161 | * @example 162 | * ```js 163 | * //load the simple oracledb 164 | * const SimpleOracleDB = require('simple-oracledb'); 165 | * 166 | * function myFunction(pool) { 167 | * //modify the original oracledb pool instance 168 | * SimpleOracleDB.extend(pool); 169 | * 170 | * //from this point connections fetched via pool.getConnection(...) 171 | * //have access to additional functionality. 172 | * pool.getConnection(function onConnection(error, connection) { 173 | * if (error) { 174 | * //handle error 175 | * } else { 176 | * //work with new capabilities or original oracledb capabilities 177 | * connection.query(...); 178 | * } 179 | * }); 180 | * } 181 | * ``` 182 | * 183 | * @also 184 | * 185 | * Extends the oracledb connection instance which from that point will allow access to all 186 | * the extended capabilities of this library. 187 | * 188 | * @function 189 | * @memberof! SimpleOracleDB 190 | * @public 191 | * @param {Connection} connection - The oracledb connection instance 192 | * @example 193 | * ```js 194 | * //load the simple oracledb 195 | * const SimpleOracleDB = require('simple-oracledb'); 196 | * 197 | * function doSomething(connection, callback) { 198 | * //modify the original oracledb connection instance 199 | * SimpleOracleDB.extend(connection); 200 | * 201 | * //from this point the connection has access to additional functionality as well as the original oracledb capabilities. 202 | * connection.query(...); 203 | * } 204 | * ``` 205 | */ 206 | SimpleOracleDB.prototype.extend = function (oracle) { 207 | const self = this; 208 | 209 | if (oracle) { 210 | if (oracle.createPool) { 211 | if (!oracle.simplified) { 212 | OracleDB.extend(oracle); 213 | 214 | self.proxyEvents(oracle, [ 215 | 'pool-created', 216 | 'pool-released', 217 | 'connection-created', 218 | 'connection-released' 219 | ]); 220 | } 221 | } else if (oracle.getConnection) { 222 | Pool.extend(oracle); 223 | } else if (oracle.execute) { 224 | Connection.extend(oracle); 225 | } else { 226 | throw new Error('Unsupported object provided.'); 227 | } 228 | } else { 229 | throw new Error('Invalid input provided.'); 230 | } 231 | }; 232 | //jscs:enable jsDoc 233 | /*eslint-enable valid-jsdoc*/ 234 | 235 | /** 236 | * Adds an extension to all newly created objects of the requested type.
237 | * An extension, is a function which will be added to any pool or connection instance created after the extension was added.
238 | * This function enables external libraries to further extend oracledb using a very simple API and without the need to wrap the pool/connection creation functions.
239 | * Extension functions automatically get promisified unless specified differently in the optional options. 240 | * 241 | * @function 242 | * @memberof! SimpleOracleDB 243 | * @public 244 | * @param {String} type - Either 'connection' or 'pool' 245 | * @param {String} name - The function name which will be added to the object 246 | * @param {function} extension - The function to be added 247 | * @param {Object} [options] - Any extension options needed 248 | * @param {Object} [options.promise] - Promise options 249 | * @param {Boolean} [options.promise.noPromise=false] - If true, do not promisify function 250 | * @param {Boolean} [options.promise.force=false] - If true, do not check if promise is supported 251 | * @param {Boolean} [options.promise.defaultCallback=false] - If true and no callback provided, generate an empty callback 252 | * @param {Number} [options.promise.callbackMinIndex=0] - The minimum index in the arguments that the callback is found in 253 | * @returns {Boolean} True if added, false if ignored 254 | * @example 255 | * ```js 256 | * //define a new function for all new connection objects called 'myConnFunc' which accepts 2 arguments 257 | * SimpleOracleDB.addExtension('connection', 'myConnFunc', function (myParam1, myParam2, callback) { 258 | * //implement some custom functionality... 259 | * 260 | * callback(); 261 | * }); 262 | * 263 | * //get connection (via oracledb directly or via pool) and start using the new function 264 | * connection.myConnFunc('test', 123, function () { 265 | * //continue flow... 266 | * }); 267 | * 268 | * //extensions are automatically promisified (can be disabled) so you can also run extension functions without callback 269 | * const promise = connection.myConnFunc('test', 123); 270 | * promise.then(function () { 271 | * //continue flow... 272 | * }).catch(function (error) { 273 | * //got some error... 274 | * }); 275 | * 276 | * //define a new function for all new pool objects called 'myPoolFunc' 277 | * SimpleOracleDB.addExtension('pool', 'myPoolFunc', function () { 278 | * //implement some custom functionality 279 | * }); 280 | * 281 | * //get pool and start using the new function 282 | * pool.myPoolFunc(); 283 | * ``` 284 | */ 285 | SimpleOracleDB.prototype.addExtension = function (type, name, extension, options) { 286 | return extensions.add(type, name, extension, options); 287 | }; 288 | 289 | /** 290 | * Internal utility function which returns a callback function which will invoke the requested action on the provided connection. 291 | * 292 | * @function 293 | * @memberof! SimpleOracleDB 294 | * @private 295 | * @param {function} action - The function to invoke with the provided connection 296 | * @param {Object} invocationOptions - See oracledb.run/pool.run 297 | * @param {Object} releaseOptions - See oracledb.run/pool.run 298 | * @param {function} callback - Invoked with result/error after the action is invoked and the connection is released 299 | * @returns {function} The callback function 300 | * @example 301 | * ```js 302 | * const actionRunner = simpleOracleDB.createOnConnectionCallback(function (connection) { 303 | * return connection.query('SELECT * FROM PEOPLE'); 304 | * }, { 305 | * ignoreReleaseErrors: false 306 | * }, { 307 | * retryCount: 5 308 | * }, callback); 309 | * oracledb.getConnection(connectionAttributes, actionRunner); 310 | * ``` 311 | */ 312 | SimpleOracleDB.prototype.createOnConnectionCallback = function (action, invocationOptions, releaseOptions, callback) { 313 | return function onConnection(connectionError, connection) { 314 | if (connectionError) { 315 | callback(connectionError); 316 | } else { 317 | try { 318 | let onActionDone = function (actionAsyncError, result) { 319 | connection.release(releaseOptions, function onConnectionRelease(releaseError) { 320 | if (actionAsyncError) { 321 | callback(actionAsyncError); 322 | } else if (releaseError && (!invocationOptions.ignoreReleaseErrors)) { 323 | callback(releaseError); 324 | } else { 325 | callback(null, result); 326 | } 327 | }); 328 | }; 329 | 330 | onActionDone = funcs.once(onActionDone, { 331 | callbackStyle: true 332 | }); 333 | 334 | const promise = action(connection, onActionDone); 335 | 336 | if (promiseHelper.isPromise(promise)) { 337 | promise.then(function onActionResult(result) { 338 | onActionDone(null, result); 339 | }).catch(onActionDone); 340 | } 341 | } catch (actionSyncError) { 342 | connection.release(releaseOptions, function onConnectionRelease() { 343 | callback(actionSyncError); 344 | }); 345 | } 346 | } 347 | }; 348 | }; 349 | 350 | module.exports = new SimpleOracleDB(); 351 | -------------------------------------------------------------------------------- /test/spec/record-reader-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const helper = require('../helpers/test-oracledb'); 6 | const RecordReader = require('../../lib/record-reader'); 7 | 8 | describe('RecordReader Tests', function () { 9 | describe('getValue tests', function () { 10 | it('null', function (done) { 11 | const info = {}; 12 | RecordReader.getValue(null, info, function (error, value) { 13 | assert.isNull(error); 14 | assert.isNull(value); 15 | 16 | assert.isUndefined(info.lobFound); 17 | 18 | done(); 19 | }); 20 | }); 21 | 22 | it('undefined', function (done) { 23 | const info = {}; 24 | RecordReader.getValue(undefined, info, function (error, value) { 25 | assert.isNull(error); 26 | assert.isUndefined(value); 27 | 28 | assert.isUndefined(info.lobFound); 29 | 30 | done(); 31 | }); 32 | }); 33 | 34 | it('boolean - true', function (done) { 35 | const info = {}; 36 | RecordReader.getValue(true, info, function (error, value) { 37 | assert.isNull(error); 38 | assert.isTrue(value); 39 | 40 | assert.isUndefined(info.lobFound); 41 | 42 | done(); 43 | }); 44 | }); 45 | 46 | it('boolean - false', function (done) { 47 | const info = {}; 48 | RecordReader.getValue(false, info, function (error, value) { 49 | assert.isNull(error); 50 | assert.isFalse(value); 51 | 52 | assert.isUndefined(info.lobFound); 53 | 54 | done(); 55 | }); 56 | }); 57 | 58 | it('number - 0', function (done) { 59 | const info = {}; 60 | RecordReader.getValue(0, info, function (error, value) { 61 | assert.isNull(error); 62 | assert.equal(0, value); 63 | 64 | assert.isUndefined(info.lobFound); 65 | 66 | done(); 67 | }); 68 | }); 69 | 70 | it('number - int', function (done) { 71 | const info = {}; 72 | RecordReader.getValue(1, info, function (error, value) { 73 | assert.isNull(error); 74 | assert.equal(1, value); 75 | 76 | assert.isUndefined(info.lobFound); 77 | 78 | done(); 79 | }); 80 | }); 81 | 82 | it('number - float', function (done) { 83 | const info = {}; 84 | RecordReader.getValue(1.5, info, function (error, value) { 85 | assert.isNull(error); 86 | assert.equal(1.5, value); 87 | 88 | assert.isUndefined(info.lobFound); 89 | 90 | done(); 91 | }); 92 | }); 93 | 94 | it('string - empty', function (done) { 95 | const info = {}; 96 | RecordReader.getValue('', info, function (error, value) { 97 | assert.isNull(error); 98 | assert.equal('', value); 99 | 100 | assert.isUndefined(info.lobFound); 101 | 102 | done(); 103 | }); 104 | }); 105 | 106 | it('string - with text', function (done) { 107 | const info = {}; 108 | RecordReader.getValue('TEST', info, function (error, value) { 109 | assert.isNull(error); 110 | assert.equal('TEST', value); 111 | 112 | assert.isUndefined(info.lobFound); 113 | 114 | done(); 115 | }); 116 | }); 117 | 118 | it('date', function (done) { 119 | const info = {}; 120 | const date = new Date(); 121 | RecordReader.getValue(date, info, function (error, value) { 122 | assert.isNull(error); 123 | assert.equal(date, value); 124 | 125 | assert.isUndefined(info.lobFound); 126 | 127 | done(); 128 | }); 129 | }); 130 | 131 | it('LOB', function (done) { 132 | const testStream = helper.createCLOB(); 133 | 134 | const info = {}; 135 | RecordReader.getValue(testStream, info, function (error, value) { 136 | assert.isNull(error); 137 | assert.equal('first line\nsecond line, second part.', value); 138 | 139 | assert.isTrue(info.lobFound); 140 | 141 | done(); 142 | }); 143 | 144 | testStream.emit('data', 'first line\n'); 145 | testStream.emit('data', 'second line'); 146 | testStream.emit('data', ', second part.'); 147 | testStream.emit('end'); 148 | }); 149 | 150 | it('unsupported', function (done) { 151 | const info = {}; 152 | RecordReader.getValue({}, info, function (error) { 153 | assert.isDefined(error); 154 | 155 | assert.isUndefined(info.lobFound); 156 | 157 | done(); 158 | }); 159 | }); 160 | 161 | it('error', function (done) { 162 | const testStream = helper.createCLOB(); 163 | 164 | const info = {}; 165 | RecordReader.getValue(testStream, info, function (error, value) { 166 | assert.isDefined(error); 167 | assert.equal(error.message, 'test error'); 168 | assert.isUndefined(value); 169 | 170 | assert.isTrue(info.lobFound); 171 | 172 | done(); 173 | }); 174 | 175 | testStream.emit('data', 'first line\n'); 176 | testStream.emit('data', 'second line'); 177 | testStream.emit('data', ', second part.'); 178 | testStream.emit('error', new Error('test error')); 179 | }); 180 | }); 181 | 182 | describe('read tests', function () { 183 | it('empty', function (done) { 184 | const info = {}; 185 | RecordReader.read([], [], info, function (error, jsObject) { 186 | assert.isNull(error); 187 | assert.deepEqual({}, jsObject); 188 | 189 | assert.isUndefined(info.lobFound); 190 | 191 | done(); 192 | }); 193 | }); 194 | 195 | it('array - basic js types', function (done) { 196 | const info = {}; 197 | RecordReader.read([ 198 | { 199 | name: 'COL1' 200 | }, 201 | { 202 | name: 'COL2' 203 | }, 204 | { 205 | name: 'COL3' 206 | }, 207 | { 208 | name: 'COL4' 209 | } 210 | ], [ 211 | 1, 212 | 'test', 213 | 50, 214 | undefined 215 | ], info, function (error, jsObject) { 216 | assert.isNull(error); 217 | assert.deepEqual({ 218 | COL1: 1, 219 | COL2: 'test', 220 | COL3: 50, 221 | COL4: undefined 222 | }, jsObject); 223 | 224 | assert.isUndefined(info.lobFound); 225 | 226 | done(); 227 | }); 228 | }); 229 | 230 | it('object - basic js types', function (done) { 231 | const info = {}; 232 | RecordReader.read([ 233 | { 234 | name: 'COL1' 235 | }, 236 | { 237 | name: 'COL2' 238 | }, 239 | { 240 | name: 'COL3' 241 | }, 242 | { 243 | name: 'COL4' 244 | } 245 | ], { 246 | COL1: 1, 247 | COL2: 'test', 248 | COL3: 50, 249 | COL4: undefined 250 | }, info, function (error, jsObject) { 251 | assert.isNull(error); 252 | assert.deepEqual({ 253 | COL1: 1, 254 | COL2: 'test', 255 | COL3: 50, 256 | COL4: undefined 257 | }, jsObject); 258 | 259 | assert.isUndefined(info.lobFound); 260 | 261 | done(); 262 | }); 263 | }); 264 | 265 | it('array - LOB types', function (done) { 266 | const lob1 = helper.createCLOB(); 267 | const lob2 = helper.createCLOB(); 268 | 269 | const info = {}; 270 | RecordReader.read([ 271 | { 272 | name: 'COL1' 273 | }, 274 | { 275 | name: 'COL2' 276 | }, 277 | { 278 | name: 'COL3' 279 | }, 280 | { 281 | name: 'COL4' 282 | }, 283 | { 284 | name: 'LOB1' 285 | }, 286 | { 287 | name: 'LOB2' 288 | } 289 | ], [ 290 | 1, 291 | 'test', 292 | 50, 293 | undefined, 294 | lob1, 295 | lob2 296 | ], info, function (error, jsObject) { 297 | assert.isNull(error); 298 | assert.deepEqual({ 299 | COL1: 1, 300 | COL2: 'test', 301 | COL3: 50, 302 | COL4: undefined, 303 | LOB1: 'test1\ntest2', 304 | LOB2: '123456' 305 | }, jsObject); 306 | 307 | assert.isTrue(info.lobFound); 308 | 309 | done(); 310 | }); 311 | 312 | setTimeout(function () { 313 | lob1.emit('data', 'test1'); 314 | lob1.emit('data', '\ntest2'); 315 | lob1.emit('end'); 316 | 317 | lob2.emit('data', '123'); 318 | lob2.emit('data', '456'); 319 | lob2.emit('end'); 320 | }, 10); 321 | }); 322 | 323 | it('object - LOB types', function (done) { 324 | const lob1 = helper.createCLOB(); 325 | const lob2 = helper.createCLOB(); 326 | 327 | const info = {}; 328 | RecordReader.read([ 329 | { 330 | name: 'COL1' 331 | }, 332 | { 333 | name: 'COL2' 334 | }, 335 | { 336 | name: 'COL3' 337 | }, 338 | { 339 | name: 'COL4' 340 | }, 341 | { 342 | name: 'LOB1' 343 | }, 344 | { 345 | name: 'LOB2' 346 | } 347 | ], { 348 | COL1: 1, 349 | COL2: 'test', 350 | COL3: 50, 351 | COL4: undefined, 352 | LOB1: lob1, 353 | LOB2: lob2 354 | }, info, function (error, jsObject) { 355 | assert.isNull(error); 356 | assert.deepEqual({ 357 | COL1: 1, 358 | COL2: 'test', 359 | COL3: 50, 360 | COL4: undefined, 361 | LOB1: 'test1\ntest2', 362 | LOB2: '123456' 363 | }, jsObject); 364 | 365 | assert.isTrue(info.lobFound); 366 | 367 | done(); 368 | }); 369 | 370 | setTimeout(function () { 371 | lob1.emit('data', 'test1'); 372 | lob1.emit('data', '\ntest2'); 373 | lob1.emit('end'); 374 | 375 | lob2.emit('data', '123'); 376 | lob2.emit('data', '456'); 377 | lob2.emit('end'); 378 | }, 10); 379 | }); 380 | 381 | it('array - error', function (done) { 382 | const lob1 = helper.createCLOB(); 383 | const lob2 = helper.createCLOB(); 384 | 385 | const info = {}; 386 | RecordReader.read([ 387 | { 388 | name: 'COL1' 389 | }, 390 | { 391 | name: 'COL2' 392 | }, 393 | { 394 | name: 'COL3' 395 | }, 396 | { 397 | name: 'COL4' 398 | }, 399 | { 400 | name: 'LOB1' 401 | }, 402 | { 403 | name: 'LOB2' 404 | } 405 | ], [ 406 | 1, 407 | 'test', 408 | 50, 409 | undefined, 410 | lob1, 411 | lob2 412 | ], info, function (error) { 413 | assert.isDefined(error); 414 | assert.equal(error.message, 'lob1 error'); 415 | 416 | assert.isTrue(info.lobFound); 417 | 418 | done(); 419 | }); 420 | 421 | setTimeout(function () { 422 | lob1.emit('data', 'test1'); 423 | lob1.emit('error', new Error('lob1 error')); 424 | 425 | lob2.emit('data', '123'); 426 | lob2.emit('data', '456'); 427 | lob2.emit('end'); 428 | }, 10); 429 | }); 430 | 431 | it('object - error', function (done) { 432 | const lob1 = helper.createCLOB(); 433 | const lob2 = helper.createCLOB(); 434 | 435 | const info = {}; 436 | RecordReader.read([ 437 | { 438 | name: 'COL1' 439 | }, 440 | { 441 | name: 'COL2' 442 | }, 443 | { 444 | name: 'COL3' 445 | }, 446 | { 447 | name: 'COL4' 448 | }, 449 | { 450 | name: 'LOB1' 451 | }, 452 | { 453 | name: 'LOB2' 454 | } 455 | ], { 456 | COL1: 1, 457 | COL2: 'test', 458 | COL3: 50, 459 | COL4: undefined, 460 | LOB1: lob1, 461 | LOB2: lob2 462 | }, info, function (error) { 463 | assert.isDefined(error); 464 | assert.equal(error.message, 'lob1 error'); 465 | 466 | assert.isTrue(info.lobFound); 467 | 468 | done(); 469 | }); 470 | 471 | setTimeout(function () { 472 | lob1.emit('data', 'test1'); 473 | lob1.emit('error', new Error('lob1 error')); 474 | 475 | lob2.emit('data', '123'); 476 | lob2.emit('data', '456'); 477 | lob2.emit('end'); 478 | }, 10); 479 | }); 480 | }); 481 | 482 | describe('readJSON tests', function () { 483 | it('undefined no column', function () { 484 | const json = RecordReader.readJSON(); 485 | assert.isUndefined(json); 486 | }); 487 | 488 | it('undefined with column', function () { 489 | const json = RecordReader.readJSON(undefined, 'data'); 490 | assert.isUndefined(json); 491 | }); 492 | 493 | it('not json no column', function () { 494 | const json = RecordReader.readJSON('some text'); 495 | 496 | assert.isUndefined(json); 497 | }); 498 | 499 | it('not json with column', function () { 500 | const json = RecordReader.readJSON('some text', 'data'); 501 | 502 | assert.isObject(json); 503 | }); 504 | 505 | it('not json field', function () { 506 | let errorFound = false; 507 | 508 | try { 509 | RecordReader.readJSON({ 510 | data: 'some text' 511 | }, 'data'); 512 | } catch (error) { 513 | errorFound = true; 514 | } 515 | 516 | assert.isTrue(errorFound); 517 | }); 518 | 519 | it('not json in data with column', function () { 520 | let errorFound = false; 521 | 522 | try { 523 | RecordReader.readJSON({ 524 | data: 'some text' 525 | }, 'data'); 526 | } catch (error) { 527 | errorFound = true; 528 | } 529 | 530 | assert.isTrue(errorFound); 531 | }); 532 | 533 | it('json no column', function () { 534 | const output = RecordReader.readJSON({ 535 | data: JSON.stringify({ 536 | a: 1 537 | }) 538 | }); 539 | 540 | assert.isUndefined(output); 541 | }); 542 | 543 | it('json with column', function () { 544 | const output = RecordReader.readJSON({ 545 | data: JSON.stringify({ 546 | a: 1, 547 | test: true, 548 | array: [ 549 | 1, 550 | 2, 551 | 3 552 | ], 553 | subObject: { 554 | key1: 'value1' 555 | } 556 | }) 557 | }, 'data'); 558 | 559 | assert.deepEqual(output, { 560 | a: 1, 561 | test: true, 562 | array: [ 563 | 1, 564 | 2, 565 | 3 566 | ], 567 | subObject: { 568 | key1: 'value1' 569 | } 570 | }); 571 | }); 572 | 573 | it('json wrong column', function () { 574 | const output = RecordReader.readJSON({ 575 | data: JSON.stringify({ 576 | a: 1, 577 | test: true, 578 | array: [ 579 | 1, 580 | 2, 581 | 3 582 | ], 583 | subObject: { 584 | key1: 'value1' 585 | } 586 | }) 587 | }, 'wrong'); 588 | 589 | assert.deepEqual(output, {}); 590 | }); 591 | }); 592 | }); 593 | -------------------------------------------------------------------------------- /test/spec/rows-reader-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const helper = require('../helpers/test-oracledb'); 6 | const utils = require('../helpers/utils'); 7 | const RowsReader = require('../../lib/rows-reader'); 8 | 9 | describe('RowsReader Tests', function () { 10 | describe('getFlattenRowsCount', function () { 11 | it('no flattenStackEveryRows value', function () { 12 | const count = RowsReader.getFlattenRowsCount(['test'], {}); 13 | assert.strictEqual(count, 100); 14 | }); 15 | 16 | it('flattenStackEveryRows value provided', function () { 17 | const count = RowsReader.getFlattenRowsCount(['test'], { 18 | flattenStackEveryRows: 5000 19 | }); 20 | assert.strictEqual(count, 5000); 21 | }); 22 | 23 | it('lots of columns', function () { 24 | const columnNames = []; 25 | for (let index = 0; index < 6000; index++) { 26 | columnNames.push('test' + index); 27 | } 28 | 29 | const count = RowsReader.getFlattenRowsCount(columnNames, {}); 30 | assert.strictEqual(count, 1); 31 | }); 32 | 33 | it('fews columns', function () { 34 | const columnNames = []; 35 | for (let index = 0; index < 23; index++) { 36 | columnNames.push('test' + index); 37 | } 38 | 39 | const count = RowsReader.getFlattenRowsCount(columnNames, {}); 40 | assert.strictEqual(count, 4); 41 | }); 42 | 43 | it('exact check columns amount', function () { 44 | const columnNames = []; 45 | for (let index = 0; index < 100; index++) { 46 | columnNames.push('test' + index); 47 | } 48 | 49 | const count = RowsReader.getFlattenRowsCount(columnNames, {}); 50 | assert.strictEqual(count, 1); 51 | }); 52 | 53 | it('almost exact check columns amount', function () { 54 | const columnNames = []; 55 | for (let index = 0; index < 99; index++) { 56 | columnNames.push('test' + index); 57 | } 58 | 59 | const count = RowsReader.getFlattenRowsCount(columnNames, {}); 60 | assert.strictEqual(count, 1); 61 | }); 62 | }); 63 | 64 | describe('read', function () { 65 | it('empty', function (done) { 66 | RowsReader.read([], [], function (error, jsRows) { 67 | assert.isNull(error); 68 | assert.deepEqual([], jsRows); 69 | 70 | done(); 71 | }); 72 | }); 73 | 74 | it('array - basic js types', function (done) { 75 | const date = new Date(); 76 | RowsReader.read([ 77 | { 78 | name: 'COL1' 79 | }, 80 | { 81 | name: 'COL2' 82 | }, 83 | { 84 | name: 'COL3' 85 | }, 86 | { 87 | name: 'COL4' 88 | } 89 | ], [ 90 | [ 91 | 1, 92 | 'test', 93 | 50, 94 | undefined 95 | ], 96 | [ 97 | 'a', 98 | date, 99 | undefined, 100 | null 101 | ] 102 | ], function (error, jsRows) { 103 | assert.isNull(error); 104 | assert.deepEqual([ 105 | { 106 | COL1: 1, 107 | COL2: 'test', 108 | COL3: 50, 109 | COL4: undefined 110 | }, 111 | { 112 | COL1: 'a', 113 | COL2: date, 114 | COL3: undefined, 115 | COL4: null 116 | } 117 | ], jsRows); 118 | 119 | done(); 120 | }); 121 | }); 122 | 123 | it('object - basic js types', function (done) { 124 | const date = new Date(); 125 | RowsReader.read([ 126 | { 127 | name: 'COL1' 128 | }, 129 | { 130 | name: 'COL2' 131 | }, 132 | { 133 | name: 'COL3' 134 | }, 135 | { 136 | name: 'COL4' 137 | } 138 | ], [ 139 | { 140 | COL1: 1, 141 | COL2: 'test', 142 | COL3: 50, 143 | COL4: undefined 144 | }, 145 | { 146 | COL1: 'a', 147 | COL2: date, 148 | COL3: undefined, 149 | COL4: undefined 150 | } 151 | ], function (error, jsRows) { 152 | assert.isNull(error); 153 | assert.deepEqual([ 154 | { 155 | COL1: 1, 156 | COL2: 'test', 157 | COL3: 50, 158 | COL4: undefined 159 | }, 160 | { 161 | COL1: 'a', 162 | COL2: date, 163 | COL3: undefined, 164 | COL4: undefined 165 | } 166 | ], jsRows); 167 | 168 | done(); 169 | }); 170 | }); 171 | 172 | it('array - basic js types, huge size', function (done) { 173 | this.timeout(5000); 174 | 175 | const data = []; 176 | const result = []; 177 | 178 | for (let index = 0; index < 5000; index++) { 179 | const numValue = (index % 20); 180 | 181 | const rowData = [ 182 | index, 183 | 'test-' + index, 184 | numValue, 185 | undefined 186 | ]; 187 | 188 | data.push(rowData); 189 | result.push({ 190 | COL1: rowData[0], 191 | COL2: rowData[1], 192 | COL3: rowData[2], 193 | COL4: rowData[3] 194 | }); 195 | } 196 | 197 | RowsReader.read([ 198 | { 199 | name: 'COL1' 200 | }, 201 | { 202 | name: 'COL2' 203 | }, 204 | { 205 | name: 'COL3' 206 | }, 207 | { 208 | name: 'COL4' 209 | } 210 | ], data, { 211 | flattenStackEveryRows: 50 212 | }, function (error, jsRows) { 213 | assert.isNull(error); 214 | assert.deepEqual(result, jsRows); 215 | 216 | done(); 217 | }); 218 | }); 219 | 220 | it('array - CLOB types', function (done) { 221 | const lob1 = helper.createCLOB(); 222 | const lob2 = helper.createCLOB(); 223 | 224 | const date = new Date(); 225 | RowsReader.read([ 226 | { 227 | name: 'COL1' 228 | }, 229 | { 230 | name: 'COL2' 231 | }, 232 | { 233 | name: 'COL3' 234 | }, 235 | { 236 | name: 'COL4' 237 | } 238 | ], [ 239 | [ 240 | lob1, 241 | 'test', 242 | 50, 243 | undefined 244 | ], 245 | [ 246 | 'a', 247 | date, 248 | undefined, 249 | lob2 250 | ] 251 | ], function (error, jsRows) { 252 | assert.isNull(error); 253 | assert.deepEqual([ 254 | { 255 | COL1: 'test1\ntest2', 256 | COL2: 'test', 257 | COL3: 50, 258 | COL4: undefined 259 | }, 260 | { 261 | COL1: 'a', 262 | COL2: date, 263 | COL3: undefined, 264 | COL4: '123456' 265 | } 266 | ], jsRows); 267 | 268 | done(); 269 | }); 270 | 271 | setTimeout(function () { 272 | lob1.emit('data', 'test1'); 273 | lob1.emit('data', '\ntest2'); 274 | lob1.emit('end'); 275 | 276 | lob2.emit('data', '123'); 277 | lob2.emit('data', '456'); 278 | lob2.emit('end'); 279 | }, 10); 280 | }); 281 | 282 | it('object - CLOB types', function (done) { 283 | const lob1 = helper.createCLOB(); 284 | const lob2 = helper.createCLOB(); 285 | 286 | const date = new Date(); 287 | RowsReader.read([ 288 | { 289 | name: 'COL1' 290 | }, 291 | { 292 | name: 'COL2' 293 | }, 294 | { 295 | name: 'COL3' 296 | }, 297 | { 298 | name: 'COL4' 299 | } 300 | ], [ 301 | { 302 | COL1: lob1, 303 | COL2: 'test', 304 | COL3: 50, 305 | COL4: undefined 306 | }, 307 | { 308 | COL1: 'a', 309 | COL2: date, 310 | COL3: undefined, 311 | COL4: lob2 312 | } 313 | ], function (error, jsRows) { 314 | assert.isNull(error); 315 | assert.deepEqual([ 316 | { 317 | COL1: 'test1\ntest2', 318 | COL2: 'test', 319 | COL3: 50, 320 | COL4: undefined 321 | }, 322 | { 323 | COL1: 'a', 324 | COL2: date, 325 | COL3: undefined, 326 | COL4: '123456' 327 | } 328 | ], jsRows); 329 | 330 | done(); 331 | }); 332 | 333 | setTimeout(function () { 334 | lob1.emit('data', 'test1'); 335 | lob1.emit('data', '\ntest2'); 336 | lob1.emit('end'); 337 | 338 | lob2.emit('data', '123'); 339 | lob2.emit('data', '456'); 340 | lob2.emit('end'); 341 | }, 10); 342 | }); 343 | 344 | it('array - BLOB types', function (done) { 345 | const lob1 = helper.createBLOB(); 346 | const lob2 = helper.createBLOB(); 347 | 348 | const date = new Date(); 349 | RowsReader.read([ 350 | { 351 | name: 'COL1' 352 | }, 353 | { 354 | name: 'COL2' 355 | }, 356 | { 357 | name: 'COL3' 358 | }, 359 | { 360 | name: 'COL4' 361 | } 362 | ], [ 363 | [ 364 | lob1, 365 | 'test', 366 | 50, 367 | undefined 368 | ], 369 | [ 370 | 'a', 371 | date, 372 | undefined, 373 | lob2 374 | ] 375 | ], function (error, jsRows) { 376 | assert.isNull(error); 377 | 378 | jsRows[0].COL1 = jsRows[0].COL1.toJSON(); 379 | jsRows[1].COL4 = jsRows[1].COL4.toJSON(); 380 | 381 | assert.deepEqual([ 382 | { 383 | COL1: (utils.createBuffer('test1\ntest2', 'utf8')).toJSON(), 384 | COL2: 'test', 385 | COL3: 50, 386 | COL4: undefined 387 | }, 388 | { 389 | COL1: 'a', 390 | COL2: date, 391 | COL3: undefined, 392 | COL4: (utils.createBuffer('123456', 'utf8')).toJSON() 393 | } 394 | ], jsRows); 395 | 396 | done(); 397 | }); 398 | 399 | setTimeout(function () { 400 | lob1.emit('data', utils.createBuffer('test1', 'utf8')); 401 | lob1.emit('data', utils.createBuffer('\ntest2', 'utf8')); 402 | lob1.emit('end'); 403 | 404 | lob2.emit('data', utils.createBuffer('123', 'utf8')); 405 | lob2.emit('data', utils.createBuffer('456', 'utf8')); 406 | lob2.emit('end'); 407 | }, 10); 408 | }); 409 | 410 | it('object - BLOB types', function (done) { 411 | const lob1 = helper.createBLOB(); 412 | const lob2 = helper.createBLOB(); 413 | 414 | const date = new Date(); 415 | RowsReader.read([ 416 | { 417 | name: 'COL1' 418 | }, 419 | { 420 | name: 'COL2' 421 | }, 422 | { 423 | name: 'COL3' 424 | }, 425 | { 426 | name: 'COL4' 427 | } 428 | ], [ 429 | { 430 | COL1: lob1, 431 | COL2: 'test', 432 | COL3: 50, 433 | COL4: undefined 434 | }, 435 | { 436 | COL1: 'a', 437 | COL2: date, 438 | COL3: undefined, 439 | COL4: lob2 440 | } 441 | ], function (error, jsRows) { 442 | assert.isNull(error); 443 | 444 | jsRows[0].COL1 = jsRows[0].COL1.toJSON(); 445 | jsRows[1].COL4 = jsRows[1].COL4.toJSON(); 446 | 447 | assert.deepEqual([ 448 | { 449 | COL1: (utils.createBuffer('test1\ntest2', 'utf8')).toJSON(), 450 | COL2: 'test', 451 | COL3: 50, 452 | COL4: undefined 453 | }, 454 | { 455 | COL1: 'a', 456 | COL2: date, 457 | COL3: undefined, 458 | COL4: (utils.createBuffer('123456', 'utf8')).toJSON() 459 | } 460 | ], jsRows); 461 | 462 | done(); 463 | }); 464 | 465 | setTimeout(function () { 466 | lob1.emit('data', utils.createBuffer('test1', 'utf8')); 467 | lob1.emit('data', utils.createBuffer('\ntest2', 'utf8')); 468 | lob1.emit('end'); 469 | 470 | lob2.emit('data', utils.createBuffer('123', 'utf8')); 471 | lob2.emit('data', utils.createBuffer('456', 'utf8')); 472 | lob2.emit('end'); 473 | }, 10); 474 | }); 475 | 476 | it('array - error', function (done) { 477 | const lob1 = helper.createCLOB(); 478 | const lob2 = helper.createCLOB(); 479 | 480 | const date = new Date(); 481 | RowsReader.read([ 482 | { 483 | name: 'COL1' 484 | }, 485 | { 486 | name: 'COL2' 487 | }, 488 | { 489 | name: 'COL3' 490 | }, 491 | { 492 | name: 'COL4' 493 | } 494 | ], [ 495 | [ 496 | lob1, 497 | 'test', 498 | 50, 499 | undefined 500 | ], 501 | [ 502 | 'a', 503 | date, 504 | undefined, 505 | lob2 506 | ] 507 | ], function (error) { 508 | assert.isDefined(error); 509 | assert.equal(error.message, 'test lob error'); 510 | 511 | done(); 512 | }); 513 | 514 | setTimeout(function () { 515 | lob1.emit('data', 'test1'); 516 | lob1.emit('error', new Error('test lob error')); 517 | 518 | lob2.emit('data', '123'); 519 | lob2.emit('data', '456'); 520 | lob2.emit('end'); 521 | }, 10); 522 | }); 523 | 524 | it('object - error', function (done) { 525 | const lob1 = helper.createCLOB(); 526 | const lob2 = helper.createCLOB(); 527 | 528 | const date = new Date(); 529 | RowsReader.read([ 530 | { 531 | name: 'COL1' 532 | }, 533 | { 534 | name: 'COL2' 535 | }, 536 | { 537 | name: 'COL3' 538 | }, 539 | { 540 | name: 'COL4' 541 | } 542 | ], [ 543 | { 544 | COL1: lob1, 545 | COL2: 'test', 546 | COL3: 50, 547 | COL4: undefined 548 | }, 549 | { 550 | COL1: 'a', 551 | COL2: date, 552 | COL3: undefined, 553 | COL4: lob2 554 | } 555 | ], function (error) { 556 | assert.isDefined(error); 557 | assert.equal(error.message, 'test lob error'); 558 | 559 | done(); 560 | }); 561 | 562 | setTimeout(function () { 563 | lob1.emit('data', 'test1'); 564 | lob1.emit('error', new Error('test lob error')); 565 | 566 | lob2.emit('data', '123'); 567 | lob2.emit('data', '456'); 568 | lob2.emit('end'); 569 | }, 10); 570 | }); 571 | }); 572 | 573 | describe('readJSON', function () { 574 | it('undefined', function () { 575 | const json = RowsReader.readJSON(); 576 | assert.isUndefined(json); 577 | }); 578 | 579 | it('multiple keys', function () { 580 | let errorFound = false; 581 | 582 | try { 583 | RowsReader.readJSON([ 584 | { 585 | key1: JSON.stringify({ 586 | test: true 587 | }), 588 | key2: JSON.stringify({ 589 | test: true 590 | }) 591 | } 592 | ]); 593 | } catch (error) { 594 | errorFound = true; 595 | } 596 | 597 | assert.isTrue(errorFound); 598 | }); 599 | 600 | it('no rows', function () { 601 | const json = RowsReader.readJSON([]); 602 | assert.deepEqual([], json); 603 | }); 604 | 605 | it('multiple json rows', function () { 606 | const output = RowsReader.readJSON([ 607 | { 608 | data: JSON.stringify({ 609 | a: 1, 610 | test: true, 611 | array: [1, 2, 3], 612 | subObject: { 613 | key1: 'value1' 614 | } 615 | }) 616 | }, 617 | { 618 | data: JSON.stringify({ 619 | a: 2, 620 | test: false, 621 | array: [1, 'b', 3], 622 | subObject: { 623 | key1: 'value1' 624 | } 625 | }) 626 | } 627 | ]); 628 | 629 | assert.deepEqual(output, [ 630 | { 631 | a: 1, 632 | test: true, 633 | array: [1, 2, 3], 634 | subObject: { 635 | key1: 'value1' 636 | } 637 | }, 638 | { 639 | a: 2, 640 | test: false, 641 | array: [1, 'b', 3], 642 | subObject: { 643 | key1: 'value1' 644 | } 645 | } 646 | ]); 647 | }); 648 | }); 649 | }); 650 | --------------------------------------------------------------------------------