├── .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 |
--------------------------------------------------------------------------------