sequelize
Version:
Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift and Snowflake’s Data Cloud. It features solid transaction support, relations, eager and lazy loading, read replication and more.
324 lines (323 loc) • 12.4 kB
JavaScript
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 (err) {
if (err.code === "ECONNRESET") {
connection._invalid = true;
}
err.sql = sql;
err.parameters = parameters;
throw this.formatError(err, 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
;