2016-06-20 17:03:52 +02:00
|
|
|
// Oracledb Client
|
|
|
|
// -------
|
2020-04-18 20:41:23 +03:00
|
|
|
const each = require('lodash/each');
|
|
|
|
const flatten = require('lodash/flatten');
|
|
|
|
const isEmpty = require('lodash/isEmpty');
|
|
|
|
const map = require('lodash/map');
|
2021-01-07 17:48:14 +02:00
|
|
|
|
2021-01-09 17:40:30 +02:00
|
|
|
const Formatter = require('../../formatter');
|
2021-01-07 23:34:46 +02:00
|
|
|
const QueryCompiler = require('./query/oracledb-querycompiler');
|
2021-09-03 15:25:22 -04:00
|
|
|
const TableCompiler = require('./schema/oracledb-tablecompiler');
|
2021-01-01 17:46:10 +02:00
|
|
|
const ColumnCompiler = require('./schema/oracledb-columncompiler');
|
2021-10-20 23:35:51 +03:00
|
|
|
const {
|
|
|
|
BlobHelper,
|
|
|
|
ReturningHelper,
|
|
|
|
monkeyPatchConnection,
|
|
|
|
} = require('./utils');
|
2021-10-20 22:23:29 +02:00
|
|
|
const ViewCompiler = require('./schema/oracledb-viewcompiler');
|
|
|
|
const ViewBuilder = require('./schema/oracledb-viewbuilder');
|
2016-06-20 17:03:52 +02:00
|
|
|
const Transaction = require('./transaction');
|
2016-09-13 08:15:58 -04:00
|
|
|
const Client_Oracle = require('../oracle');
|
2020-10-05 21:29:39 +03:00
|
|
|
const { isString } = require('../../util/is');
|
2021-01-09 17:40:30 +02:00
|
|
|
const { outputQuery, unwrapRaw } = require('../../formatter/wrappingFormatter');
|
|
|
|
const { compileCallback } = require('../../formatter/formatterUtils');
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
class Client_Oracledb extends Client_Oracle {
|
|
|
|
constructor(config) {
|
|
|
|
super(config);
|
2023-10-16 17:31:38 -06:00
|
|
|
|
|
|
|
if (this.version) {
|
|
|
|
// Normalize version format; null bad format
|
|
|
|
// to trigger fallback to auto-detect.
|
|
|
|
this.version = parseVersion(this.version);
|
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
if (this.driver) {
|
|
|
|
process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || 1;
|
|
|
|
process.env.UV_THREADPOOL_SIZE =
|
|
|
|
parseInt(process.env.UV_THREADPOOL_SIZE) + this.driver.poolMax;
|
|
|
|
}
|
2016-06-20 17:03:52 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
_driver() {
|
|
|
|
const client = this;
|
|
|
|
const oracledb = require('oracledb');
|
|
|
|
client.fetchAsString = [];
|
|
|
|
if (this.config.fetchAsString && Array.isArray(this.config.fetchAsString)) {
|
|
|
|
this.config.fetchAsString.forEach(function (type) {
|
|
|
|
if (!isString(type)) return;
|
|
|
|
type = type.toUpperCase();
|
|
|
|
if (oracledb[type]) {
|
|
|
|
if (
|
|
|
|
type !== 'NUMBER' &&
|
|
|
|
type !== 'DATE' &&
|
|
|
|
type !== 'CLOB' &&
|
|
|
|
type !== 'BUFFER'
|
|
|
|
) {
|
|
|
|
this.logger.warn(
|
|
|
|
'Only "date", "number", "clob" and "buffer" are supported for fetchAsString'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
client.fetchAsString.push(oracledb[type]);
|
2017-04-28 11:47:24 +01:00
|
|
|
}
|
2021-01-31 13:40:13 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return oracledb;
|
2017-04-28 11:47:24 +01:00
|
|
|
}
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
queryCompiler(builder, formatter) {
|
|
|
|
return new QueryCompiler(this, builder, formatter);
|
|
|
|
}
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-09-03 15:25:22 -04:00
|
|
|
tableCompiler() {
|
|
|
|
return new TableCompiler(this, ...arguments);
|
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
columnCompiler() {
|
|
|
|
return new ColumnCompiler(this, ...arguments);
|
|
|
|
}
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-10-20 22:23:29 +02:00
|
|
|
viewBuilder() {
|
|
|
|
return new ViewBuilder(this, ...arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
viewCompiler() {
|
|
|
|
return new ViewCompiler(this, ...arguments);
|
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
formatter(builder) {
|
|
|
|
return new Formatter(this, builder);
|
2021-01-09 17:40:30 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
transaction() {
|
|
|
|
return new Transaction(this, ...arguments);
|
2020-12-08 00:47:00 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
prepBindings(bindings) {
|
|
|
|
return map(bindings, (value) => {
|
|
|
|
if (value instanceof BlobHelper && this.driver) {
|
|
|
|
return { type: this.driver.BLOB, dir: this.driver.BIND_OUT };
|
|
|
|
// Returning helper always use ROWID as string
|
|
|
|
} else if (value instanceof ReturningHelper && this.driver) {
|
|
|
|
return { type: this.driver.STRING, dir: this.driver.BIND_OUT };
|
|
|
|
} else if (typeof value === 'boolean') {
|
|
|
|
return value ? 1 : 0;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
});
|
2020-12-08 00:47:00 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
// Checks whether a value is a function... if it is, we compile it
|
|
|
|
// otherwise we check whether it's a raw
|
|
|
|
parameter(value, builder, formatter) {
|
|
|
|
if (typeof value === 'function') {
|
|
|
|
return outputQuery(
|
|
|
|
compileCallback(value, undefined, this, formatter),
|
|
|
|
true,
|
|
|
|
builder,
|
|
|
|
this
|
|
|
|
);
|
|
|
|
} else if (value instanceof BlobHelper) {
|
2022-05-13 15:31:15 -04:00
|
|
|
formatter.bindings.push(value.value);
|
|
|
|
return '?';
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
|
|
|
return unwrapRaw(value, true, builder, this, formatter) || '?';
|
|
|
|
}
|
2020-12-08 00:47:00 +02:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
// Get a raw connection, called by the `pool` whenever a new
|
|
|
|
// connection needs to be added to the pool.
|
|
|
|
acquireRawConnection() {
|
2023-10-16 17:31:38 -06:00
|
|
|
return new Promise((resolver, rejecter) => {
|
2021-01-31 13:40:13 +03:00
|
|
|
// If external authentication don't have to worry about username/password and
|
|
|
|
// if not need to set the username and password
|
2023-10-16 17:31:38 -06:00
|
|
|
const oracleDbConfig = this.connectionSettings.externalAuth
|
|
|
|
? { externalAuth: this.connectionSettings.externalAuth }
|
2021-01-31 13:40:13 +03:00
|
|
|
: {
|
2023-10-16 17:31:38 -06:00
|
|
|
user: this.connectionSettings.user,
|
|
|
|
password: this.connectionSettings.password,
|
2021-01-31 13:40:13 +03:00
|
|
|
};
|
2016-11-30 20:44:43 +13:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
// In the case of external authentication connection string will be given
|
|
|
|
oracleDbConfig.connectString = resolveConnectString(
|
2023-10-16 17:31:38 -06:00
|
|
|
this.connectionSettings
|
2021-01-31 13:40:13 +03:00
|
|
|
);
|
2016-11-30 20:44:43 +13:00
|
|
|
|
2023-10-16 17:31:38 -06:00
|
|
|
if (this.connectionSettings.prefetchRowCount) {
|
|
|
|
oracleDbConfig.prefetchRows = this.connectionSettings.prefetchRowCount;
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
2016-11-30 20:44:43 +13:00
|
|
|
|
2023-10-16 17:31:38 -06:00
|
|
|
if (this.connectionSettings.stmtCacheSize !== undefined) {
|
|
|
|
oracleDbConfig.stmtCacheSize = this.connectionSettings.stmtCacheSize;
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
2016-10-14 17:00:39 +02:00
|
|
|
|
2023-10-16 17:31:38 -06:00
|
|
|
this.driver.fetchAsString = this.fetchAsString;
|
2017-04-28 11:47:24 +01:00
|
|
|
|
2023-10-16 17:31:38 -06:00
|
|
|
this.driver.getConnection(oracleDbConfig, (err, connection) => {
|
2021-01-31 13:40:13 +03:00
|
|
|
if (err) {
|
|
|
|
return rejecter(err);
|
2019-10-15 09:23:07 +03:00
|
|
|
}
|
2023-10-16 17:31:38 -06:00
|
|
|
monkeyPatchConnection(connection, this);
|
2019-10-12 22:39:34 +03:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
resolver(connection);
|
|
|
|
});
|
2016-06-20 17:03:52 +02:00
|
|
|
});
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
2019-10-12 22:39:34 +03:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
// Used to explicitly close a connection, called internally by the pool
|
|
|
|
// when a connection times out or the pool is shutdown.
|
|
|
|
destroyRawConnection(connection) {
|
|
|
|
return connection.release();
|
2019-10-12 22:39:34 +03:00
|
|
|
}
|
|
|
|
|
2023-10-16 17:31:38 -06:00
|
|
|
// Handle oracle version resolution on acquiring connection from pool instead of connection creation.
|
|
|
|
// Must do this here since only the client used to create a connection would be updated with version
|
|
|
|
// information on creation. Poses a problem when knex instance is cloned since instances share the
|
|
|
|
// connection pool while having their own client instances.
|
|
|
|
async acquireConnection() {
|
|
|
|
const connection = await super.acquireConnection();
|
|
|
|
this.checkVersion(connection);
|
|
|
|
return connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In Oracle, we need to check the version to dynamically determine
|
|
|
|
// certain limits. If user did not specify a version, get it from the connection.
|
|
|
|
checkVersion(connection) {
|
|
|
|
// Already determined version before?
|
|
|
|
if (this.version) {
|
|
|
|
return this.version;
|
|
|
|
}
|
|
|
|
|
|
|
|
const detectedVersion = parseVersion(connection.oracleServerVersionString);
|
|
|
|
if (!detectedVersion) {
|
|
|
|
// When original version is set to null, user-provided version was invalid and we fell-back to auto-detect.
|
|
|
|
// Otherwise, we couldn't auto-detect at all. Set error message accordingly.
|
|
|
|
throw new Error(
|
|
|
|
this.version === null
|
|
|
|
? 'Invalid Oracledb version number format passed to knex. Unable to successfully auto-detect as fallback. Please specify a valid oracledb version.'
|
|
|
|
: 'Unable to detect Oracledb version number automatically. Please specify the version in knex configuration.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.version = detectedVersion;
|
|
|
|
return detectedVersion;
|
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
// Runs the query on the specified connection, providing the bindings
|
|
|
|
// and any other necessary prep work.
|
|
|
|
_query(connection, obj) {
|
|
|
|
if (!obj.sql) throw new Error('The query is empty');
|
|
|
|
|
2023-07-19 15:09:36 -06:00
|
|
|
const options = Object.assign({}, obj.options, { autoCommit: false });
|
2021-01-31 13:40:13 +03:00
|
|
|
if (obj.method === 'select') {
|
|
|
|
options.resultSet = true;
|
|
|
|
}
|
|
|
|
return connection
|
|
|
|
.executeAsync(obj.sql, obj.bindings, options)
|
|
|
|
.then(async function (response) {
|
|
|
|
// Flatten outBinds
|
|
|
|
let outBinds = flatten(response.outBinds);
|
|
|
|
obj.response = response.rows || [];
|
|
|
|
obj.rowsAffected = response.rows
|
|
|
|
? response.rows.rowsAffected
|
|
|
|
: response.rowsAffected;
|
|
|
|
|
|
|
|
//added for outBind parameter
|
|
|
|
if (obj.method === 'raw' && outBinds.length > 0) {
|
|
|
|
return {
|
|
|
|
response: outBinds,
|
2020-02-26 00:50:24 +03:00
|
|
|
};
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
2020-02-26 00:50:24 +03:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
if (obj.method === 'update') {
|
|
|
|
const modifiedRowsCount = obj.rowsAffected.length || obj.rowsAffected;
|
|
|
|
const updatedObjOutBinding = [];
|
|
|
|
const updatedOutBinds = [];
|
|
|
|
const updateOutBinds = (i) =>
|
|
|
|
function (value, index) {
|
|
|
|
const OutBindsOffset = index * modifiedRowsCount;
|
|
|
|
updatedOutBinds.push(outBinds[i + OutBindsOffset]);
|
|
|
|
};
|
|
|
|
|
|
|
|
for (let i = 0; i < modifiedRowsCount; i++) {
|
|
|
|
updatedObjOutBinding.push(obj.outBinding[0]);
|
|
|
|
each(obj.outBinding[0], updateOutBinds(i));
|
|
|
|
}
|
|
|
|
outBinds = updatedOutBinds;
|
|
|
|
obj.outBinding = updatedObjOutBinding;
|
2020-02-26 00:50:24 +03:00
|
|
|
}
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
if (!obj.returning && outBinds.length === 0) {
|
|
|
|
if (!connection.isTransaction) {
|
|
|
|
await connection.commitAsync();
|
|
|
|
}
|
|
|
|
return obj;
|
2020-02-26 00:50:24 +03:00
|
|
|
}
|
2021-01-31 13:40:13 +03:00
|
|
|
const rowIds = [];
|
|
|
|
let offset = 0;
|
2019-09-22 16:31:56 -04:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
for (let line = 0; line < obj.outBinding.length; line++) {
|
|
|
|
const ret = obj.outBinding[line];
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
offset =
|
|
|
|
offset +
|
|
|
|
(obj.outBinding[line - 1] ? obj.outBinding[line - 1].length : 0);
|
2016-06-20 17:03:52 +02:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
for (let index = 0; index < ret.length; index++) {
|
|
|
|
const out = ret[index];
|
2019-10-12 22:39:34 +03:00
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
await new Promise(function (bindResolver, bindRejecter) {
|
|
|
|
if (out instanceof BlobHelper) {
|
|
|
|
const blob = outBinds[index + offset];
|
|
|
|
if (out.returning) {
|
|
|
|
obj.response[line] = obj.response[line] || {};
|
|
|
|
obj.response[line][out.columnName] = out.value;
|
|
|
|
}
|
|
|
|
blob.on('error', function (err) {
|
|
|
|
bindRejecter(err);
|
|
|
|
});
|
|
|
|
blob.on('finish', function () {
|
|
|
|
bindResolver();
|
|
|
|
});
|
|
|
|
blob.write(out.value);
|
|
|
|
blob.end();
|
|
|
|
} else if (obj.outBinding[line][index] === 'ROWID') {
|
|
|
|
rowIds.push(outBinds[index + offset]);
|
|
|
|
bindResolver();
|
|
|
|
} else {
|
2020-02-26 00:50:24 +03:00
|
|
|
obj.response[line] = obj.response[line] || {};
|
2021-01-31 13:40:13 +03:00
|
|
|
obj.response[line][out] = outBinds[index + offset];
|
2020-02-26 00:50:24 +03:00
|
|
|
bindResolver();
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (obj.returningSql) {
|
|
|
|
const response = await connection.executeAsync(
|
|
|
|
obj.returningSql(),
|
|
|
|
rowIds,
|
|
|
|
{ resultSet: true }
|
|
|
|
);
|
|
|
|
obj.response = response.rows;
|
2020-02-26 00:50:24 +03:00
|
|
|
}
|
2023-07-04 12:34:35 -07:00
|
|
|
if (connection.isTransaction) {
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
await connection.commitAsync();
|
2020-02-26 00:50:24 +03:00
|
|
|
return obj;
|
2021-01-31 13:40:13 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process the response as returned from the query.
|
|
|
|
processResponse(obj, runner) {
|
2021-02-08 13:45:13 +02:00
|
|
|
const { response } = obj;
|
2021-01-31 13:40:13 +03:00
|
|
|
if (obj.output) {
|
|
|
|
return obj.output.call(runner, response);
|
|
|
|
}
|
2021-02-08 13:45:13 +02:00
|
|
|
switch (obj.method) {
|
2021-01-31 13:40:13 +03:00
|
|
|
case 'select':
|
2021-02-08 13:45:13 +02:00
|
|
|
return response;
|
2021-01-31 13:40:13 +03:00
|
|
|
case 'first':
|
2021-02-08 13:45:13 +02:00
|
|
|
return response[0];
|
|
|
|
case 'pluck':
|
|
|
|
return map(response, obj.pluck);
|
2021-01-31 13:40:13 +03:00
|
|
|
case 'insert':
|
|
|
|
case 'del':
|
|
|
|
case 'update':
|
|
|
|
case 'counter':
|
2023-07-04 12:34:35 -07:00
|
|
|
if ((obj.returning && !isEmpty(obj.returning)) || obj.returningSql) {
|
2021-01-31 13:40:13 +03:00
|
|
|
return response;
|
|
|
|
} else if (obj.rowsAffected !== undefined) {
|
|
|
|
return obj.rowsAffected;
|
|
|
|
} else {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
2021-10-20 23:35:51 +03:00
|
|
|
|
|
|
|
processPassedConnection(connection) {
|
2023-10-16 17:31:38 -06:00
|
|
|
this.checkVersion(connection);
|
2021-10-20 23:35:51 +03:00
|
|
|
monkeyPatchConnection(connection, this);
|
|
|
|
}
|
2021-01-31 13:40:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Client_Oracledb.prototype.driverName = 'oracledb';
|
|
|
|
|
2023-10-16 17:31:38 -06:00
|
|
|
function parseVersion(versionString) {
|
|
|
|
try {
|
|
|
|
// We only care about first two version components at most
|
|
|
|
const versionParts = versionString.split('.').slice(0, 2);
|
|
|
|
// Strip off any character suffixes in version number (ex. 12c => 12, 12.2c => 12.2)
|
|
|
|
versionParts.forEach((versionPart, idx) => {
|
|
|
|
versionParts[idx] = versionPart.replace(/\D$/, '');
|
|
|
|
});
|
|
|
|
const version = versionParts.join('.');
|
|
|
|
return version.match(/^\d+\.?\d*$/) ? version : null;
|
|
|
|
} catch (err) {
|
|
|
|
// Non-string versionString passed in.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 13:40:13 +03:00
|
|
|
function resolveConnectString(connectionSettings) {
|
|
|
|
if (connectionSettings.connectString) {
|
|
|
|
return connectionSettings.connectString;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!connectionSettings.port) {
|
|
|
|
return connectionSettings.host + '/' + connectionSettings.database;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
connectionSettings.host +
|
|
|
|
':' +
|
|
|
|
connectionSettings.port +
|
|
|
|
'/' +
|
|
|
|
connectionSettings.database
|
|
|
|
);
|
|
|
|
}
|
2016-06-20 17:03:52 +02:00
|
|
|
|
|
|
|
module.exports = Client_Oracledb;
|