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.
427 lines
16 KiB
427 lines
16 KiB
"use strict"; |
|
const AbstractQuery = require("../abstract/query"); |
|
const sequelizeErrors = require("../../errors"); |
|
const parserStore = require("../parserStore")("db2"); |
|
const _ = require("lodash"); |
|
const { logger } = require("../../utils/logger"); |
|
const moment = require("moment"); |
|
const debug = logger.debugContext("sql:db2"); |
|
class Query extends AbstractQuery { |
|
getInsertIdField() { |
|
return "id"; |
|
} |
|
getSQLTypeFromJsType(value) { |
|
const param = { ParamType: "INPUT", Data: value }; |
|
if (Buffer.isBuffer(value)) { |
|
param.DataType = "BLOB"; |
|
return param; |
|
} |
|
return value; |
|
} |
|
async _run(connection, sql, parameters) { |
|
this.sql = sql; |
|
const benchmark = this.sequelize.options.benchmark || this.options.benchmark; |
|
let queryBegin; |
|
if (benchmark) { |
|
queryBegin = Date.now(); |
|
} else { |
|
this.sequelize.log(`Executing (${this.connection.uuid || "default"}): ${this.sql}`, this.options); |
|
} |
|
const errStack = new Error().stack; |
|
return new Promise((resolve, reject) => { |
|
if (_.startsWith(this.sql, "BEGIN TRANSACTION")) { |
|
connection.beginTransaction((err) => { |
|
if (err) { |
|
reject(this.formatError(err, errStack)); |
|
} else { |
|
resolve(this.formatResults()); |
|
} |
|
}); |
|
} else if (_.startsWith(this.sql, "COMMIT TRANSACTION")) { |
|
connection.commitTransaction((err) => { |
|
if (err) { |
|
reject(this.formatError(err, errStack)); |
|
} else { |
|
resolve(this.formatResults()); |
|
} |
|
}); |
|
} else if (_.startsWith(this.sql, "ROLLBACK TRANSACTION")) { |
|
connection.rollbackTransaction((err) => { |
|
if (err) { |
|
reject(this.formatError(err, errStack)); |
|
} else { |
|
resolve(this.formatResults()); |
|
} |
|
}); |
|
} else if (_.startsWith(this.sql, "SAVE TRANSACTION")) { |
|
connection.commitTransaction((err) => { |
|
if (err) { |
|
reject(this.formatError(err, errStack)); |
|
} else { |
|
connection.beginTransaction((err2) => { |
|
if (err2) { |
|
reject(this.formatError(err2, errStack)); |
|
} else { |
|
resolve(this.formatResults()); |
|
} |
|
}); |
|
} |
|
}, this.options.transaction.name); |
|
} else { |
|
const params = []; |
|
if (parameters) { |
|
_.forOwn(parameters, (value, key) => { |
|
const param = this.getSQLTypeFromJsType(value, key); |
|
params.push(param); |
|
}); |
|
} |
|
const SQL = this.sql.toUpperCase(); |
|
let newSql = this.sql; |
|
if ((this.isSelectQuery() || _.startsWith(SQL, "SELECT ")) && SQL.indexOf(" FROM ", 8) === -1) { |
|
if (this.sql.charAt(this.sql.length - 1) === ";") { |
|
newSql = this.sql.slice(0, this.sql.length - 1); |
|
} |
|
newSql += " FROM SYSIBM.SYSDUMMY1;"; |
|
} |
|
connection.prepare(newSql, (err, stmt) => { |
|
if (err) { |
|
reject(this.formatError(err, errStack)); |
|
} |
|
stmt.execute(params, (err2, result, outparams) => { |
|
debug(`executed(${this.connection.uuid || "default"}):${newSql} ${parameters ? JSON.stringify(parameters) : ""}`); |
|
if (benchmark) { |
|
this.sequelize.log(`Executed (${this.connection.uuid || "default"}): ${newSql} ${parameters ? JSON.stringify(parameters) : ""}`, Date.now() - queryBegin, this.options); |
|
} |
|
if (err2 && err2.message) { |
|
err2 = this.filterSQLError(err2, this.sql, connection); |
|
if (err2 === null) { |
|
stmt.closeSync(); |
|
resolve(this.formatResults([], 0)); |
|
} |
|
} |
|
if (err2) { |
|
err2.sql = sql; |
|
stmt.closeSync(); |
|
reject(this.formatError(err2, errStack, connection, parameters)); |
|
} else { |
|
let data = []; |
|
let metadata = []; |
|
let affectedRows = 0; |
|
if (typeof result === "object") { |
|
if (_.startsWith(this.sql, "DELETE FROM ")) { |
|
affectedRows = result.getAffectedRowsSync(); |
|
} else { |
|
data = result.fetchAllSync(); |
|
metadata = result.getColumnMetadataSync(); |
|
} |
|
result.closeSync(); |
|
} |
|
stmt.closeSync(); |
|
const datalen = data.length; |
|
if (datalen > 0) { |
|
const coltypes = {}; |
|
for (let i = 0; i < metadata.length; i++) { |
|
coltypes[metadata[i].SQL_DESC_NAME] = metadata[i].SQL_DESC_TYPE_NAME; |
|
} |
|
for (let i = 0; i < datalen; i++) { |
|
for (const column in data[i]) { |
|
const parse = parserStore.get(coltypes[column]); |
|
const value = data[i][column]; |
|
if (value !== null) { |
|
if (parse) { |
|
data[i][column] = parse(value); |
|
} else if (coltypes[column] === "TIMESTAMP") { |
|
data[i][column] = new Date(moment.utc(value)); |
|
} else if (coltypes[column] === "BLOB") { |
|
data[i][column] = new Buffer.from(value); |
|
} else if (coltypes[column].indexOf("FOR BIT DATA") > 0) { |
|
data[i][column] = new Buffer.from(value, "hex"); |
|
} |
|
} |
|
} |
|
} |
|
if (outparams && outparams.length) { |
|
data.unshift(outparams); |
|
} |
|
resolve(this.formatResults(data, datalen, metadata, connection)); |
|
} else { |
|
resolve(this.formatResults(data, affectedRows)); |
|
} |
|
} |
|
}); |
|
}); |
|
} |
|
}); |
|
} |
|
async run(sql, parameters) { |
|
return await this._run(this.connection, sql, parameters); |
|
} |
|
static formatBindParameters(sql, values, dialect) { |
|
let bindParam = {}; |
|
const replacementFunc = (match, key, values2) => { |
|
if (values2[key] !== void 0) { |
|
bindParam[key] = values2[key]; |
|
return "?"; |
|
} |
|
return void 0; |
|
}; |
|
sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; |
|
if (Array.isArray(values) && typeof values[0] === "object") { |
|
bindParam = values; |
|
} |
|
return [sql, bindParam]; |
|
} |
|
filterSQLError(err, sql, connection) { |
|
if (err.message.search("SQL0204N") != -1 && _.startsWith(sql, "DROP ")) { |
|
err = null; |
|
} else if (err.message.search("SQL0443N") != -1) { |
|
if (this.isDropSchemaQuery()) { |
|
connection.querySync("DROP TABLE ERRORSCHEMA.ERRORTABLE;"); |
|
connection.querySync(this.sql); |
|
} |
|
err = null; |
|
} else if (err.message.search("SQL0601N") != -1) { |
|
const match = err.message.match(/SQL0601N {2}The name of the object to be created is identical to the existing name "(.*)" of type "(.*)"./); |
|
if (match && match.length > 1 && match[2] === "TABLE") { |
|
let table; |
|
const mtarray = match[1].split("."); |
|
if (mtarray[1]) { |
|
table = `"${mtarray[0]}"."${mtarray[1]}"`; |
|
} else { |
|
table = `"${mtarray[0]}"`; |
|
} |
|
if (connection.dropTable !== false) { |
|
connection.querySync(`DROP TABLE ${table}`); |
|
err = connection.querySync(sql); |
|
} else { |
|
err = null; |
|
} |
|
} else { |
|
err = null; |
|
} |
|
} else if (err.message.search("SQL0911N") != -1) { |
|
if (err.message.search('Reason code "2"') != -1) { |
|
err = null; |
|
} |
|
} else if (err.message.search("SQL0605W") != -1) { |
|
err = null; |
|
} else if (err.message.search("SQL0668N") != -1 && _.startsWith(sql, "ALTER TABLE ")) { |
|
connection.querySync(`CALL SYSPROC.ADMIN_CMD('REORG TABLE ${sql.substring(12).split(" ")[0]}')`); |
|
err = connection.querySync(sql); |
|
} |
|
if (err && err.length === 0) { |
|
err = null; |
|
} |
|
return err; |
|
} |
|
formatResults(data, rowCount, metadata, conn) { |
|
let result = this.instance; |
|
if (this.isInsertQuery(data, metadata)) { |
|
this.handleInsertQuery(data, metadata); |
|
if (!this.instance) { |
|
if (this.options.plain) { |
|
const record = data[0]; |
|
result = record[Object.keys(record)[0]]; |
|
} else { |
|
result = data; |
|
} |
|
} |
|
} |
|
if (this.isShowTablesQuery()) { |
|
result = data; |
|
} else if (this.isDescribeQuery()) { |
|
result = {}; |
|
for (const _result of data) { |
|
if (_result.Default) { |
|
_result.Default = _result.Default.replace("('", "").replace("')", "").replace(/'/g, ""); |
|
} |
|
result[_result.Name] = { |
|
type: _result.Type.toUpperCase(), |
|
allowNull: _result.IsNull === "Y" ? true : false, |
|
defaultValue: _result.Default, |
|
primaryKey: _result.KeySeq > 0, |
|
autoIncrement: _result.IsIdentity === "Y" ? true : false, |
|
comment: _result.Comment |
|
}; |
|
} |
|
} else if (this.isShowIndexesQuery()) { |
|
result = this.handleShowIndexesQuery(data); |
|
} else if (this.isSelectQuery()) { |
|
result = this.handleSelectQuery(data); |
|
} else if (this.isUpsertQuery()) { |
|
result = data; |
|
} else if (this.isDropSchemaQuery()) { |
|
result = data[0]; |
|
if (conn) { |
|
const query = "DROP TABLE ERRORSCHEMA.ERRORTABLE"; |
|
conn.querySync(query); |
|
} |
|
} else if (this.isCallQuery()) { |
|
result = data; |
|
} else if (this.isBulkUpdateQuery()) { |
|
result = data.length; |
|
} else if (this.isBulkDeleteQuery()) { |
|
result = rowCount; |
|
} else if (this.isVersionQuery()) { |
|
result = data[0].VERSION; |
|
} else if (this.isForeignKeysQuery()) { |
|
result = data; |
|
} else if (this.isInsertQuery() || this.isUpdateQuery()) { |
|
result = [result, rowCount]; |
|
} else if (this.isShowConstraintsQuery()) { |
|
result = this.handleShowConstraintsQuery(data); |
|
} else if (this.isRawQuery()) { |
|
result = [data, metadata]; |
|
} else { |
|
result = data; |
|
} |
|
return result; |
|
} |
|
handleShowTablesQuery(results) { |
|
return results.map((resultSet) => { |
|
return { |
|
tableName: resultSet.TABLE_NAME, |
|
schema: resultSet.TABLE_SCHEMA |
|
}; |
|
}); |
|
} |
|
handleShowConstraintsQuery(data) { |
|
return _.remove(data, (constraint) => { |
|
return !_.startsWith(constraint.constraintName, "SQL"); |
|
}); |
|
} |
|
formatError(err, errStack, conn, parameters) { |
|
let match; |
|
if (!(err && err.message)) { |
|
err["message"] = "No error message found."; |
|
} |
|
match = err.message.match(/SQL0803N {2}One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "(\d)+" constrains table "(.*)\.(.*)" from having duplicate values for the index key./); |
|
if (match && match.length > 0) { |
|
let uniqueIndexName = ""; |
|
let uniqueKey = ""; |
|
const fields = {}; |
|
let message = err.message; |
|
const query = `SELECT INDNAME FROM SYSCAT.INDEXES WHERE IID = ${match[1]} AND TABSCHEMA = '${match[2]}' AND TABNAME = '${match[3]}'`; |
|
if (!!conn && match.length > 3) { |
|
uniqueIndexName = conn.querySync(query); |
|
uniqueIndexName = uniqueIndexName[0]["INDNAME"]; |
|
} |
|
if (this.model && !!uniqueIndexName) { |
|
uniqueKey = this.model.uniqueKeys[uniqueIndexName]; |
|
} |
|
if (!uniqueKey && this.options.fields) { |
|
uniqueKey = this.options.fields[match[1] - 1]; |
|
} |
|
if (uniqueKey) { |
|
if (this.options.where && this.options.where[uniqueKey.column] !== void 0) { |
|
fields[uniqueKey.column] = this.options.where[uniqueKey.column]; |
|
} else if (this.options.instance && this.options.instance.dataValues && this.options.instance.dataValues[uniqueKey.column]) { |
|
fields[uniqueKey.column] = this.options.instance.dataValues[uniqueKey.column]; |
|
} else if (parameters) { |
|
fields[uniqueKey.column] = parameters["0"]; |
|
} |
|
} |
|
if (uniqueKey && !!uniqueKey.msg) { |
|
message = uniqueKey.msg; |
|
} |
|
const errors = []; |
|
_.forOwn(fields, (value, field) => { |
|
errors.push(new sequelizeErrors.ValidationErrorItem(this.getUniqueConstraintErrorMessage(field), "unique violation", field, value, this.instance, "not_unique")); |
|
}); |
|
return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); |
|
} |
|
match = err.message.match(/SQL0532N {2}A parent row cannot be deleted because the relationship "(.*)" restricts the deletion/) || err.message.match(/SQL0530N/) || err.message.match(/SQL0531N/); |
|
if (match && match.length > 0) { |
|
return new sequelizeErrors.ForeignKeyConstraintError({ |
|
fields: null, |
|
index: match[1], |
|
parent: err, |
|
stack: errStack |
|
}); |
|
} |
|
match = err.message.match(/SQL0204N {2}"(.*)" is an undefined name./); |
|
if (match && match.length > 1) { |
|
const constraint = match[1]; |
|
let table = err.sql.match(/table "(.+?)"/i); |
|
table = table ? table[1] : void 0; |
|
return new sequelizeErrors.UnknownConstraintError({ |
|
message: match[0], |
|
constraint, |
|
table, |
|
parent: err, |
|
stack: errStack |
|
}); |
|
} |
|
return new sequelizeErrors.DatabaseError(err, { stack: errStack }); |
|
} |
|
isDropSchemaQuery() { |
|
let result = false; |
|
if (_.startsWith(this.sql, "CALL SYSPROC.ADMIN_DROP_SCHEMA")) { |
|
result = true; |
|
} |
|
return result; |
|
} |
|
isShowOrDescribeQuery() { |
|
let result = false; |
|
result = result || this.sql.toLowerCase().startsWith("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'"); |
|
result = result || this.sql.toLowerCase().startsWith("select tablename = t.name, name = ind.name,"); |
|
result = result || this.sql.toLowerCase().startsWith("exec sys.sp_helpindex @objname"); |
|
return result; |
|
} |
|
isShowIndexesQuery() { |
|
let result = false; |
|
result = result || this.sql.toLowerCase().startsWith("exec sys.sp_helpindex @objname"); |
|
result = result || this.sql.startsWith('SELECT NAME AS "name", TBNAME AS "tableName", UNIQUERULE AS "keyType", COLNAMES, INDEXTYPE AS "type" FROM SYSIBM.SYSINDEXES'); |
|
return result; |
|
} |
|
handleShowIndexesQuery(data) { |
|
let currItem; |
|
const result = []; |
|
data.forEach((item) => { |
|
if (!currItem || currItem.name !== item.Key_name) { |
|
currItem = { |
|
primary: item.keyType === "P", |
|
fields: [], |
|
name: item.name, |
|
tableName: item.tableName, |
|
unique: item.keyType === "U", |
|
type: item.type |
|
}; |
|
_.forEach(item.COLNAMES.replace(/\+|-/g, (x) => { |
|
return ` ${x}`; |
|
}).split(" "), (column) => { |
|
let columnName = column.trim(); |
|
if (columnName) { |
|
columnName = columnName.replace(/\+|-/, ""); |
|
currItem.fields.push({ |
|
attribute: columnName, |
|
length: void 0, |
|
order: column.indexOf("-") === -1 ? "ASC" : "DESC", |
|
collate: void 0 |
|
}); |
|
} |
|
}); |
|
result.push(currItem); |
|
} |
|
}); |
|
return result; |
|
} |
|
handleInsertQuery(results, metaData) { |
|
if (this.instance) { |
|
const autoIncrementAttribute = this.model.autoIncrementAttribute; |
|
let id = null; |
|
let autoIncrementAttributeAlias = null; |
|
if (Object.prototype.hasOwnProperty.call(this.model.rawAttributes, autoIncrementAttribute) && this.model.rawAttributes[autoIncrementAttribute].field !== void 0) |
|
autoIncrementAttributeAlias = this.model.rawAttributes[autoIncrementAttribute].field; |
|
id = id || results && results[0][this.getInsertIdField()]; |
|
id = id || metaData && metaData[this.getInsertIdField()]; |
|
id = id || results && results[0][autoIncrementAttribute]; |
|
id = id || autoIncrementAttributeAlias && results && results[0][autoIncrementAttributeAlias]; |
|
this.instance[autoIncrementAttribute] = id; |
|
} |
|
} |
|
} |
|
module.exports = Query; |
|
module.exports.Query = Query; |
|
module.exports.default = Query; |
|
//# sourceMappingURL=query.js.map
|
|
|