You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
16 KiB
479 lines
16 KiB
"use strict"; |
|
var __defProp = Object.defineProperty; |
|
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); |
|
var __export = (target, all) => { |
|
__markAsModule(target); |
|
for (var name in all) |
|
__defProp(target, name, { get: all[name], enumerable: true }); |
|
}; |
|
__export(exports, { |
|
OracleQuery: () => OracleQuery |
|
}); |
|
const AbstractQuery = require("../abstract/query"); |
|
const SequelizeErrors = require("../../errors"); |
|
const parserStore = require("../parserStore")("oracle"); |
|
const _ = require("lodash"); |
|
const Utils = require("../../utils"); |
|
const { logger } = require("../../utils/logger"); |
|
const debug = logger.debugContext("sql:oracle"); |
|
class OracleQuery extends AbstractQuery { |
|
constructor(connection, sequelize, options) { |
|
super(connection, sequelize, options); |
|
this.options = _.extend({ |
|
logging: console.log, |
|
plain: false, |
|
raw: false |
|
}, options || {}); |
|
this.checkLoggingOption(); |
|
this.outFormat = options.outFormat || this.sequelize.connectionManager.lib.OBJECT; |
|
} |
|
getInsertIdField() { |
|
return "id"; |
|
} |
|
getExecOptions() { |
|
const execOpts = { outFormat: this.outFormat, autoCommit: this.autoCommit }; |
|
const oracledb = this.sequelize.connectionManager.lib; |
|
if (this.isSelectQuery() && this.model) { |
|
const fInfo = {}; |
|
const keys = Object.keys(this.model.tableAttributes); |
|
for (const key of keys) { |
|
const keyValue = this.model.tableAttributes[key]; |
|
if (keyValue.type.key === "DECIMAL") { |
|
fInfo[key] = { type: oracledb.STRING }; |
|
} |
|
if (keyValue.type.key === "BIGINT") { |
|
fInfo[key] = { type: oracledb.STRING }; |
|
} |
|
} |
|
if (fInfo) { |
|
execOpts.fetchInfo = fInfo; |
|
} |
|
} |
|
return execOpts; |
|
} |
|
async run(sql, parameters) { |
|
const oracledb = this.sequelize.connectionManager.lib; |
|
const complete = this._logQuery(sql, debug, parameters); |
|
const outParameters = []; |
|
const bindParameters = []; |
|
const bindDef = []; |
|
if (!sql.match(/END;$/)) { |
|
this.sql = sql.replace(/; *$/, ""); |
|
} else { |
|
this.sql = sql; |
|
} |
|
if (this.options.outBindAttributes && (Array.isArray(parameters) || _.isPlainObject(parameters))) { |
|
outParameters.push(...Object.values(this.options.outBindAttributes)); |
|
if (this.isUpsertQuery()) { |
|
outParameters.push({ dir: oracledb.BIND_OUT }); |
|
} |
|
} |
|
this.bindParameters = outParameters; |
|
if (Array.isArray(parameters) || _.isPlainObject(parameters)) { |
|
if (this.options.executeMany) { |
|
bindDef.push(...Object.values(this.options.inbindAttributes)); |
|
bindDef.push(...outParameters); |
|
this.bindParameters = parameters; |
|
} else { |
|
Object.values(parameters).forEach((value) => { |
|
bindParameters.push(value); |
|
}); |
|
bindParameters.push(...outParameters); |
|
Object.assign(this.bindParameters, bindParameters); |
|
} |
|
} |
|
if (this.sql.startsWith("BEGIN TRANSACTION")) { |
|
this.autocommit = false; |
|
return Promise.resolve(); |
|
} |
|
if (this.sql.startsWith("SET AUTOCOMMIT ON")) { |
|
this.autocommit = true; |
|
return Promise.resolve(); |
|
} |
|
if (this.sql.startsWith("SET AUTOCOMMIT OFF")) { |
|
this.autocommit = false; |
|
return Promise.resolve(); |
|
} |
|
if (this.sql.startsWith("DECLARE x NUMBER")) { |
|
if (this.autoCommit === void 0) { |
|
if (this.connection.uuid) { |
|
this.autoCommit = false; |
|
} else { |
|
this.autoCommit = true; |
|
} |
|
} |
|
try { |
|
await this.connection.execute(this.sql, this.bindParameters, { autoCommit: this.autoCommit }); |
|
return Object.create(null); |
|
} catch (error) { |
|
throw this.formatError(error); |
|
} finally { |
|
complete(); |
|
} |
|
} |
|
if (this.sql.startsWith("BEGIN")) { |
|
if (this.autoCommit === void 0) { |
|
if (this.connection.uuid) { |
|
this.autoCommit = false; |
|
} else { |
|
this.autoCommit = true; |
|
} |
|
} |
|
try { |
|
const result = await this.connection.execute(this.sql, this.bindParameters, { |
|
outFormat: this.outFormat, |
|
autoCommit: this.autoCommit |
|
}); |
|
if (!Array.isArray(result.outBinds)) { |
|
return [result.outBinds]; |
|
} |
|
return result.outBinds; |
|
} catch (error) { |
|
throw this.formatError(error); |
|
} finally { |
|
complete(); |
|
} |
|
} |
|
if (this.sql.startsWith("COMMIT TRANSACTION")) { |
|
try { |
|
await this.connection.commit(); |
|
return Object.create(null); |
|
} catch (error) { |
|
throw this.formatError(error); |
|
} finally { |
|
complete(); |
|
} |
|
} |
|
if (this.sql.startsWith("ROLLBACK TRANSACTION")) { |
|
try { |
|
await this.connection.rollback(); |
|
return Object.create(null); |
|
} catch (error) { |
|
throw this.formatError(error); |
|
} finally { |
|
complete(); |
|
} |
|
} |
|
if (this.sql.startsWith("SET TRANSACTION")) { |
|
try { |
|
await this.connection.execute(this.sql, [], { autoCommit: false }); |
|
return Object.create(null); |
|
} catch (error) { |
|
throw this.formatError(error); |
|
} finally { |
|
complete(); |
|
} |
|
} |
|
if (this.autoCommit === void 0) { |
|
if (this.connection.uuid) { |
|
this.autoCommit = false; |
|
} else { |
|
this.autoCommit = true; |
|
} |
|
} |
|
if ("inputParameters" in this.options && this.options.inputParameters !== null) { |
|
Object.assign(this.bindParameters, this.options.inputParameters); |
|
} |
|
const execOpts = this.getExecOptions(); |
|
if (this.options.executeMany && bindDef.length > 0) { |
|
execOpts.bindDefs = bindDef; |
|
} |
|
const executePromise = this.options.executeMany ? this.connection.executeMany(this.sql, this.bindParameters, execOpts) : this.connection.execute(this.sql, this.bindParameters, execOpts); |
|
try { |
|
const result = await executePromise; |
|
return this.formatResults(result); |
|
} catch (error) { |
|
throw this.formatError(error); |
|
} finally { |
|
complete(); |
|
} |
|
} |
|
static formatBindParameters(sql, values, dialect) { |
|
const replacementFunc = (match, key, values2) => { |
|
if (values2[key] !== void 0) { |
|
return `:${key}`; |
|
} |
|
return void 0; |
|
}; |
|
sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; |
|
return [sql, values]; |
|
} |
|
_getAttributeMap(attrsMap, rawAttributes) { |
|
attrsMap = Object.assign(attrsMap, _.reduce(rawAttributes, (mp, _2, key) => { |
|
const catalogKey = this.sequelize.queryInterface.queryGenerator.getCatalogName(key); |
|
mp[catalogKey] = key; |
|
return mp; |
|
}, {})); |
|
} |
|
_processRows(rows) { |
|
let result = rows; |
|
let attrsMap = {}; |
|
if (this.sequelize.options.quoteIdentifiers === false) { |
|
attrsMap = _.reduce(this.options.attributes, (mp, v) => { |
|
if (typeof v === "object") { |
|
v = v[1]; |
|
} |
|
const catalogv = this.sequelize.queryInterface.queryGenerator.getCatalogName(v); |
|
mp[catalogv] = v; |
|
return mp; |
|
}, {}); |
|
if (this.model) { |
|
this._getAttributeMap(attrsMap, this.model.rawAttributes); |
|
} |
|
if (this.options.aliasesMapping) { |
|
const obj = Object.fromEntries(this.options.aliasesMapping); |
|
rows = rows.map((row) => _.toPairs(row).reduce((acc, [key, value]) => { |
|
const mapping = Object.values(obj).find((element) => { |
|
const catalogElement = this.sequelize.queryInterface.queryGenerator.getCatalogName(element); |
|
return catalogElement === key; |
|
}); |
|
if (mapping) |
|
acc[mapping || key] = value; |
|
return acc; |
|
}, {})); |
|
} |
|
result = rows.map((row) => { |
|
return _.mapKeys(row, (value, key) => { |
|
const targetAttr = attrsMap[key]; |
|
if (typeof targetAttr === "string" && targetAttr !== key) { |
|
return targetAttr; |
|
} |
|
return key; |
|
}); |
|
}); |
|
} |
|
if (this.model) { |
|
result = result.map((row) => { |
|
return _.mapValues(row, (value, key) => { |
|
if (this.model.rawAttributes[key] && this.model.rawAttributes[key].type) { |
|
let typeid = this.model.rawAttributes[key].type.toLocaleString(); |
|
if (this.model.rawAttributes[key].type.key === "JSON") { |
|
value = JSON.parse(value); |
|
} |
|
if (typeid.indexOf("(") > -1 && this.model.rawAttributes[key].type.key !== "BOOLEAN") { |
|
typeid = typeid.substr(0, typeid.indexOf("(")); |
|
} |
|
const parse = parserStore.get(typeid); |
|
if (value !== null & !!parse) { |
|
value = parse(value); |
|
} |
|
} |
|
return value; |
|
}); |
|
}); |
|
} |
|
return result; |
|
} |
|
formatResults(data) { |
|
let result = this.instance; |
|
if (this.isInsertQuery(data)) { |
|
let insertData; |
|
if (data.outBinds) { |
|
const keys = Object.keys(this.options.outBindAttributes); |
|
insertData = data.outBinds; |
|
if (this.instance) { |
|
insertData = [insertData]; |
|
} |
|
const res = insertData.map((row) => { |
|
const obj = {}; |
|
row.forEach((element, index) => { |
|
obj[keys[index]] = element[0]; |
|
}); |
|
return obj; |
|
}); |
|
insertData = res; |
|
if (!this.instance) { |
|
result = res; |
|
} |
|
} |
|
this.handleInsertQuery(insertData); |
|
return [result, data.rowsAffected]; |
|
} |
|
if (this.isShowTablesQuery()) { |
|
result = this.handleShowTablesQuery(data.rows); |
|
} else if (this.isDescribeQuery()) { |
|
result = {}; |
|
const table = Object.keys(this.sequelize.models); |
|
const modelAttributes = {}; |
|
if (this.sequelize.models && table.length > 0) { |
|
this._getAttributeMap(modelAttributes, this.sequelize.models[table[0]].rawAttributes); |
|
} |
|
data.rows.forEach((_result) => { |
|
if (_result.Default) { |
|
_result.Default = _result.Default.replace("('", "").replace("')", "").replace(/'/g, ""); |
|
} |
|
if (!(modelAttributes[_result.COLUMN_NAME] in result)) { |
|
let key = modelAttributes[_result.COLUMN_NAME]; |
|
if (!key) { |
|
key = _result.COLUMN_NAME; |
|
} |
|
result[key] = { |
|
type: _result.DATA_TYPE.toUpperCase(), |
|
allowNull: _result.NULLABLE === "N" ? false : true, |
|
defaultValue: void 0, |
|
primaryKey: _result.CONSTRAINT_TYPE === "P" |
|
}; |
|
} |
|
}); |
|
} else if (this.isShowIndexesQuery()) { |
|
result = this.handleShowIndexesQuery(data.rows); |
|
} else if (this.isSelectQuery()) { |
|
const rows = data.rows; |
|
const result2 = this._processRows(rows); |
|
return this.handleSelectQuery(result2); |
|
} else if (this.isCallQuery()) { |
|
result = data.rows[0]; |
|
} else if (this.isUpdateQuery()) { |
|
result = [result, data.rowsAffected]; |
|
} else if (this.isBulkUpdateQuery()) { |
|
result = data.rowsAffected; |
|
} else if (this.isBulkDeleteQuery()) { |
|
result = data.rowsAffected; |
|
} else if (this.isVersionQuery()) { |
|
const version = data.rows[0].VERSION_FULL; |
|
if (version) { |
|
const versions = version.split("."); |
|
result = `${versions[0]}.${versions[1]}.${versions[2]}`; |
|
} else { |
|
result = "0.0.0"; |
|
} |
|
} else if (this.isForeignKeysQuery()) { |
|
result = data.rows; |
|
} else if (this.isUpsertQuery()) { |
|
data = data.outBinds; |
|
const keys = Object.keys(this.options.outBindAttributes); |
|
const obj = {}; |
|
for (const k in keys) { |
|
obj[keys[k]] = data[k]; |
|
} |
|
obj.isUpdate = data[data.length - 1]; |
|
data = obj; |
|
result = [{ isNewRecord: data.isUpdate, value: data }, data.isUpdate == 0]; |
|
} else if (this.isShowConstraintsQuery()) { |
|
result = this.handleShowConstraintsQuery(data); |
|
} else if (this.isRawQuery()) { |
|
if (data && data.rows) { |
|
return [data.rows, data.metaData]; |
|
} |
|
return [data, data]; |
|
} |
|
return result; |
|
} |
|
handleShowConstraintsQuery(data) { |
|
return data.rows.map((result) => { |
|
const constraint = {}; |
|
for (const key in result) { |
|
constraint[_.camelCase(key)] = result[key].toLowerCase(); |
|
} |
|
return constraint; |
|
}); |
|
} |
|
handleShowTablesQuery(results) { |
|
return results.map((resultSet) => { |
|
return { |
|
tableName: resultSet.TABLE_NAME, |
|
schema: resultSet.TABLE_SCHEMA |
|
}; |
|
}); |
|
} |
|
formatError(err) { |
|
let match; |
|
match = err.message.match(/unique constraint ([\s\S]*) violated/); |
|
if (match && match.length > 1) { |
|
match[1] = match[1].replace("(", "").replace(")", "").split(".")[1]; |
|
const errors = []; |
|
let fields = [], message = "Validation error", uniqueKey = null; |
|
if (this.model) { |
|
const uniqueKeys = Object.keys(this.model.uniqueKeys); |
|
const currKey = uniqueKeys.find((key) => { |
|
return key.toUpperCase() === match[1].toUpperCase() || key.toUpperCase() === `"${match[1].toUpperCase()}"`; |
|
}); |
|
if (currKey) { |
|
uniqueKey = this.model.uniqueKeys[currKey]; |
|
fields = uniqueKey.fields; |
|
} |
|
if (uniqueKey && !!uniqueKey.msg) { |
|
message = uniqueKey.msg; |
|
} |
|
fields.forEach((field) => { |
|
errors.push(new SequelizeErrors.ValidationErrorItem(this.getUniqueConstraintErrorMessage(field), "unique violation", field, null)); |
|
}); |
|
} |
|
return new SequelizeErrors.UniqueConstraintError({ |
|
message, |
|
errors, |
|
err, |
|
fields |
|
}); |
|
} |
|
match = err.message.match(/ORA-02291/) || err.message.match(/ORA-02292/); |
|
if (match && match.length > 0) { |
|
return new SequelizeErrors.ForeignKeyConstraintError({ |
|
fields: null, |
|
index: match[1], |
|
parent: err |
|
}); |
|
} |
|
match = err.message.match(/ORA-02443/); |
|
if (match && match.length > 0) { |
|
return new SequelizeErrors.UnknownConstraintError(match[1]); |
|
} |
|
return new SequelizeErrors.DatabaseError(err); |
|
} |
|
isShowIndexesQuery() { |
|
return this.sql.indexOf("SELECT i.index_name,i.table_name, i.column_name, u.uniqueness") > -1; |
|
} |
|
isSelectCountQuery() { |
|
return this.sql.toUpperCase().indexOf("SELECT COUNT(") > -1; |
|
} |
|
handleShowIndexesQuery(data) { |
|
const acc = []; |
|
data.forEach((indexRecord) => { |
|
if (!acc[indexRecord.INDEX_NAME]) { |
|
acc[indexRecord.INDEX_NAME] = { |
|
unique: indexRecord.UNIQUENESS === "UNIQUE" ? true : false, |
|
primary: indexRecord.CONSTRAINT_TYPE === "P", |
|
name: indexRecord.INDEX_NAME.toLowerCase(), |
|
tableName: indexRecord.TABLE_NAME.toLowerCase(), |
|
type: void 0 |
|
}; |
|
acc[indexRecord.INDEX_NAME].fields = []; |
|
} |
|
acc[indexRecord.INDEX_NAME].fields.push({ |
|
attribute: indexRecord.COLUMN_NAME, |
|
length: void 0, |
|
order: indexRecord.DESCEND, |
|
collate: void 0 |
|
}); |
|
}); |
|
const returnIndexes = []; |
|
const accKeys = Object.keys(acc); |
|
for (const accKey of accKeys) { |
|
const columns = {}; |
|
columns.fields = acc[accKey].fields; |
|
if (acc[accKey].name.match(/sys_c[0-9]*/)) { |
|
acc[accKey].name = Utils.nameIndex(columns, acc[accKey].tableName).name; |
|
} |
|
returnIndexes.push(acc[accKey]); |
|
} |
|
return returnIndexes; |
|
} |
|
handleInsertQuery(results, metaData) { |
|
if (this.instance && results.length > 0) { |
|
if ("pkReturnVal" in results[0]) { |
|
results[0][this.model.primaryKeyAttribute] = results[0].pkReturnVal; |
|
delete results[0].pkReturnVal; |
|
} |
|
const autoIncrementField = this.model.autoIncrementAttribute; |
|
let autoIncrementFieldAlias = null, id = null; |
|
if (Object.prototype.hasOwnProperty.call(this.model.rawAttributes, autoIncrementField) && this.model.rawAttributes[autoIncrementField].field !== void 0) |
|
autoIncrementFieldAlias = this.model.rawAttributes[autoIncrementField].field; |
|
id = id || results && results[0][this.getInsertIdField()]; |
|
id = id || metaData && metaData[this.getInsertIdField()]; |
|
id = id || results && results[0][autoIncrementField]; |
|
id = id || autoIncrementFieldAlias && results && results[0][autoIncrementFieldAlias]; |
|
this.instance[autoIncrementField] = id; |
|
} |
|
} |
|
} |
|
//# sourceMappingURL=query.js.map
|
|
|