simple-oracledb
Version:
Extend capabilities of oracledb with simplified API for quicker development.
1,123 lines (1,038 loc) • 78.5 kB
JavaScript
'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) {