API Reference Source

lib/dialects/mysql/query.js

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