query.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. 'use strict';
  2. const AbstractQuery = require('../abstract/query');
  3. const QueryTypes = require('../../query-types');
  4. const sequelizeErrors = require('../../errors');
  5. const _ = require('lodash');
  6. const { logger } = require('../../utils/logger');
  7. const debug = logger.debugContext('sql:pg');
  8. class Query extends AbstractQuery {
  9. /**
  10. * Rewrite query with parameters.
  11. *
  12. * @param {string} sql
  13. * @param {Array|object} values
  14. * @param {string} dialect
  15. * @private
  16. */
  17. static formatBindParameters(sql, values, dialect) {
  18. const stringReplaceFunc = value => typeof value === 'string' ? value.replace(/\0/g, '\\0') : value;
  19. let bindParam;
  20. if (Array.isArray(values)) {
  21. bindParam = values.map(stringReplaceFunc);
  22. sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0];
  23. } else {
  24. bindParam = [];
  25. let i = 0;
  26. const seen = {};
  27. const replacementFunc = (match, key, values) => {
  28. if (seen[key] !== undefined) {
  29. return seen[key];
  30. }
  31. if (values[key] !== undefined) {
  32. i = i + 1;
  33. bindParam.push(stringReplaceFunc(values[key]));
  34. seen[key] = `$${i}`;
  35. return `$${i}`;
  36. }
  37. return undefined;
  38. };
  39. sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
  40. }
  41. return [sql, bindParam];
  42. }
  43. async run(sql, parameters) {
  44. const { connection } = this;
  45. if (!_.isEmpty(this.options.searchPath)) {
  46. sql = this.sequelize.getQueryInterface().queryGenerator.setSearchPath(this.options.searchPath) + sql;
  47. }
  48. if (this.sequelize.options.minifyAliases && this.options.includeAliases) {
  49. _.toPairs(this.options.includeAliases)
  50. // Sorting to replace the longest aliases first to prevent alias collision
  51. .sort((a, b) => b[1].length - a[1].length)
  52. .forEach(([alias, original]) => {
  53. const reg = new RegExp(_.escapeRegExp(original), 'g');
  54. sql = sql.replace(reg, alias);
  55. });
  56. }
  57. this.sql = sql;
  58. const query = parameters && parameters.length
  59. ? new Promise((resolve, reject) => connection.query(sql, parameters, (error, result) => error ? reject(error) : resolve(result)))
  60. : new Promise((resolve, reject) => connection.query(sql, (error, result) => error ? reject(error) : resolve(result)));
  61. const complete = this._logQuery(sql, debug, parameters);
  62. let queryResult;
  63. try {
  64. queryResult = await query;
  65. } catch (err) {
  66. // set the client so that it will be reaped if the connection resets while executing
  67. if (err.code === 'ECONNRESET') {
  68. connection._invalid = true;
  69. }
  70. err.sql = sql;
  71. err.parameters = parameters;
  72. throw this.formatError(err);
  73. }
  74. complete();
  75. let rows = Array.isArray(queryResult)
  76. ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), [])
  77. : queryResult.rows;
  78. const rowCount = Array.isArray(queryResult)
  79. ? queryResult.reduce(
  80. (count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count,
  81. 0
  82. )
  83. : queryResult.rowCount || 0;
  84. if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) {
  85. rows = rows
  86. .map(row => _.toPairs(row)
  87. .reduce((acc, [key, value]) => {
  88. const mapping = this.options.aliasesMapping.get(key);
  89. acc[mapping || key] = value;
  90. return acc;
  91. }, {})
  92. );
  93. }
  94. const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables');
  95. const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN');
  96. if (isRelNameQuery) {
  97. return rows.map(row => ({
  98. name: row.relname,
  99. tableName: row.relname.split('_')[0]
  100. }));
  101. }
  102. if (isTableNameQuery) {
  103. return rows.map(row => Object.values(row));
  104. }
  105. if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
  106. if (rows[0].sequelize_caught_exception !== null) {
  107. throw this.formatError({
  108. sql,
  109. parameters,
  110. code: '23505',
  111. detail: rows[0].sequelize_caught_exception
  112. });
  113. }
  114. for (const row of rows) {
  115. delete row.sequelize_caught_exception;
  116. }
  117. }
  118. if (this.isShowIndexesQuery()) {
  119. for (const row of rows) {
  120. const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(',');
  121. // Map column index in table to column name
  122. const columns = _.zipObject(
  123. row.column_indexes,
  124. this.sequelize.getQueryInterface().queryGenerator.fromArray(row.column_names)
  125. );
  126. delete row.column_indexes;
  127. delete row.column_names;
  128. let field;
  129. let attribute;
  130. // Indkey is the order of attributes in the index, specified by a string of attribute indexes
  131. row.fields = row.indkey.split(' ').map((indKey, index) => {
  132. field = columns[indKey];
  133. // for functional indices indKey = 0
  134. if (!field) {
  135. return null;
  136. }
  137. attribute = attributes[index];
  138. return {
  139. attribute: field,
  140. collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
  141. order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined,
  142. length: undefined
  143. };
  144. }).filter(n => n !== null);
  145. delete row.columns;
  146. }
  147. return rows;
  148. }
  149. if (this.isForeignKeysQuery()) {
  150. const result = [];
  151. for (const row of rows) {
  152. let defParts;
  153. if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) {
  154. row.id = row.constraint_name;
  155. row.table = defParts[2];
  156. row.from = defParts[1];
  157. row.to = defParts[3];
  158. let i;
  159. for (i = 5; i <= 8; i += 3) {
  160. if (/(UPDATE|DELETE)/.test(defParts[i])) {
  161. row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1];
  162. }
  163. }
  164. }
  165. result.push(row);
  166. }
  167. return result;
  168. }
  169. if (this.isSelectQuery()) {
  170. let result = rows;
  171. // Postgres will treat tables as case-insensitive, so fix the case
  172. // of the returned values to match attributes
  173. if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
  174. const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => {
  175. m[k.toLowerCase()] = k;
  176. return m;
  177. }, {});
  178. result = rows.map(row => {
  179. return _.mapKeys(row, (value, key) => {
  180. const targetAttr = attrsMap[key];
  181. if (typeof targetAttr === 'string' && targetAttr !== key) {
  182. return targetAttr;
  183. }
  184. return key;
  185. });
  186. });
  187. }
  188. return this.handleSelectQuery(result);
  189. }
  190. if (QueryTypes.DESCRIBE === this.options.type) {
  191. const result = {};
  192. for (const row of rows) {
  193. result[row.Field] = {
  194. type: row.Type.toUpperCase(),
  195. allowNull: row.Null === 'YES',
  196. defaultValue: row.Default,
  197. comment: row.Comment,
  198. special: row.special ? this.sequelize.getQueryInterface().queryGenerator.fromArray(row.special) : [],
  199. primaryKey: row.Constraint === 'PRIMARY KEY'
  200. };
  201. if (result[row.Field].type === 'BOOLEAN') {
  202. result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue];
  203. if (result[row.Field].defaultValue === undefined) {
  204. result[row.Field].defaultValue = null;
  205. }
  206. }
  207. if (typeof result[row.Field].defaultValue === 'string') {
  208. result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, '');
  209. if (result[row.Field].defaultValue.includes('::')) {
  210. const split = result[row.Field].defaultValue.split('::');
  211. if (split[1].toLowerCase() !== 'regclass)') {
  212. result[row.Field].defaultValue = split[0];
  213. }
  214. }
  215. }
  216. }
  217. return result;
  218. }
  219. if (this.isVersionQuery()) {
  220. return rows[0].server_version;
  221. }
  222. if (this.isShowOrDescribeQuery()) {
  223. return rows;
  224. }
  225. if (QueryTypes.BULKUPDATE === this.options.type) {
  226. if (!this.options.returning) {
  227. return parseInt(rowCount, 10);
  228. }
  229. return this.handleSelectQuery(rows);
  230. }
  231. if (QueryTypes.BULKDELETE === this.options.type) {
  232. return parseInt(rowCount, 10);
  233. }
  234. if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) {
  235. if (this.instance && this.instance.dataValues) {
  236. for (const key in rows[0]) {
  237. if (Object.prototype.hasOwnProperty.call(rows[0], key)) {
  238. const record = rows[0][key];
  239. const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key);
  240. this.instance.dataValues[attr && attr.fieldName || key] = record;
  241. }
  242. }
  243. }
  244. if (this.isUpsertQuery()) {
  245. return [
  246. this.instance,
  247. null
  248. ];
  249. }
  250. return [
  251. this.instance || rows && (this.options.plain && rows[0] || rows) || undefined,
  252. rowCount
  253. ];
  254. }
  255. if (this.isRawQuery()) {
  256. return [rows, queryResult];
  257. }
  258. return rows;
  259. }
  260. formatError(err) {
  261. let match;
  262. let table;
  263. let index;
  264. let fields;
  265. let errors;
  266. let message;
  267. const code = err.code || err.sqlState;
  268. const errMessage = err.message || err.messagePrimary;
  269. const errDetail = err.detail || err.messageDetail;
  270. switch (code) {
  271. case '23503':
  272. index = errMessage.match(/violates foreign key constraint "(.+?)"/);
  273. index = index ? index[1] : undefined;
  274. table = errMessage.match(/on table "(.+?)"/);
  275. table = table ? table[1] : undefined;
  276. return new sequelizeErrors.ForeignKeyConstraintError({ message: errMessage, fields: null, index, table, parent: err });
  277. case '23505':
  278. // there are multiple different formats of error messages for this error code
  279. // this regex should check at least two
  280. if (errDetail && (match = errDetail.replace(/"/g, '').match(/Key \((.*?)\)=\((.*?)\)/))) {
  281. fields = _.zipObject(match[1].split(', '), match[2].split(', '));
  282. errors = [];
  283. message = 'Validation error';
  284. _.forOwn(fields, (value, field) => {
  285. errors.push(new sequelizeErrors.ValidationErrorItem(
  286. this.getUniqueConstraintErrorMessage(field),
  287. 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB,
  288. field,
  289. value,
  290. this.instance,
  291. 'not_unique'
  292. ));
  293. });
  294. if (this.model && this.model.uniqueKeys) {
  295. _.forOwn(this.model.uniqueKeys, constraint => {
  296. if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
  297. message = constraint.msg;
  298. return false;
  299. }
  300. });
  301. }
  302. return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields });
  303. }
  304. return new sequelizeErrors.UniqueConstraintError({
  305. message: errMessage,
  306. parent: err
  307. });
  308. case '23P01':
  309. match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
  310. if (match) {
  311. fields = _.zipObject(match[1].split(', '), match[2].split(', '));
  312. }
  313. message = 'Exclusion constraint error';
  314. return new sequelizeErrors.ExclusionConstraintError({
  315. message,
  316. constraint: err.constraint,
  317. fields,
  318. table: err.table,
  319. parent: err
  320. });
  321. case '42704':
  322. if (err.sql && /(CONSTRAINT|INDEX)/gi.test(err.sql)) {
  323. message = 'Unknown constraint error';
  324. index = errMessage.match(/(?:constraint|index) "(.+?)"/i);
  325. index = index ? index[1] : undefined;
  326. table = errMessage.match(/relation "(.+?)"/i);
  327. table = table ? table[1] : undefined;
  328. throw new sequelizeErrors.UnknownConstraintError({
  329. message,
  330. constraint: index,
  331. fields,
  332. table,
  333. parent: err
  334. });
  335. }
  336. // falls through
  337. default:
  338. return new sequelizeErrors.DatabaseError(err);
  339. }
  340. }
  341. isForeignKeysQuery() {
  342. 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);
  343. }
  344. getInsertIdField() {
  345. return 'id';
  346. }
  347. }
  348. module.exports = Query;
  349. module.exports.Query = Query;
  350. module.exports.default = Query;