"use strict"; const AbstractQuery = require("../abstract/query"); const sequelizeErrors = require("../../errors"); const parserStore = require("../parserStore")("mssql"); const _ = require("lodash"); const { logger } = require("../../utils/logger"); const debug = logger.debugContext("sql:mssql"); const minSafeIntegerAsBigInt = BigInt(Number.MIN_SAFE_INTEGER); const maxSafeIntegerAsBigInt = BigInt(Number.MAX_SAFE_INTEGER); function getScale(aNum) { if (!Number.isFinite(aNum)) return 0; let e = 1; while (Math.round(aNum * e) / e !== aNum) e *= 10; return Math.log10(e); } class Query extends AbstractQuery { getInsertIdField() { return "id"; } getSQLTypeFromJsType(value, TYPES) { const paramType = { type: TYPES.NVarChar, typeOptions: {}, value }; if (typeof value === "number") { if (Number.isInteger(value)) { if (value >= -2147483648 && value <= 2147483647) { paramType.type = TYPES.Int; } else { paramType.type = TYPES.BigInt; } } else { paramType.type = TYPES.Numeric; paramType.typeOptions = { precision: 30, scale: getScale(value) }; } } else if (typeof value === "bigint") { if (value < minSafeIntegerAsBigInt || value > maxSafeIntegerAsBigInt) { paramType.type = TYPES.VarChar; paramType.value = value.toString(); } else { return this.getSQLTypeFromJsType(Number(value), TYPES); } } else if (typeof value === "boolean") { paramType.type = TYPES.Bit; } if (Buffer.isBuffer(value)) { paramType.type = TYPES.VarBinary; } return paramType; } async _run(connection, sql, parameters, errStack) { this.sql = sql; const { options } = this; const complete = this._logQuery(sql, debug, parameters); const query = new Promise((resolve, reject) => { if (sql.startsWith("BEGIN TRANSACTION")) { return connection.beginTransaction((error) => error ? reject(error) : resolve([]), options.transaction.name, connection.lib.ISOLATION_LEVEL[options.isolationLevel]); } if (sql.startsWith("COMMIT TRANSACTION")) { return connection.commitTransaction((error) => error ? reject(error) : resolve([])); } if (sql.startsWith("ROLLBACK TRANSACTION")) { return connection.rollbackTransaction((error) => error ? reject(error) : resolve([]), options.transaction.name); } if (sql.startsWith("SAVE TRANSACTION")) { return connection.saveTransaction((error) => error ? reject(error) : resolve([]), options.transaction.name); } const rows2 = []; const request = new connection.lib.Request(sql, (err, rowCount2) => err ? reject(err) : resolve([rows2, rowCount2])); if (parameters) { _.forOwn(parameters, (value, key) => { const paramType = this.getSQLTypeFromJsType(value, connection.lib.TYPES); request.addParameter(key, paramType.type, value, paramType.typeOptions); }); } request.on("row", (columns) => { rows2.push(columns); }); connection.execSql(request); }); let rows, rowCount; try { [rows, rowCount] = await query; } catch (err) { err.sql = sql; err.parameters = parameters; throw this.formatError(err, errStack); } complete(); if (Array.isArray(rows)) { rows = rows.map((columns) => { const row = {}; for (const column of columns) { const typeid = column.metadata.type.id; const parse = parserStore.get(typeid); let value = column.value; if (value !== null & !!parse) { value = parse(value); } row[column.metadata.colName] = value; } return row; }); } return this.formatResults(rows, rowCount); } run(sql, parameters) { const errForStack = new Error(); return this.connection.queue.enqueue(() => this._run(this.connection, sql, parameters, errForStack.stack)); } static formatBindParameters(sql, values, dialect) { const bindParam = {}; const replacementFunc = (match, key, values2) => { if (values2[key] !== void 0) { bindParam[key] = values2[key]; return `@${key}`; } return void 0; }; sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; return [sql, bindParam]; } formatResults(data, rowCount) { if (this.isInsertQuery(data)) { this.handleInsertQuery(data); return [this.instance || data, rowCount]; } if (this.isShowTablesQuery()) { return this.handleShowTablesQuery(data); } if (this.isDescribeQuery()) { const 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 === "YES" ? true : false, defaultValue: _result.Default, primaryKey: _result.Constraint === "PRIMARY KEY", autoIncrement: _result.IsIdentity === 1, comment: _result.Comment }; if (result[_result.Name].type.includes("CHAR") && _result.Length) { if (_result.Length === -1) { result[_result.Name].type += "(MAX)"; } else { result[_result.Name].type += `(${_result.Length})`; } } } return result; } if (this.isSelectQuery()) { return this.handleSelectQuery(data); } if (this.isShowIndexesQuery()) { return this.handleShowIndexesQuery(data); } if (this.isCallQuery()) { return data[0]; } if (this.isBulkUpdateQuery()) { if (this.options.returning) { return this.handleSelectQuery(data); } return rowCount; } if (this.isBulkDeleteQuery()) { return data[0] ? data[0].AFFECTEDROWS : 0; } if (this.isVersionQuery()) { return data[0].version; } if (this.isForeignKeysQuery()) { return data; } if (this.isUpsertQuery()) { if (data && data.length === 0) { return [this.instance || data, false]; } this.handleInsertQuery(data); return [this.instance || data, data[0].$action === "INSERT"]; } if (this.isUpdateQuery()) { return [this.instance || data, rowCount]; } if (this.isShowConstraintsQuery()) { return this.handleShowConstraintsQuery(data); } if (this.isRawQuery()) { return [data, rowCount]; } return data; } handleShowTablesQuery(results) { return results.map((resultSet) => { return { tableName: resultSet.TABLE_NAME, schema: resultSet.TABLE_SCHEMA }; }); } handleShowConstraintsQuery(data) { return data.slice(1).map((result) => { const constraint = {}; for (const key in result) { constraint[_.camelCase(key)] = result[key]; } return constraint; }); } formatError(err, errStack) { let match; match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/); match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/); if (match && match.length > 1) { let fields = {}; const uniqueKey = this.model && this.model.uniqueKeys[match[1]]; let message = "Validation error"; if (uniqueKey && !!uniqueKey.msg) { message = uniqueKey.msg; } if (match[3]) { const values = match[3].split(",").map((part) => part.trim()); if (uniqueKey) { fields = _.zipObject(uniqueKey.fields, values); } else { fields[match[1]] = match[3]; } } 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(/Failed on step '(.*)'.Could not create constraint. See previous errors./) || err.message.match(/The DELETE statement conflicted with the REFERENCE constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./) || err.message.match(/The (?:INSERT|MERGE|UPDATE) statement conflicted with the FOREIGN KEY constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./); if (match && match.length > 0) { return new sequelizeErrors.ForeignKeyConstraintError({ fields: null, index: match[1], parent: err, stack: errStack }); } match = err.message.match(/Could not drop constraint. See previous errors./); if (match && match.length > 0) { let constraint = err.sql.match(/(?:constraint|index) \[(.+?)\]/i); constraint = constraint ? constraint[1] : void 0; let table = err.sql.match(/table \[(.+?)\]/i); table = table ? table[1] : void 0; return new sequelizeErrors.UnknownConstraintError({ message: match[1], constraint, table, parent: err, stack: errStack }); } return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } 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() { return this.sql.toLowerCase().startsWith("exec sys.sp_helpindex @objname"); } handleShowIndexesQuery(data) { data = data.reduce((acc, item) => { if (!(item.index_name in acc)) { acc[item.index_name] = item; item.fields = []; } item.index_keys.split(",").forEach((column) => { let columnName = column.trim(); if (columnName.includes("(-)")) { columnName = columnName.replace("(-)", ""); } acc[item.index_name].fields.push({ attribute: columnName, length: void 0, order: column.includes("(-)") ? "DESC" : "ASC", collate: void 0 }); }); delete item.index_keys; return acc; }, {}); return _.map(data, (item) => ({ primary: item.index_name.toLowerCase().startsWith("pk"), fields: item.fields, name: item.index_name, tableName: void 0, unique: item.index_description.toLowerCase().includes("unique"), type: void 0 })); } 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; if (this.instance.dataValues) { for (const key in results[0]) { if (Object.prototype.hasOwnProperty.call(results[0], key)) { const record = results[0][key]; const attr = _.find(this.model.rawAttributes, (attribute) => attribute.fieldName === key || attribute.field === key); this.instance.dataValues[attr && attr.fieldName || key] = record; } } } } } } module.exports = Query; module.exports.Query = Query; module.exports.default = Query; //# sourceMappingURL=query.js.map