@rcronin/sequelize-ibmi-mapepire
Version:
IBM i (via Mapepire) Sequelize V7 Dialect
245 lines • 10.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.IBMiQuery = void 0;
const core_1 = require("@sequelize/core");
const logger_js_1 = require("@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js");
const debug = logger_js_1.logger.debugContext('sql:ibmi');
class IBMiQuery extends core_1.AbstractQuery {
getInsertIdField() {
return 'id';
}
async run(sql, parameters) {
this.sql = sql.replace(/;$/, '');
const complete = this._logQuery(sql, debug, parameters);
let results = {
data: []
};
let response;
try {
let query;
if (parameters) {
query = await this.connection.query(this.sql, {
parameters: parameters
});
}
else {
query = await this.connection.query(this.sql);
}
response = await query.execute();
if (response) {
results = { ...results, ...response };
}
if (response.has_results) {
while (!response.is_done) {
response = await query.fetchMore();
if (response.data) {
results.data = [...results.data, ...response.data];
}
}
}
await query.close();
}
catch (error) {
throw this.formatError(error);
}
complete();
if (results && results.data && results.data.length > 0) {
for (const row of results.data) {
for (const column of results.metadata.columns) {
const value = row[column.name];
if (value == null) {
continue;
}
const parse = this.sequelize.dialect.getParserForDatabaseDataType(column.type);
if (parse) {
row[column.name] = parse(value);
}
}
}
}
return this.formatResults(results);
}
/**
* High level function that handles the results of a query execution.
*
*
* Example:
* query.formatResults([
* {
* id: 1, // this is from the main table
* attr2: 'snafu', // this is from the main table
* Tasks.id: 1, // this is from the associated table
* Tasks.title: 'task' // this is from the associated table
* }
* ])
*
* @param {Array} results - The result of the query execution.
* @private
*/
formatResults(results) {
if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) {
if (this.instance && this.instance.dataValues) {
if (this.isInsertQuery() && !this.isUpsertQuery() && results.data.length === 0) {
throw new core_1.EmptyResultError();
}
if (this.options.returning && Array.isArray(results.data) && results.data[0]) {
for (const attributeOrColumnName of Object.keys(results.data[0])) {
const modelDefinition = this.model.modelDefinition;
const attribute = modelDefinition.columns.get(attributeOrColumnName);
const updatedValue = this._parseDatabaseValue(results.data[0][attributeOrColumnName], attribute?.type);
this.instance.set(attribute?.attributeName ?? attributeOrColumnName, updatedValue, {
raw: true,
comesFromDatabase: true
});
}
}
}
if (this.isUpsertQuery()) {
return [this.instance, null];
}
return [this.instance || (results.data && ((this.options.plain && results.data[0]) || results.data)) || undefined, results.update_count];
}
if (this.isSelectQuery()) {
return this.handleSelectQuery(results.data);
}
if (this.isShowIndexesQuery()) {
return this.handleShowIndexesQuery(results.data);
}
if (this.isDescribeQuery()) {
const result = {};
for (const _result of results.data) {
const enumRegex = /^enum/i;
result[_result.COLUMN_NAME] = {
type: enumRegex.test(_result.Type) ? _result.Type.replace(enumRegex, 'ENUM') : _result.DATA_TYPE.toUpperCase(),
allowNull: _result.IS_NULLABLE === 'Y',
defaultValue: _result.COLUMN_DEFAULT,
primaryKey: _result.CONSTRAINT_TYPE === 'PRIMARY KEY',
autoIncrement: _result.IS_GENERATED !== 'IDENTITY_GENERATION'
};
}
return result;
}
if (this.isCallQuery()) {
return results.data[0];
}
if (this.isDeleteQuery()) {
return results.update_count;
}
if (this.isBulkUpdateQuery()) {
return this.options.returning ? this.handleSelectQuery(results.data) : results.update_count;
}
if (this.isShowConstraintsQuery()) {
return results.data;
}
if (this.isRawQuery()) {
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
return [results.data, results.data];
}
return this.instance;
}
handleInsertQuery(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
const autoIncrementAttribute = this.model.autoIncrementAttribute.field;
let id = null;
id ||= results && results[autoIncrementAttribute];
id ||= metaData && metaData[autoIncrementAttribute];
this.instance[this.model.autoIncrementAttribute] = id;
}
}
handleShowIndexesQuery(data) {
const indexes = Object.create(null);
data.forEach(item => {
if (Object.hasOwn(indexes, item.NAME)) {
indexes[item.NAME].fields.push({
attribute: item.COLUMN_NAME,
length: undefined,
order: undefined,
collate: undefined
});
}
else {
indexes[item.NAME] = {
primary: item.CONSTRAINT_TYPE === 'PRIMARY KEY',
fields: [
{
attribute: item.COLUMN_NAME,
length: undefined,
order: undefined,
collate: undefined
}
],
name: item.NAME,
tableName: item.TABLE_NAME,
unique: item.CONSTRAINT_TYPE === 'PRIMARY KEY' || item.CONSTRAINT_TYPE === 'UNIQUE',
type: item.CONSTRAINT_TYPE
};
}
});
return Object.values(indexes);
}
formatError(err) {
if (err.message.toString().includes('Error connecting to the database')
|| err.message.toString().includes('getaddrinfo ENOTFOUND')
|| err.message.toString().includes('connect ECONNREFUSED')
|| err.message.toString().includes('Password is incorrect')
|| err.message.toString().includes('connect ETIMEDOUT')) {
this.connection.connected = false;
if (err.message.toString().includes('Error connecting to the database')) {
return new core_1.ConnectionRefusedError(error);
}
if (err.message.toString().includes('getaddrinfo ENOTFOUND')) {
return new HostNotFoundError(error);
}
if (err.message.toString().includes('connect ECONNREFUSED')) {
return new core_1.ConnectionRefusedError(error);
}
if (err.message.toString().includes('Password is incorrect')) {
return new AccessDeniedError(error);
}
if (err.message.toString().includes('connect ETIMEDOUT')) {
return new ConnectionAcquireTimeoutError('Timeout connecting to database', error);
}
}
const foreignKeyConstraintCodes = [
'-530', // The insert or update value of a foreign key is invalid.
'-531', // The update or delete of a parent key is prevented by a NO ACTION update or delete rule.
'-532' // The update or delete of a parent key is prevented by a NO ACTION update or delete rule.
];
const uniqueConstraintCodes = [
'-803' // A violation of the constraint imposed by a unique index or a unique constraint occurred.
];
if (foreignKeyConstraintCodes.includes(err.message)) {
return new core_1.ForeignKeyConstraintError({
cause: err,
sql: {},
fields: {}
});
}
if (uniqueConstraintCodes.includes(err.message)) {
return new core_1.UniqueConstraintError({
errors: [err.message],
cause: err,
sql: {},
fields: {}
});
}
if (err.message?.includes('-204')) {
const constraintNameRegex = /(\w+)\s+in\s+(\w+)\s+type\s+(\*\w+)/;
const constraintNameRegexMatches = err.message?.match(constraintNameRegex);
if (constraintNameRegexMatches && constraintNameRegexMatches.length === 4) {
const constraintName = constraintNameRegexMatches[1];
const type = constraintNameRegexMatches[2];
if (type === '*N') {
return new core_1.UnknownConstraintError({
cause: err,
constraint: constraintName
});
}
}
}
return new core_1.DatabaseError(err);
}
}
exports.IBMiQuery = IBMiQuery;
//# sourceMappingURL=query.js.map