"use strict"; const AbstractQuery = require("../abstract/query"); const QueryTypes = require("../../query-types"); const sequelizeErrors = require("../../errors"); const _ = require("lodash"); const { logger } = require("../../utils/logger"); const debug = logger.debugContext("sql:pg"); class Query extends AbstractQuery { static formatBindParameters(sql, values, dialect) { const stringReplaceFunc = (value) => typeof value === "string" ? value.replace(/\0/g, "\\0") : value; let bindParam; if (Array.isArray(values)) { bindParam = values.map(stringReplaceFunc); sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0]; } else { bindParam = []; let i = 0; const seen = {}; const replacementFunc = (match, key, values2) => { if (seen[key] !== void 0) { return seen[key]; } if (values2[key] !== void 0) { i = i + 1; bindParam.push(stringReplaceFunc(values2[key])); seen[key] = `$${i}`; return `$${i}`; } return void 0; }; sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; } return [sql, bindParam]; } async run(sql, parameters) { const { connection } = this; if (!_.isEmpty(this.options.searchPath)) { sql = this.sequelize.getQueryInterface().queryGenerator.setSearchPath(this.options.searchPath) + sql; } if (this.sequelize.options.minifyAliases && this.options.includeAliases) { _.toPairs(this.options.includeAliases).sort((a, b) => b[1].length - a[1].length).forEach(([alias, original]) => { const reg = new RegExp(_.escapeRegExp(original), "g"); sql = sql.replace(reg, alias); }); } this.sql = sql; const query = parameters && parameters.length ? new Promise((resolve, reject) => connection.query(sql, parameters, (error, result) => error ? reject(error) : resolve(result))) : new Promise((resolve, reject) => connection.query(sql, (error, result) => error ? reject(error) : resolve(result))); const complete = this._logQuery(sql, debug, parameters); let queryResult; const errForStack = new Error(); try { queryResult = await query; } catch (error) { if (error.code === "ECONNRESET" || /Unable to set non-blocking to true/i.test(error) || /SSL SYSCALL error: EOF detected/i.test(error) || /Local: Authentication failure/i.test(error) || error.message === "Query read timeout") { connection._invalid = true; } error.sql = sql; error.parameters = parameters; throw this.formatError(error, errForStack.stack); } complete(); let rows = Array.isArray(queryResult) ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), []) : queryResult.rows; const rowCount = Array.isArray(queryResult) ? queryResult.reduce((count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, 0) : queryResult.rowCount || 0; if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) { rows = rows.map((row) => _.toPairs(row).reduce((acc, [key, value]) => { const mapping = this.options.aliasesMapping.get(key); acc[mapping || key] = value; return acc; }, {})); } const isTableNameQuery = sql.startsWith("SELECT table_name FROM information_schema.tables"); const isRelNameQuery = sql.startsWith("SELECT relname FROM pg_class WHERE oid IN"); if (isRelNameQuery) { return rows.map((row) => ({ name: row.relname, tableName: row.relname.split("_")[0] })); } if (isTableNameQuery) { return rows.map((row) => Object.values(row)); } if (rows[0] && rows[0].sequelize_caught_exception !== void 0) { if (rows[0].sequelize_caught_exception !== null) { throw this.formatError({ sql, parameters, code: "23505", detail: rows[0].sequelize_caught_exception }); } for (const row of rows) { delete row.sequelize_caught_exception; } } if (this.isShowIndexesQuery()) { for (const row of rows) { const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(","); const columns = _.zipObject(row.column_indexes, this.sequelize.getQueryInterface().queryGenerator.fromArray(row.column_names)); delete row.column_indexes; delete row.column_names; let field; let attribute; row.fields = row.indkey.split(" ").map((indKey, index) => { field = columns[indKey]; if (!field) { return null; } attribute = attributes[index]; return { attribute: field, collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : void 0, order: attribute.includes("DESC") ? "DESC" : attribute.includes("ASC") ? "ASC" : void 0, length: void 0 }; }).filter((n) => n !== null); delete row.columns; } return rows; } if (this.isForeignKeysQuery()) { const result = []; for (const row of rows) { let defParts; if (row.condef !== void 0 && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) { row.id = row.constraint_name; row.table = defParts[2]; row.from = defParts[1]; row.to = defParts[3]; let i; for (i = 5; i <= 8; i += 3) { if (/(UPDATE|DELETE)/.test(defParts[i])) { row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1]; } } } result.push(row); } return result; } if (this.isSelectQuery()) { let result = rows; if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => { m[k.toLowerCase()] = k; return m; }, {}); result = rows.map((row) => { return _.mapKeys(row, (value, key) => { const targetAttr = attrsMap[key]; if (typeof targetAttr === "string" && targetAttr !== key) { return targetAttr; } return key; }); }); } return this.handleSelectQuery(result); } if (QueryTypes.DESCRIBE === this.options.type) { const result = {}; for (const row of rows) { result[row.Field] = { type: row.Type.toUpperCase(), allowNull: row.Null === "YES", defaultValue: row.Default, comment: row.Comment, special: row.special ? this.sequelize.getQueryInterface().queryGenerator.fromArray(row.special) : [], primaryKey: row.Constraint === "PRIMARY KEY" }; if (result[row.Field].type === "BOOLEAN") { result[row.Field].defaultValue = { "false": false, "true": true }[result[row.Field].defaultValue]; if (result[row.Field].defaultValue === void 0) { result[row.Field].defaultValue = null; } } if (typeof result[row.Field].defaultValue === "string") { result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, ""); if (result[row.Field].defaultValue.includes("::")) { const split = result[row.Field].defaultValue.split("::"); if (split[1].toLowerCase() !== "regclass)") { result[row.Field].defaultValue = split[0]; } } } } return result; } if (this.isVersionQuery()) { return rows[0].server_version; } if (this.isShowOrDescribeQuery()) { return rows; } if (QueryTypes.BULKUPDATE === this.options.type) { if (!this.options.returning) { return parseInt(rowCount, 10); } return this.handleSelectQuery(rows); } if (QueryTypes.BULKDELETE === this.options.type) { return parseInt(rowCount, 10); } if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) { if (this.instance && this.instance.dataValues) { if (this.isInsertQuery() && !this.isUpsertQuery() && rowCount === 0) { throw new sequelizeErrors.EmptyResultError(); } for (const key in rows[0]) { if (Object.prototype.hasOwnProperty.call(rows[0], key)) { const record = rows[0][key]; const attr = _.find(this.model.rawAttributes, (attribute) => attribute.fieldName === key || attribute.field === key); this.instance.dataValues[attr && attr.fieldName || key] = record; } } } if (this.isUpsertQuery()) { return [ this.instance, null ]; } return [ this.instance || rows && (this.options.plain && rows[0] || rows) || void 0, rowCount ]; } if (this.isRawQuery()) { return [rows, queryResult]; } return rows; } formatError(err, errStack) { let match; let table; let index; let fields; let errors; let message; const code = err.code || err.sqlState; const errMessage = err.message || err.messagePrimary; const errDetail = err.detail || err.messageDetail; switch (code) { case "23503": index = errMessage.match(/violates foreign key constraint "(.+?)"/); index = index ? index[1] : void 0; table = errMessage.match(/on table "(.+?)"/); table = table ? table[1] : void 0; return new sequelizeErrors.ForeignKeyConstraintError({ message: errMessage, fields: null, index, table, parent: err, stack: errStack }); case "23505": if (errDetail && (match = errDetail.replace(/"/g, "").match(/Key \((.*?)\)=\((.*?)\)/))) { fields = _.zipObject(match[1].split(", "), match[2].split(", ")); errors = []; message = "Validation error"; _.forOwn(fields, (value, field) => { errors.push(new sequelizeErrors.ValidationErrorItem(this.getUniqueConstraintErrorMessage(field), "unique violation", field, value, this.instance, "not_unique")); }); if (this.model && this.model.uniqueKeys) { _.forOwn(this.model.uniqueKeys, (constraint) => { if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) { message = constraint.msg; return false; } }); } return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } return new sequelizeErrors.UniqueConstraintError({ message: errMessage, parent: err, stack: errStack }); case "23P01": match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/); if (match) { fields = _.zipObject(match[1].split(", "), match[2].split(", ")); } message = "Exclusion constraint error"; return new sequelizeErrors.ExclusionConstraintError({ message, constraint: err.constraint, fields, table: err.table, parent: err, stack: errStack }); case "42704": if (err.sql && /(CONSTRAINT|INDEX)/gi.test(err.sql)) { message = "Unknown constraint error"; index = errMessage.match(/(?:constraint|index) "(.+?)"/i); index = index ? index[1] : void 0; table = errMessage.match(/relation "(.+?)"/i); table = table ? table[1] : void 0; throw new sequelizeErrors.UnknownConstraintError({ message, constraint: index, fields, table, parent: err, stack: errStack }); } default: return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } isForeignKeysQuery() { return /SELECT conname as constraint_name, pg_catalog\.pg_get_constraintdef\(r\.oid, true\) as condef FROM pg_catalog\.pg_constraint r WHERE r\.conrelid = \(SELECT oid FROM pg_class WHERE relname = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql); } getInsertIdField() { return "id"; } } module.exports = Query; module.exports.Query = Query; module.exports.default = Query; //# sourceMappingURL=query.js.map