
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// Copyright (c) 2015, 2022, Oracle and/or its affiliates.
//-----------------------------------------------------------------------------
//
// You may not use the identified files except in compliance with the Apache
// License, Version 2.0 (the "License.")
//
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-----------------------------------------------------------------------------
'use strict';
const nodbUtil = require('./util.js');
const util = require('util');
// This version of node-oracledb works with Node.js 8.16, 10.16 or
// later. The test stops hard-to-interpret runtime errors and crashes
// with older Node.js versions. Also Node.js 8.16 and 10.16 (and
// 12.0) contain an important Node-API performance regression fix. If
// you're using the obsolete Node.js 9 or 11 versions, you're on your
// own regarding performance and functionality
let vs = process.version.substring(1).split(".").map(Number);
if (vs[0] < 8 || (vs[0] === 8 && vs[1] < 16)) {
throw new Error(nodbUtil.getErrorMessage('NJS-069', nodbUtil.PACKAGE_JSON_VERSION, "8.16"));
} else if ((vs[0] === 10 && vs[1] < 16)) {
throw new Error(nodbUtil.getErrorMessage('NJS-069', nodbUtil.PACKAGE_JSON_VERSION, "10.16"));
}
const AqDeqOptions = require('./aqDeqOptions.js');
const AqEnqOptions = require('./aqEnqOptions.js');
const AqMessage = require('./aqMessage.js');
const AqQueue = require('./aqQueue.js');
const BaseDbObject = require('./dbObject.js');
const Connection = require('./connection.js');
const Lob = require('./lob.js');
const Pool = require('./pool.js');
const PoolStatistics = require('./poolStatistics.js');
const ResultSet = require('./resultset.js');
const SodaDatabase = require('./sodaDatabase.js');
const SodaCollection = require('./sodaCollection.js');
const SodaDocCursor = require('./sodaDocCursor.js');
const SodaDocument = require('./sodaDocument.js');
const SodaOperation = require('./sodaOperation.js');
let poolCache = {};
let tempUsedPoolAliases = {};
const defaultPoolAlias = 'default';
// Load the Oracledb binary
/*global __non_webpack_require__*/ // quieten eslint
const requireBinary = (typeof __non_webpack_require__ === 'function') ? __non_webpack_require__ : require; // See Issue 1156
const binaryLocations = [
'../' + nodbUtil.RELEASE_DIR + '/' + nodbUtil.BINARY_FILE, // pre-built binary
'../' + nodbUtil.RELEASE_DIR + '/' + 'oracledb.node', // binary built from source
'../build/Debug/oracledb.node', // debug binary
// For Webpack. A Webpack copy plugin is still needed to copy 'node_modules/oracledb/build/' to the output directory
// See https://github.com/oracle/node-oracledb/issues/1156
'./node_modules/oracledb/' + nodbUtil.RELEASE_DIR + '/' + nodbUtil.BINARY_FILE,
'./node_modules/oracledb/' + nodbUtil.RELEASE_DIR + '/' + 'oracledb.node'
];
let oracledbCLib;
for (let i = 0; i < binaryLocations.length; i++) {
try {
oracledbCLib = requireBinary(binaryLocations[i]);
break;
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND' || i == binaryLocations.length - 1) {
let nodeInfo;
if (err.code === 'MODULE_NOT_FOUND') {
// A binary was not found in any of the search directories.
// Note this message may not be accurate for Webpack users since Webpack changes __dirname
nodeInfo = `\n Looked for ${binaryLocations.map(x => require('path').resolve(__dirname, x)).join(', ')}\n ${nodbUtil.getInstallURL()}\n`;
} else {
nodeInfo = `\n Node.js require('oracledb') error was:\n ${err.message}\n ${nodbUtil.getInstallHelp()}\n`;
}
throw new Error(nodbUtil.getErrorMessage('NJS-045', nodeInfo));
}
}
}
class OracleDb {
constructor() {
this.queueTimeout = 60000;
this.queueMax = 500;
this.errorOnConcurrentExecute = false;
}
// extend class with promisified functions
_extend(_oracledb) {
this.getConnection = nodbUtil.callbackify(getConnection).bind(_oracledb);
this.createPool = nodbUtil.callbackify(createPool).bind(_oracledb);
this.shutdown = nodbUtil.callbackify(shutdown).bind(_oracledb);
this.startup = nodbUtil.callbackify(startup).bind(_oracledb);
this.initOracleClient = this.initOracleClient.bind(_oracledb);
this._accessTokenHandler = this._accessTokenHandler.bind(_oracledb);
}
// temporary method for determining if an object is a date until
// napi_is_date() can be used (when Node-API v5 can be used)
_isDate(val) {
return util.isDate(val);
}
// handler for access token callbacks
async _accessTokenHandler(userFn, externalObj, refresh) {
let accessToken;
try {
let result = userFn(refresh);
if (result instanceof Promise) {
result = await result;
}
_checkToken(result);
accessToken = result;
} finally {
this._returnAccessToken(externalObj, accessToken);
}
}
// retrieves a pool from the pool cache (synchronous method)
getPool(poolAlias) {
let pool;
nodbUtil.checkArgCount(arguments, 0, 1);
if (poolAlias) {
nodbUtil.assert(typeof poolAlias === 'string' || typeof poolAlias === 'number', 'NJS-005', 1);
}
poolAlias = poolAlias || defaultPoolAlias;
pool = poolCache[poolAlias];
if (!pool) {
throw new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias));
}
return pool;
}
initOracleClient(arg1) {
let options = {};
nodbUtil.checkArgCount(arguments, 0, 1);
if (arg1 !== undefined) {
nodbUtil.assert(nodbUtil.isObject(arg1), 'NJS-005', 1);
options = arg1;
}
this._initOracleClient(options);
}
}
// Oracledb functions and classes
//-----------------------------------------------------------------------------
// createPool()
// Create a pool with the specified options and return it to the caller.
//-----------------------------------------------------------------------------
async function createPool(poolAttrs) {
let poolAlias;
// check arguments
nodbUtil.checkArgCount(arguments, 1, 1);
nodbUtil.assert(nodbUtil.isObject(poolAttrs), 'NJS-005', 1);
if (poolAttrs.poolAlias !== undefined) {
if (typeof poolAttrs.poolAlias !== 'string' ||
poolAttrs.poolAlias.length === 0) {
throw new Error(nodbUtil.getErrorMessage('NJS-004',
'poolAttrs.poolAlias'));
}
poolAlias = poolAttrs.poolAlias;
} else if (poolAttrs.poolAlias === undefined
&& !poolCache[defaultPoolAlias]
&& !tempUsedPoolAliases[defaultPoolAlias]) {
poolAlias = defaultPoolAlias;
}
if (poolCache[poolAlias] || tempUsedPoolAliases[poolAlias]) {
throw new Error(nodbUtil.getErrorMessage('NJS-046', poolAlias));
}
if (poolAttrs.accessToken !== undefined) {
// cannot set username or password for token based authentication
if (poolAttrs.user !== undefined ||
poolAttrs.password !== undefined) {
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
}
// homogeneous and externalAuth must be set to true for token based
// authentication
if (poolAttrs.homogeneous === false ||
poolAttrs.externalAuth === false) {
throw new Error(nodbUtil.getErrorMessage('NJS-085'));
}
}
// create an adjusted set of pool attributes to pass to the C layer; the
// session callback must be removed if it is a JavaScript function and the
// queue timeout is used to specify the maximum amount of time that the C
// layer will wait for a connection to be returned; ordinarily since the
// JavaScript layer never calls the C layer to get a connection unless one is
// known to be available, this should not be needed, but in some cases (such
// as when the maximum for a particular shard is specified) this may not be
// known, so this prevents an unnecessarily long wait from taking place
const adjustedPoolAttrs = Object.defineProperties({},
Object.getOwnPropertyDescriptors(poolAttrs));
if (typeof poolAttrs.sessionCallback === 'function') {
delete adjustedPoolAttrs.sessionCallback;
}
if (adjustedPoolAttrs.queueTimeout === undefined) {
adjustedPoolAttrs.queueTimeout = this.queueTimeout;
}
// token based authentication
if (poolAttrs.accessToken !== undefined) {
// accessTokenCallback is depricated from node-oracledb 5.5
if (poolAttrs.accessTokenCallback !== undefined &&
typeof poolAttrs.accessToken === 'function') {
throw new Error(nodbUtil.getErrorMessage('NJS-088'));
}
let accessToken;
if (typeof poolAttrs.accessToken === 'function') {
adjustedPoolAttrs.accessTokenCallback = poolAttrs.accessToken;
accessToken = await poolAttrs.accessToken(false);
if (_isTokenExpired(accessToken)) {
accessToken = await poolAttrs.accessToken(true);
}
} else {
accessToken = poolAttrs.accessToken;
}
_checkToken(accessToken, adjustedPoolAttrs);
}
// Need to prevent another call in the same stack from succeeding, otherwise
// two pools could be created with the same poolAlias and the second one that
// comes back would overwrite the first in the cache.
if (poolAlias) {
tempUsedPoolAliases[poolAlias] = true;
}
try {
const pool = await this._createPool(adjustedPoolAttrs);
if (poolAlias) {
poolCache[poolAlias] = pool;
// It's now safe to remove this alias from the tempUsedPoolAliases.
delete tempUsedPoolAliases[poolAlias];
}
pool._setup(poolAttrs, poolAlias, this);
pool.on('_afterPoolClose', () => {
if (pool.poolAlias) {
delete poolCache[pool.poolAlias];
}
});
return pool;
} catch (err) {
// We need to free this up since the creation of the pool failed.
if (poolAlias) {
delete tempUsedPoolAliases[poolAlias];
}
// add installation help instructions to error message, if applicable
if (err.message.match(/DPI-1047/)) {
err.message += "\n" + nodbUtil.getInstallHelp();
}
throw err;
}
}
//-----------------------------------------------------------------------------
// getConnection()
// Gets either a standalone connection, or a connection from a pool cache
//-----------------------------------------------------------------------------
async function getConnection(a1) {
let pool;
let poolAlias;
let connAttrs = {};
// verify the number and types of arguments
nodbUtil.checkArgCount(arguments, 0, 1);
if (arguments.length == 0) {
poolAlias = defaultPoolAlias;
} else {
nodbUtil.assert(typeof a1 === 'string' || nodbUtil.isObject(a1),
'NJS-005', 1);
if (typeof a1 === 'string') {
poolAlias = a1;
} else {
connAttrs = a1;
if (connAttrs.poolAlias) {
poolAlias = connAttrs.poolAlias;
}
}
}
// if a pool alias is available, acquire a connection from the specified pool
if (poolAlias) {
pool = poolCache[poolAlias];
if (!pool) {
throw new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias));
}
return await pool.getConnection(connAttrs);
// otherwise, create a new standalone connection
} else {
if (connAttrs.accessToken !== undefined) {
// cannot set username or password for token based authentication
if (connAttrs.user !== undefined ||
connAttrs.password !== undefined) {
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
}
// externalAuth must be set to true for token based authentication
if (connAttrs.externalAuth === false) {
throw new Error(nodbUtil.getErrorMessage('NJS-086'));
}
let accessToken;
if (typeof connAttrs.accessToken === 'function') {
accessToken = await connAttrs.accessToken(false);
if (_isTokenExpired(accessToken)) {
accessToken = await connAttrs.accessToken(true);
}
} else {
accessToken = connAttrs.accessToken;
}
_checkToken(accessToken, connAttrs);
}
try {
return await this._getConnection(connAttrs);
} catch (err) {
if (err.message.match(/DPI-1047/)) {
err.message += "\n" + nodbUtil.getInstallHelp();
}
throw err;
}
}
}
//-----------------------------------------------------------------------------
// shutdown()
// Shuts down the database.
//-----------------------------------------------------------------------------
async function shutdown(a1, a2) {
let connAttr = {};
let shutdownMode = this.SHUTDOWN_MODE_DEFAULT;
// verify the number and types of arguments
nodbUtil.checkArgCount(arguments, 0, 2);
if (arguments.length == 2) {
nodbUtil.assert(typeof a1 === 'object', 'NJS-005', 1);
nodbUtil.assert(typeof a2 === 'number', 'NJS-005', 2);
connAttr = a1;
shutdownMode = a2;
} else if (arguments.length == 1) {
nodbUtil.assert(typeof a1 === 'object', 'NJS-005', 1);
connAttr = a1;
}
// only look for the keys that are used for shutting down the database
// use SYSOPER privilege
const dbConfig = {
user: connAttr.user,
password: connAttr.password,
connectString: connAttr.connectString,
connectionString: connAttr.connectionString,
externalAuth: connAttr.externalAuth,
privilege: this.SYSOPER
};
const conn = await this.getConnection(dbConfig);
await conn.shutdown(shutdownMode);
if (shutdownMode != this.SHUTDOWN_MODE_ABORT) {
await conn.execute("ALTER DATABASE CLOSE");
await conn.execute("ALTER DATABASE DISMOUNT");
await conn.shutdown(this.SHUTDOWN_MODE_FINAL);
}
await conn.close();
}
//-----------------------------------------------------------------------------
// startup()
// Starts up the database.
//-----------------------------------------------------------------------------
async function startup(a1, a2) {
let connAttr = {};
let startupAttr = {};
// verify the number and types of arguments
nodbUtil.checkArgCount(arguments, 0, 2);
if (arguments.length == 2) {
nodbUtil.assert (typeof a1 === 'object', 'NJS-005', 1);
nodbUtil.assert (typeof a2 === 'object', 'NJS-005', 2);
connAttr = a1;
startupAttr = a2;
} else if (arguments.length == 1) {
nodbUtil.assert(typeof a1 === 'object', 'NJS-005', 1);
connAttr = a1;
}
// only look for the keys that are used for starting up the database
// use SYSOPER and SYSPRELIM privileges
const dbConfig = {
user: connAttr.user,
password: connAttr.password,
connectString: connAttr.connectString,
connectionString: connAttr.connectionString,
externalAuth: connAttr.externalAuth,
privilege: this.SYSOPER | this.SYSPRELIM
};
let conn = await this.getConnection(dbConfig);
await conn.startup(startupAttr);
await conn.close();
dbConfig.privilege = this.SYSOPER;
conn = await this.getConnection(dbConfig);
await conn.execute("ALTER DATABASE MOUNT");
await conn.execute("ALTER DATABASE OPEN");
await conn.close();
}
//-----------------------------------------------------------------------------
// _isTokenExpiredUtil()
// Function to check validity and expiry of token
//-----------------------------------------------------------------------------
function _isTokenExpiredUtil(accessToken) {
nodbUtil.assert(typeof accessToken === 'string', 'NJS-084', 1);
if (accessToken.split('.')[1] === undefined) {
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
}
const base64Url = accessToken.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const buff = Buffer.from(base64, 'base64');
const payloadInit = buff.toString('ascii');
let expiry = JSON.parse(payloadInit).exp;
nodbUtil.assert(expiry != undefined, 'NJS-084');
expiry = expiry * 1000;
return (new Date().getTime() > expiry);
}
//-----------------------------------------------------------------------------
// _isTokenExpired()
// Function to check validity of token parameter
//-----------------------------------------------------------------------------
function _isTokenExpired(accessToken) {
switch (typeof accessToken) {
case 'string':
if (accessToken === '') {
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
}
return _isTokenExpiredUtil(accessToken);
case 'object':
if (accessToken.token === undefined ||
accessToken.token === '' ||
accessToken.privateKey === undefined ||
accessToken.privateKey === '') {
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
}
return _isTokenExpiredUtil(accessToken.token);
default:
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
}
}
//-----------------------------------------------------------------------------
// _checkToken()
// Function to check validity of token parameter
//-----------------------------------------------------------------------------
function _checkToken(accessToken, attrs) {
if (_isTokenExpired(accessToken)) {
throw new Error(nodbUtil.getErrorMessage('NJS-087'));
}
if (attrs === undefined)
return;
if (typeof accessToken === 'string') {
attrs.token = accessToken;
} else {
attrs.token = accessToken.token;
attrs.privateKey = accessToken.privateKey;
}
}
// create instance which will be exported
let oracleDbInst = new OracleDb();
// add classes to prototype
let proto = Object.getPrototypeOf(oracleDbInst);
proto.OracleDb = OracleDb;
proto.AqDeqOptions = AqDeqOptions;
proto.AqEnqOptions = AqEnqOptions;
proto.AqMessage = AqMessage;
proto.AqQueue = AqQueue;
proto.BaseDbObject = BaseDbObject;
proto.Connection = Connection;
proto.Lob = Lob;
proto.Pool = Pool;
proto.PoolStatistics = PoolStatistics;
proto.ResultSet = ResultSet;
proto.SodaDatabase = SodaDatabase;
proto.SodaCollection = SodaCollection;
proto.SodaDocCursor = SodaDocCursor;
proto.SodaDocument = SodaDocument;
proto.SodaOperation = SodaOperation;
// call C to extend classes
oracledbCLib.init(oracleDbInst);
module.exports = oracleDbInst;