query.js 13 KB

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