API Reference Source

lib/dialects/mariadb/query.js

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