UNPKG

simple-oracledb

Version:

Extend capabilities of oracledb with simplified API for quicker development.

1,123 lines (1,038 loc) 78.5 kB
'use strict'; const fs = require('fs'); const debug = require('debuglog')('simple-oracledb'); const asyncLib = require('async'); const funcs = require('funcs-js'); const rowsReader = require('./rows-reader'); const resultSetReader = require('./resultset-reader'); const recordWriter = require('./record-writer'); const constants = require('./constants'); const ResultSetReadStream = require('./resultset-read-stream'); const extensions = require('./extensions'); const emitter = require('./emitter'); const promiseHelper = require('./promise-helper'); let connectionIDCounter = 0; /** * This events is triggered when the connection is released successfully. * * @event Connection#release */ /*jslint debug: true */ /*istanbul ignore next*/ /** * This class holds all the extended capabilities added the oracledb connection. * * @author Sagie Gur-Ari * @class Connection * @public * @fires event:release */ function Connection() { //should not be called } /*jslint debug: false */ /** * Marker property. * * @member {Boolean} * @alias Connection.simplified * @memberof! Connection * @public */ Connection.prototype.simplified = true; /** * Empty function. * * @function * @memberof! Connection * @private * @returns {undefined} Empty return */ Connection.prototype.noop = function () { return undefined; }; /** * Returns the execute arguments if provided. * * @function * @memberof! Connection * @private * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - Optional bind parameters * @param {Object} [options] - Optional execute options * @param {AsyncCallback} [callback] - Callback function with the execution results * @returns {Object} All the arguments found * @example * ```js * //get object with actual named arguments by checking input parametrs * const input = connection.getExecuteArguments('SELECT department_id, department_name FROM departments WHERE manager_id < :id', myCallback); * * const sql = input.sql; * const bindParams = input.bindParams; * const options = input.options; * const callback = input.callback; * ``` */ Connection.prototype.getExecuteArguments = function (sql, bindParams, options, callback) { if (bindParams && (typeof bindParams === 'function')) { callback = bindParams; bindParams = null; options = null; } else if (options && (typeof options === 'function')) { callback = options; options = null; } this.setupDefaultOptions(options); return { callback, sql, bindParams, options }; }; /** * Modifies the options with default values. * * @function * @memberof! Connection * @private * @param {Object} [options] - Optional execute options * @param {Object} [flags] - Used to setup the defaults * @param {Boolean} [flags.batch=false] - True if batch insert/update * @example * ```js * connection.setupDefaultOptions(options); * ``` */ Connection.prototype.setupDefaultOptions = function (options, flags) { if (options && typeof options === 'object') { flags = flags || {}; if (flags.batch) { const useExecuteMany = options.useExecuteMany; if (useExecuteMany === null || useExecuteMany === undefined) { options.useExecuteMany = true; } } } }; /** * Extends the original oracledb connection.execute/executeMany to provide additional behavior. * * @function * @memberof! Connection * @private * @param {String} operation - The operation to trigger * @param {String} sql - The SQL to execute * @param {Object|Array} [bindParams] - Optional bind parameters * @param {Object} [options] - Optional execute options * @param {AsyncCallback} [callback] - Callback function with the execution results * @returns {Promise} In case of no callback provided in input, this function will return a promise * @example * ```js * connection.executeImpl('SELECT department_id, department_name FROM departments WHERE manager_id < :id', [110], function onResults(error, results) { * if (error) { * //handle error... * } else { * //continue * } * }); * ``` */ Connection.prototype.executeImpl = function (operation, sql, bindParams, options, callback) { const input = this.getExecuteArguments(sql, bindParams, options, callback); sql = input.sql; bindParams = input.bindParams; options = input.options; callback = input.callback; if (this.inTransaction && options) { options.autoCommit = false; } this.diagnosticInfo.lastSQL = sql; debug('Execute, SQL: ', sql, ' Bind Params: ', bindParams, 'Options', options); const args = [ sql ]; if (bindParams) { args.push(bindParams); if (options) { args.push(options); } } if (callback) { args.push(callback); } return this[operation].apply(this, args); }; /** * Extends the original oracledb connection.execute to provide additional behavior. * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - Optional bind parameters * @param {Object} [options] - Optional execute options * @param {AsyncCallback} [callback] - Callback function with the execution results * @returns {Promise} In case of no callback provided in input, this function will return a promise * @example * ```js * //see oracledb documentation for more examples * connection.execute('SELECT department_id, department_name FROM departments WHERE manager_id < :id', [110], function onResults(error, results) { * if (error) { * //handle error... * } else { * //continue * } * }); * ``` */ Connection.prototype.execute = function (sql, bindParams, options, callback) { return this.executeImpl('baseExecute', sql, bindParams, options, callback); }; /** * Extends the original oracledb connection.executeMany to provide additional behavior. * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Array} [bindParamsArray] - Optional bind parameters * @param {Object} [options] - Optional execute options * @param {AsyncCallback} [callback] - Callback function with the execution results * @returns {Promise} In case of no callback provided in input, this function will return a promise * @example * ```js * //see oracledb documentation for more examples * connection.executeMany('SELECT department_id, department_name FROM departments WHERE manager_id < :id', [[110]], function onResults(error, results) { * if (error) { * //handle error... * } else { * //continue * } * }); * ``` */ Connection.prototype.executeMany = function (sql, bindParamsArray, options, callback) { return this.executeImpl('baseExecuteMany', sql, bindParamsArray, options, callback); }; /** * Provides simpler interface than the original oracledb connection.execute function to enable simple query invocation.<br> * The callback output will be an array of objects, each object holding a property for each field with the actual value.<br> * All LOBs will be read and all rows will be fetched.<br> * This function is not recommended for huge results sets or huge LOB values as it will consume a lot of memory.<br> * The function arguments used to execute the 'query' are exactly as defined in the oracledb connection.execute function. * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - Optional bind parameters * @param {Object} [options] - Optional execute options * @param {Object} [options.splitResults=false] - True to enable to split the results into bulks, each bulk will invoke the provided callback (last callback invocation will have empty results, promise not supported). See also bulkRowsAmount option. * @param {Object} [options.streamResults=false] - True to enable to stream the results, the callback will receive a read stream object which can be piped or used with standard stream events (ignored if splitResults=true). * @param {Number} [options.bulkRowsAmount=100] - The amount of rows to fetch (for splitting results, that is the max rows that the callback will get for each callback invocation) * @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 * @param {AsyncCallback} [callback] - Invoked with an error or the query results object holding all data including LOBs * @returns {ResultSetReadStream|Promise} The stream to read the results from (if streamResults=true in options) or promise if callback not provided * @example * ```js * //read all rows and get an array of objects with all data * connection.query('SELECT department_id, department_name FROM departments WHERE manager_id < :id', [110], function onResults(error, results) { * if (error) { * //handle error... * } else { * //print the 4th row DEPARTMENT_ID column value * console.log(results[3].DEPARTMENT_ID); * } * }); * * //same as previous example but with promise support * connection.query('SELECT department_id, department_name FROM departments WHERE manager_id < :id', [110]).then(function (results) { * //print the 4th row DEPARTMENT_ID column value * console.log(results[3].DEPARTMENT_ID); * }); * * //In order to split results into bulks, you can provide the splitResults = true option. * //The callback will be called for each bulk with array of objects. * //Once all rows are read, the callback will be called with an empty array. * //Promises are not supported with splitResults=true * connection.query('SELECT * FROM departments WHERE manager_id > :id', [110], { * splitResults: true, * bulkRowsAmount: 100 //The amount of rows to fetch (for splitting results, that is the max rows that the callback will get for each callback invocation) * }, function onResults(error, results) { * if (error) { * //handle error... * } else if (results.length) { * //handle next bulk of results * } else { * //all rows read * } * }); * * //In order to stream results into a read stream, you can provide the streamResults = true option. * //The optional callback will be called with a read stream instance which can be used to fetch/pipe the data. * //Once all rows are read, the proper stream events will be called. * const stream = connection.query('SELECT * FROM departments WHERE manager_id > :id', [110], { * streamResults: true * }); * * //listen to fetched rows via data event or just pipe to another handler * stream.on('data', function (row) { * //use row object * * if (row.MY_ID === 800) { * stream.close(); //optionally call the close function to prevent any more 'data' events and free the connection to execute other operations * } * }); * * //optionally listen also to metadata of query * stream.on('metadata', function (metaData) { * console.log(metaData); * }); * * //listen to other events such as end/close/error.... * ``` */ Connection.prototype.query = function (sql, bindParams, options, callback) { const self = this; const input = self.getExecuteArguments(sql, bindParams, options, callback); sql = input.sql; bindParams = input.bindParams; options = input.options; callback = input.callback; let handleType = 0; let output; const queryAsync = function (promiseCallback) { callback = callback || promiseCallback; if ((!output) && (!callback)) { throw new Error('Callback not provided.'); } //by default is not defined, use resultset if (!options) { bindParams = bindParams || []; options = {}; } if ((!options.maxRows) && (options.resultSet === undefined)) { options.resultSet = true; } const queryCallback = self.createQueryCallback(callback, options, handleType, output); self.execute(sql, bindParams, options, queryCallback); }; if (options) { if (options.splitResults) { handleType = 1; } else if (options.streamResults || options.stream) { //support also the oracledb stream option handleType = 2; output = new ResultSetReadStream(); } if (handleType) { options.resultSet = true; //use simple-oracledb streaming instead of oracledb stream capability (which is actually based on this project) delete options.stream; } } //if promise requested and supported by current platform if ((!output) && (!callback) && (handleType !== 1)) { //split doesn't support promise output = promiseHelper.runPromise(queryAsync, callback, (!output) && (handleType !== 1)); } else { queryAsync(); } return output; }; /** * Provides simpler interface than the original oracledb connection.execute function to enable simple insert invocation with LOB support.<br> * The callback output will be the same as oracledb connection.execute.<br> * All LOBs will be written to the DB via streams and only after all LOBs are written the callback will be called.<br> * The function arguments used to execute the 'insert' are exactly as defined in the oracledb connection.execute function, however the options are mandatory. * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - The bind parameters used to specify the values for the columns * @param {Object} [options] - Any execute options * @param {Object} [options.autoCommit] - If you wish to commit after the insert, this property must be set to true in the options (oracledb.autoCommit is not checked) * @param {Object} [options.lobMetaInfo] - For LOB support this object must hold a mapping between DB column name and bind constiable name * @param {Object} [options.returningInfo] - columnName/bindconstName pairs which will be added to the RETURNING ... INTO ... clause (only used if lobMetaInfo is provided) * @param {AsyncCallback} [callback] - Invoked with an error or the insert results (if LOBs are provided, the callback will be triggered after they have been fully written to the DB) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.insert('INSERT INTO mylobs (id, clob_column1, blob_column2) VALUES (:id, EMPTY_CLOB(), EMPTY_BLOB())', { //no need to specify the RETURNING clause in the SQL * id: 110, * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }, function onResults(error, output) { * //continue flow... * }); * * //add few more items to the RETURNING clause (only used if lobMetaInfo is provided) * connection.insert('INSERT INTO mylobs (id, clob_column1, blob_column2) VALUES (:myid, EMPTY_CLOB(), EMPTY_BLOB())', { //no need to specify the RETURNING clause in the SQL * myid: { * type: oracledb.NUMBER, * dir: oracledb.BIND_INOUT, * val: 1234 * }, * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * }, * returningInfo: { //all items in this column/bind constiable object will be added to the generated RETURNING clause * id: 'myid' * } * }, function onResults(error, output) { * //continue flow... * }); * * //another example but with promise support * connection.insert('INSERT INTO mylobs (id, clob_column1, blob_column2) VALUES (:id, EMPTY_CLOB(), EMPTY_BLOB())', { //no need to specify the RETURNING clause in the SQL * id: 110, * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }).then(function (results) { * console.log(results.rowsAffected); * }); * ``` */ Connection.prototype.insert = function (sql, bindParams, options, callback) { return this.insertOrUpdate(true, sql, bindParams, options, callback); }; /** * Provides simpler interface than the original oracledb connection.execute function to enable simple update invocation with LOB support.<br> * The callback output will be the same as oracledb connection.execute.<br> * All LOBs will be written to the DB via streams and only after all LOBs are written the callback will be called.<br> * The function arguments used to execute the 'update' are exactly as defined in the oracledb connection.execute function, however the options are mandatory. * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - The bind parameters used to specify the values for the columns * @param {Object} [options] - Any execute options * @param {Object} [options.autoCommit] - If you wish to commit after the update, this property must be set to true in the options (oracledb.autoCommit is not checked) * @param {Object} [options.lobMetaInfo] - For LOB support this object must hold a mapping between DB column name and bind constiable name * @param {Object} [options.returningInfo] - columnName/bindconstName pairs which will be added to the RETURNING ... INTO ... clause (only used if lobMetaInfo is provided), see connection.insert example * @param {AsyncCallback} [callback] - Invoked with an error or the update results (if LOBs are provided, the callback will be triggered after they have been fully written to the DB) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.update('UPDATE mylobs SET name = :name, clob_column1 = EMPTY_CLOB(), blob_column2 = EMPTY_BLOB() WHERE id = :id', { //no need to specify the RETURNING clause in the SQL * id: 110, * name: 'My Name', * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }, function onResults(error, output) { * //continue flow... * }); * * //another example but with promise support * connection.update('UPDATE mylobs SET name = :name, clob_column1 = EMPTY_CLOB(), blob_column2 = EMPTY_BLOB() WHERE id = :id', { //no need to specify the RETURNING clause in the SQL * id: 110, * name: 'My Name', * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }).then(function (results) { * console.log(results.rowsAffected); * }); * ``` */ Connection.prototype.update = function (sql, bindParams, options, callback) { return this.insertOrUpdate(false, sql, bindParams, options, callback); }; /*eslint-disable valid-jsdoc*/ //jscs:disable jsDoc /** * Internal function which handles both INSERT and UPDATE commands. * * @function * @memberof! Connection * @private * @param {Boolean} insert - True for insert, false for update * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - The bind parameters used to specify the values for the columns * @param {Object} [options] - Any execute options * @param {AsyncCallback} [callback] - Invoked with an error or the results * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.insertOrUpdate(true, 'INSERT INTO mylobs (id, clob_column1, blob_column2) VALUES (:id, EMPTY_CLOB(), EMPTY_BLOB())', { //no need to specify the RETURNING clause in the SQL * id: 110, * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }, function onResults(error, output) { * //continue flow... * }); * * connection.insertOrUpdate(false, 'UPDATE mylobs SET name = :name, clob_column1 = EMPTY_CLOB(), blob_column2 = EMPTY_BLOB() WHERE id = :id', { //no need to specify the RETURNING clause in the SQL * id: 110, * name: 'My Name', * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }, function onResults(error, output) { * //continue flow... * }); * ``` */ Connection.prototype.insertOrUpdate = function (insert, sql, bindParams, options, callback) { const self = this; const input = self.getExecuteArguments(sql, bindParams, options, callback); sql = input.sql; bindParams = input.bindParams; options = input.options; callback = input.callback; let lobInfo; let lobData; let operation = 'execute'; const useExecuteMany = (options && options.useExecuteMany && self.executeMany && typeof self.executeMany === 'function'); if (useExecuteMany) { operation = 'executeMany'; lobInfo = self.modifyParams(sql, bindParams[0], options); lobData = [ lobInfo.lobData ]; for (let index = 1; index < bindParams.length; index++) { const additionalLobInfo = self.modifyParams(sql, bindParams[index], options); //modify bind params for next rows as well lobData.push(additionalLobInfo.lobData); } self.generateBindDefinitions(options, bindParams); } else { lobInfo = self.modifyParams(sql, bindParams, options); lobData = lobInfo.lobData; } sql = lobInfo.sql; const lobColumns = lobInfo.lobColumns; const autoCommit = lobInfo.autoCommit; self[operation](sql, bindParams, options, function onExecute(error, results) { let wrapper = self.createModifyCallback(callback, autoCommit, results); if ((!error) && results) { if (results.rowsAffected >= 1 && lobColumns && lobColumns.length) { /*istanbul ignore else*/ if (useExecuteMany) { /*jshint ignore:start*/ wrapper = self.noop; const tasks = []; const createTask = function (outBinds, lobExecuteData) { return function handleResults(asyncCallback) { recordWriter.writeMultiple(outBinds, lobExecuteData, function onWriteDone(operationError, operationResults) { /*istanbul ignore else*/ if (!operationError && operationResults) { operationResults.rowsAffected = 1; } asyncCallback(operationError, operationResults); }); }; }; for (let index = 0; index < bindParams.length; index++) { tasks.push(createTask(results.outBinds[index], lobData[index])); } asyncLib.series(tasks, function onBatchDone(batchError, batchResults) { const batchCallback = self.createModifyCallback(callback, autoCommit, batchResults); batchCallback(batchError); }); /*jshint ignore:end*/ } else if (insert && (results.rowsAffected === 1)) { recordWriter.write(results.outBinds, lobData, wrapper); wrapper = self.noop; } else if (!insert) { recordWriter.writeMultiple(results.outBinds, lobData, wrapper); wrapper = self.noop; } } else if (useExecuteMany) { const updatedResults = []; for (let index = 0; index < bindParams.length; index++) { updatedResults.push({ outBinds: {}, rowsAffected: 1 }); } results = updatedResults; wrapper = self.createModifyCallback(callback, autoCommit, results); } } wrapper(error, results); }); }; //jscs:enable jsDoc /*eslint-enable valid-jsdoc*/ //add promise support Connection.prototype.insertOrUpdate = promiseHelper.promisify(Connection.prototype.insertOrUpdate); /*eslint-disable valid-jsdoc*/ //jscs:disable jsDoc /** * This function modifies the existing connection.release function by enabling the input * callback to be an optional parameter and providing ability to auto retry in case of any errors during release.<br> * The connection.release also has an alias connection.close for consistent close function naming to all relevant objects. * * @function * @memberof! Connection * @public * @param {Object} [options] - Optional options used to define error handling (retry is enabled only if options are provided) * @param {Number} [options.retryCount=10] - Optional number of retries in case of any error during the release * @param {Number} [options.retryInterval=250] - Optional interval in millies between retries * @param {Boolean} [options.force=false] - If force=true the connection.break will be called before trying to release to ensure all running activities are aborted * @param {function} [callback] - An optional release callback function (see oracledb docs) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @fires event:release * @example * ```js * connection.release(); //no callback needed * * //still possible to call with a release callback function * connection.release(function onRelease(error) { * if (error) { * //now what? * } * }); * * //retry release in case of errors is enabled if options are provided * connection.release({ * retryCount: 20, //retry max 20 times in case of errors (default is 10 if not provided) * retryInterval: 1000 //retry every 1 second (default is 250 millies if not provided) * }); * * //you can provide both retry options and callback (callback will be called only after all retries are done or in case connection was released) * connection.release({ * retryCount: 10, * retryInterval: 250, * force: true //break any running operation before running release * }, function onRelease(error) { * if (error) { * //now what? * } * }); * * //can also use close instead of release * connection.close({ * retryCount: 10, * retryInterval: 250 * }, function onRelease(error) { * if (error) { * //now what? * } * }); * ``` */ Connection.prototype.release = function (options, callback) { const self = this; if (options && (typeof options === 'function')) { callback = options; options = null; } let force; if (options) { options.retryCount = Math.max(options.retryCount || 10, 1); options.retryInterval = Math.max(options.retryInterval || 250, 1); force = options.force; } if (options) { asyncLib.retry({ times: options.retryCount, interval: options.retryInterval }, function attemptRelease(asyncRetryCallback) { asyncLib.series([ function breakOperations(asyncReleaseCallback) { if (force) { self.break(function onBreak(breakError) { if (breakError) { debug('Unable to break connection operations, ', breakError.stack); } self.rollback(function onRollback(rollbackError) { /*istanbul ignore next*/ if (rollbackError) { debug('Unable to rollback connection, ', rollbackError.stack); } asyncReleaseCallback(); //never pass this error to allow the release attempt }); }); } else { asyncReleaseCallback(); } }, function runRelease(asyncReleaseCallback) { self.baseRelease(function onRelease(error) { if (error) { debug('Unable to release connection, ', error.stack); } else { self.emit('release'); } asyncReleaseCallback(error); }); } ], asyncRetryCallback); }, callback); } else { self.baseRelease(function onRelease(error) { /*istanbul ignore else*/ if (!error) { self.emit('release'); } callback(error); }); } }; //jscs:enable jsDoc /*eslint-enable valid-jsdoc*/ //add promise support Connection.prototype.release = promiseHelper.promisify(Connection.prototype.release, { defaultCallback: true }); /** * Alias for connection.release, see connection.release for more info. * * @function * @memberof! Connection * @public * @param {Object} [options] - Optional options used to define error handling (retry is enabled only if options are provided) * @param {Number} [options.retryCount=10] - Optional number of retries in case of any error during the release * @param {Number} [options.retryInterval=250] - Optional interval in millies between retries * @param {Boolean} [options.force=false] - If force=true the connection.break will be called before trying to release to ensure all running activities are aborted * @param {function} [callback] - An optional release callback function (see oracledb docs) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @fires event:release */ Connection.prototype.close = Connection.prototype.release; /*eslint-disable valid-jsdoc*/ //jscs:disable jsDoc /** * Extends the connection.commit to prevent commit being invoked while in the middle of a transaction. * * @function * @memberof! Connection * @public * @param {function} [callback] - The commit callback function (see oracledb docs) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * //using callback * connection.commit(function onCommit(error) { * //do something... * }); * * //or you can use a promise * connection.commit().then(function () { * //commit done.... * }).catch(function (error) { * //commit failed... * }); * ``` */ Connection.prototype.commit = function (callback) { if (this.inTransaction) { callback(new Error('Connection is in the middle of a transaction.')); } else { this.baseCommit(callback); } }; //jscs:enable jsDoc /*eslint-enable valid-jsdoc*/ //add promise support Connection.prototype.commit = promiseHelper.promisify(Connection.prototype.commit); /*eslint-disable valid-jsdoc*/ //jscs:disable jsDoc /** * This function modifies the existing connection.rollback function by enabling the input * callback to be an optional parameter.<br> * If rollback fails, you can't really rollback again the data, so the callback is not always needed.<br> * Therefore this function allows you to ignore the need to pass a callback and makes it as an optional parameter. * * @function * @memberof! Connection * @public * @param {function} [callback] - An optional rollback callback function (see oracledb docs) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.rollback(); //no callback needed * * //still possible to call with a rollback callback function * connection.rollback(function onRollback(error) { * if (error) { * //now what? * } * }); * ``` */ Connection.prototype.rollback = function (callback) { if (this.inTransaction) { callback(new Error('Connection is in the middle of a transaction.')); } else { this.baseRollback(callback); } }; //jscs:enable jsDoc /*eslint-enable valid-jsdoc*/ //add promise support Connection.prototype.rollback = promiseHelper.promisify(Connection.prototype.rollback, { defaultCallback: true }); /*eslint-disable valid-jsdoc*/ //jscs:disable jsDoc /** * This function will invoke the provided SQL SELECT and return a results object with the returned row count and the JSONs.<br> * The json property will hold a single JSON object in case the returned row count is 1, and an array of JSONs in case the row count is higher.<br> * The query expects that only 1 column is fetched and if more are detected in the results, this function will return an error in the callback.<br> * The function arguments used to execute the 'queryJSON' are exactly as defined in the oracledb connection.execute function. * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Object} [bindParams] - Optional bind parameters * @param {Object} [options] - Optional execute options * @param {AsyncCallback} [callback] - Invoked with an error or the query results object holding the row count and JSONs * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.queryJSON('SELECT JSON_DATA FROM APP_CONFIG WHERE ID > :id', [110], function onResults(error, results) { * if (error) { * //handle error... * } else if (results.rowCount === 1) { //single JSON is returned * //print the JSON * console.log(results.json); * } else if (results.rowCount > 1) { //multiple JSONs are returned * //print the JSON * results.json.forEach(function printJSON(json) { * console.log(json); * }); * } else { * console.log('Did not find any results'); * } * }); * * //another example but with promise support * connection.queryJSON('SELECT JSON_DATA FROM APP_CONFIG WHERE ID > :id', [110]).then(function (results) { * if (results.rowCount === 1) { //single JSON is returned * //print the JSON * console.log(results.json); * } else if (results.rowCount > 1) { //multiple JSONs are returned * //print the JSON * results.json.forEach(function printJSON(json) { * console.log(json); * }); * } * }); * ``` */ Connection.prototype.queryJSON = function (sql, bindParams, options, callback) { const self = this; const input = self.getExecuteArguments(sql, bindParams, options, callback); sql = input.sql; bindParams = input.bindParams; options = input.options; callback = input.callback; const onExecute = function (error, jsRows) { if (error) { callback(error); } else if ((!jsRows) || (!jsRows.length)) { callback(null, { rowCount: 0, json: [] }); } else { let callbackCalled = false; let json; try { json = rowsReader.readJSON(jsRows); } catch (parseError) { callbackCalled = true; callback(parseError); } if (!callbackCalled) { const output = { rowCount: jsRows.length, json }; if (output.json.length === 1) { output.json = output.json[0]; } callback(null, output); } } }; if (options) { self.query(sql, bindParams, options, onExecute); } else if (bindParams) { self.query(sql, bindParams, onExecute); } else { self.query(sql, onExecute); } }; //jscs:enable jsDoc /*eslint-enable valid-jsdoc*/ //add promise support Connection.prototype.queryJSON = promiseHelper.promisify(Connection.prototype.queryJSON, { force: true }); /** * Enables to run an INSERT SQL statement multiple times for each of the provided bind params.<br> * This allows to insert to same table multiple different rows with one single call.<br> * The callback output will be an array of objects of same as oracledb connection.execute (per row).<br> * All LOBs for all rows will be written to the DB via streams and only after all LOBs are written the callback will be called.<br> * The function arguments used to execute the 'insert' are exactly as defined in the oracledb connection.execute function, however the options are mandatory and * the bind params is now an array of bind params (one per row). * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Array} bindParamsArray - An array of instances of object/Array bind parameters used to specify the values for the columns per row * @param {Object} options - Any execute options * @param {Boolean} [options.autoCommit] - If you wish to commit after the update, this property must be set to true in the options (oracledb.autoCommit is not checked) * @param {Object} [options.lobMetaInfo] - For LOB support this object must hold a mapping between DB column name and bind constiable name * @param {Object} [options.returningInfo] - columnName/bindconstName pairs which will be added to the RETURNING ... INTO ... clause (only used if lobMetaInfo is provided), see connection.insert example * @param {Boolean} [options.useExecuteMany=true] - If true and connection.executeMany is supported, it will be used, otherwise this function will call execute per bind values row * @param {AsyncCallback} [callback] - Invoked with an error or the insert results (if LOBs are provided, the callback will be triggered after they have been fully written to the DB) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.batchInsert('INSERT INTO mylobs (id, clob_column1, blob_column2) VALUES (:id, EMPTY_CLOB(), EMPTY_BLOB())', [ //no need to specify the RETURNING clause in the SQL * { //first row values * id: 110, * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, * { //second row values * id: 111, * clobText1: 'second row', * blobBuffer2: new Buffer('second rows') * } * ], { * autoCommit: true, //must be set to true in options to support auto commit after insert is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }, function onResults(error, output) { * //continue flow... * }); * ``` */ Connection.prototype.batchInsert = function (sql, bindParamsArray, options, callback) { return this.batchInsertOrUpdate(true, sql, bindParamsArray, options, callback); }; /** * Enables to run an UPDATE SQL statement multiple times for each of the provided bind params.<br> * This allows to update to same table multiple different rows with one single call.<br> * The callback output will be an array of objects of same as oracledb connection.execute (per row).<br> * All LOBs for all rows will be written to the DB via streams and only after all LOBs are written the callback will be called.<br> * The function arguments used to execute the 'update' are exactly as defined in the oracledb connection.execute function, however the options are mandatory and * the bind params is now an array of bind params (one per row). * * @function * @memberof! Connection * @public * @param {String} sql - The SQL to execute * @param {Array} bindParamsArray - An array of instances of object/Array bind parameters used to specify the values for the columns per row * @param {Object} options - Any execute options * @param {Boolean} [options.autoCommit] - If you wish to commit after the update, this property must be set to true in the options (oracledb.autoCommit is not checked) * @param {Object} [options.lobMetaInfo] - For LOB support this object must hold a mapping between DB column name and bind constiable name * @param {Object} [options.returningInfo] - columnName/bindconstName pairs which will be added to the RETURNING ... INTO ... clause (only used if lobMetaInfo is provided), see connection.insert example * @param {Boolean} [options.useExecuteMany=true] - If true and connection.executeMany is supported, it will be used, otherwise this function will call execute per bind values row * @param {AsyncCallback} [callback] - Invoked with an error or the update results (if LOBs are provided, the callback will be triggered after they have been fully written to the DB) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * @example * ```js * connection.batchUpdate('UPDATE mylobs SET name = :name, clob_column1 = EMPTY_CLOB(), blob_column2 = EMPTY_BLOB() WHERE id = :id', [ //no need to specify the RETURNING clause in the SQL * { //first row values * id: 110, * clobText1: 'some long clob string', //add bind constiable with LOB column name and text content (need to map that name in the options) * blobBuffer2: new Buffer('some blob content, can be binary...') //add bind constiable with LOB column name and text content (need to map that name in the options) * }, * { //second row values * id: 111, * clobText1: 'second row', * blobBuffer2: new Buffer('second rows') * } * ], { * autoCommit: true, //must be set to true in options to support auto commit after update is done, otherwise the auto commit will be false (oracledb.autoCommit is not checked) * lobMetaInfo: { //if LOBs are provided, this data structure must be provided in the options object and the bind constiables parameter must be an object (not array) * clob_column1: 'clobText1', //map oracle column name to bind constiable name * blob_column2: 'blobBuffer2' * } * }, function onResults(error, output) { * //continue flow... * }); * ``` */ Connection.prototype.batchUpdate = function (sql, bindParamsArray, options, callback) { return this.batchInsertOrUpdate(false, sql, bindParamsArray, options, callback); }; /*eslint-disable valid-jsdoc*/ //jscs:disable jsDoc /** * Internal function to run batch INSERT/UPDATE commands. * * @function * @memberof! Connection * @private * @param {Boolean} insert - True for insert, false for update * @param {String} sql - The SQL to execute * @param {Array} bindParamsArray - An array of instances of object/Array bind parameters used to specify the values for the columns per row * @param {Object} options - Any execute options * @param {Boolean} [options.autoCommit] - If you wish to commit after the insert/update, this property must be set to true in the options (oracledb.autoCommit is not checked) * @param {Object} [options.lobMetaInfo] - For LOB support this object must hold a mapping between DB column name and bind constiable name * @param {Object} [options.returningInfo] - columnName/bindconstName pairs which will be added to the RETURNING ... INTO ... clause (only used if lobMetaInfo is provided), see connection.insert example * @param {Boolean} [options.useExecuteMany=true] - If true and connection.executeMany is supported, it will be used, otherwise this function will call execute per bind values row * @param {AsyncCallback} [callback] - Invoked with an error or the insert/update results (if LOBs are provided, the callback will be triggered after they have been fully written to the DB) * @returns {Promise} In case of no callback provided in input and promise is supported, this function will return a promise * ``` */ Connection.prototype.batchInsertOrUpdate = function (insert, sql, bindParamsArray, options, callback) { const self = this; let operation = 'update'; if (insert) { operation = 'insert'; } self.setupDefaultOptions(options, { batch: true }); options.useExecuteMany = ((options.forceUseExecuteMany || constants.features.executeManySupport) && options.useExecuteMany && self.executeMany && typeof self.executeMany === 'function'); if (options.useExecuteMany) { options.autoCommit = (options.autoCommit && !self.inTransaction); self[operation](sql, bindParamsArray, options, callback); } else { delete options.useExecuteMany; //for backward compatibility //auto commit should wrap all commands let commit = options.autoCommit; options.autoCommit = false; if (self.inTransaction) {