query.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. 'use strict';
  2. const AbstractQuery = require('../abstract/query');
  3. const sequelizeErrors = require('../../errors');
  4. const _ = require('lodash');
  5. const { logger } = require('../../utils/logger');
  6. const ER_DUP_ENTRY = 1062;
  7. const ER_DEADLOCK = 1213;
  8. const ER_ROW_IS_REFERENCED = 1451;
  9. const ER_NO_REFERENCED_ROW = 1452;
  10. const debug = logger.debugContext('sql:mysql');
  11. class Query extends AbstractQuery {
  12. constructor(connection, sequelize, options) {
  13. super(connection, sequelize, { showWarnings: false, ...options });
  14. }
  15. static formatBindParameters(sql, values, dialect) {
  16. const bindParam = [];
  17. const replacementFunc = (match, key, values_) => {
  18. if (values_[key] !== undefined) {
  19. bindParam.push(values_[key]);
  20. return '?';
  21. }
  22. return undefined;
  23. };
  24. sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
  25. return [sql, bindParam.length > 0 ? bindParam : undefined];
  26. }
  27. async run(sql, parameters) {
  28. this.sql = sql;
  29. const { connection, options } = this;
  30. const showWarnings = this.sequelize.options.showWarnings || options.showWarnings;
  31. const complete = this._logQuery(sql, debug, parameters);
  32. if (parameters) {
  33. debug('parameters(%j)', parameters);
  34. }
  35. let results;
  36. try {
  37. if (parameters && parameters.length) {
  38. results = await new Promise((resolve, reject) => {
  39. connection
  40. .execute(sql, parameters, (error, result) => error ? reject(error) : resolve(result))
  41. .setMaxListeners(100);
  42. });
  43. } else {
  44. results = await new Promise((resolve, reject) => {
  45. connection
  46. .query({ sql }, (error, result) => error ? reject(error) : resolve(result))
  47. .setMaxListeners(100);
  48. });
  49. }
  50. } catch (error) {
  51. if (options.transaction && error.errno === ER_DEADLOCK) {
  52. // MySQL automatically rolls-back transactions in the event of a deadlock.
  53. // However, we still initiate a manual rollback to ensure the connection gets released - see #13102.
  54. try {
  55. await options.transaction.rollback();
  56. } catch (error_) {
  57. // Ignore errors - since MySQL automatically rolled back, we're
  58. // not that worried about this redundant rollback failing.
  59. }
  60. options.transaction.finished = 'rollback';
  61. }
  62. error.sql = sql;
  63. error.parameters = parameters;
  64. throw this.formatError(error);
  65. } finally {
  66. complete();
  67. }
  68. if (showWarnings && results && results.warningStatus > 0) {
  69. await this.logWarnings(results);
  70. }
  71. return this.formatResults(results);
  72. }
  73. /**
  74. * High level function that handles the results of a query execution.
  75. *
  76. *
  77. * Example:
  78. * query.formatResults([
  79. * {
  80. * id: 1, // this is from the main table
  81. * attr2: 'snafu', // this is from the main table
  82. * Tasks.id: 1, // this is from the associated table
  83. * Tasks.title: 'task' // this is from the associated table
  84. * }
  85. * ])
  86. *
  87. * @param {Array} data - The result of the query execution.
  88. * @private
  89. */
  90. formatResults(data) {
  91. let result = this.instance;
  92. if (this.isInsertQuery(data)) {
  93. this.handleInsertQuery(data);
  94. if (!this.instance) {
  95. // handle bulkCreate AI primary key
  96. if (
  97. data.constructor.name === 'ResultSetHeader'
  98. && this.model
  99. && this.model.autoIncrementAttribute
  100. && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute
  101. && this.model.rawAttributes[this.model.primaryKeyAttribute]
  102. ) {
  103. const startId = data[this.getInsertIdField()];
  104. result = [];
  105. for (let i = startId; i < startId + data.affectedRows; i++) {
  106. result.push({ [this.model.rawAttributes[this.model.primaryKeyAttribute].field]: i });
  107. }
  108. } else {
  109. result = data[this.getInsertIdField()];
  110. }
  111. }
  112. }
  113. if (this.isSelectQuery()) {
  114. return this.handleSelectQuery(data);
  115. }
  116. if (this.isShowTablesQuery()) {
  117. return this.handleShowTablesQuery(data);
  118. }
  119. if (this.isDescribeQuery()) {
  120. result = {};
  121. for (const _result of data) {
  122. const enumRegex = /^enum/i;
  123. result[_result.Field] = {
  124. type: enumRegex.test(_result.Type) ? _result.Type.replace(enumRegex, 'ENUM') : _result.Type.toUpperCase(),
  125. allowNull: _result.Null === 'YES',
  126. defaultValue: _result.Default,
  127. primaryKey: _result.Key === 'PRI',
  128. autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra')
  129. && _result.Extra.toLowerCase() === 'auto_increment',
  130. comment: _result.Comment ? _result.Comment : null
  131. };
  132. }
  133. return result;
  134. }
  135. if (this.isShowIndexesQuery()) {
  136. return this.handleShowIndexesQuery(data);
  137. }
  138. if (this.isCallQuery()) {
  139. return data[0];
  140. }
  141. if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery()) {
  142. return data.affectedRows;
  143. }
  144. if (this.isVersionQuery()) {
  145. return data[0].version;
  146. }
  147. if (this.isForeignKeysQuery()) {
  148. return data;
  149. }
  150. if (this.isUpsertQuery()) {
  151. return [result, data.affectedRows === 1];
  152. }
  153. if (this.isInsertQuery() || this.isUpdateQuery()) {
  154. return [result, data.affectedRows];
  155. }
  156. if (this.isShowConstraintsQuery()) {
  157. return data;
  158. }
  159. if (this.isRawQuery()) {
  160. // MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
  161. return [data, data];
  162. }
  163. return result;
  164. }
  165. async logWarnings(results) {
  166. const warningResults = await this.run('SHOW WARNINGS');
  167. const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `;
  168. const messages = [];
  169. for (const _warningRow of warningResults) {
  170. if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') {
  171. continue;
  172. }
  173. for (const _warningResult of _warningRow) {
  174. if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) {
  175. messages.push(_warningResult.Message);
  176. } else {
  177. for (const _objectKey of _warningResult.keys()) {
  178. messages.push([_objectKey, _warningResult[_objectKey]].join(': '));
  179. }
  180. }
  181. }
  182. }
  183. this.sequelize.log(warningMessage + messages.join('; '), this.options);
  184. return results;
  185. }
  186. formatError(err) {
  187. const errCode = err.errno || err.code;
  188. switch (errCode) {
  189. case ER_DUP_ENTRY: {
  190. const match = err.message.match(/Duplicate entry '([\s\S]*)' for key '?((.|\s)*?)'?$/);
  191. let fields = {};
  192. let message = 'Validation error';
  193. const values = match ? match[1].split('-') : undefined;
  194. const fieldKey = match ? match[2] : undefined;
  195. const fieldVal = match ? match[1] : undefined;
  196. const uniqueKey = this.model && this.model.uniqueKeys[fieldKey];
  197. if (uniqueKey) {
  198. if (uniqueKey.msg) message = uniqueKey.msg;
  199. fields = _.zipObject(uniqueKey.fields, values);
  200. } else {
  201. fields[fieldKey] = fieldVal;
  202. }
  203. const errors = [];
  204. _.forOwn(fields, (value, field) => {
  205. errors.push(new sequelizeErrors.ValidationErrorItem(
  206. this.getUniqueConstraintErrorMessage(field),
  207. 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB,
  208. field,
  209. value,
  210. this.instance,
  211. 'not_unique'
  212. ));
  213. });
  214. return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields });
  215. }
  216. case ER_ROW_IS_REFERENCED:
  217. case ER_NO_REFERENCED_ROW: {
  218. // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`)
  219. const match = err.message.match(
  220. /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/
  221. );
  222. const quoteChar = match ? match[1] : '`';
  223. const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined;
  224. return new sequelizeErrors.ForeignKeyConstraintError({
  225. reltype: String(errCode) === String(ER_ROW_IS_REFERENCED) ? 'parent' : 'child',
  226. table: match ? match[4] : undefined,
  227. fields,
  228. value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined,
  229. index: match ? match[2] : undefined,
  230. parent: err
  231. });
  232. }
  233. default:
  234. return new sequelizeErrors.DatabaseError(err);
  235. }
  236. }
  237. handleShowIndexesQuery(data) {
  238. // Group by index name, and collect all fields
  239. data = data.reduce((acc, item) => {
  240. if (!(item.Key_name in acc)) {
  241. acc[item.Key_name] = item;
  242. item.fields = [];
  243. }
  244. acc[item.Key_name].fields[item.Seq_in_index - 1] = {
  245. attribute: item.Column_name,
  246. length: item.Sub_part || undefined,
  247. order: item.Collation === 'A' ? 'ASC' : undefined
  248. };
  249. delete item.column_name;
  250. return acc;
  251. }, {});
  252. return _.map(data, item => ({
  253. primary: item.Key_name === 'PRIMARY',
  254. fields: item.fields,
  255. name: item.Key_name,
  256. tableName: item.Table,
  257. unique: item.Non_unique !== 1,
  258. type: item.Index_type
  259. }));
  260. }
  261. }
  262. module.exports = Query;
  263. module.exports.Query = Query;
  264. module.exports.default = Query;