query.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. 'use strict';
  2. const AbstractQuery = require('../abstract/query');
  3. const sequelizeErrors = require('../../errors');
  4. const parserStore = require('../parserStore')('mssql');
  5. const _ = require('lodash');
  6. const { logger } = require('../../utils/logger');
  7. const debug = logger.debugContext('sql:mssql');
  8. function getScale(aNum) {
  9. if (!Number.isFinite(aNum)) return 0;
  10. let e = 1;
  11. while (Math.round(aNum * e) / e !== aNum) e *= 10;
  12. return Math.log10(e);
  13. }
  14. class Query extends AbstractQuery {
  15. getInsertIdField() {
  16. return 'id';
  17. }
  18. getSQLTypeFromJsType(value, TYPES) {
  19. const paramType = { type: TYPES.VarChar, typeOptions: {} };
  20. paramType.type = TYPES.NVarChar;
  21. if (typeof value === 'number') {
  22. if (Number.isInteger(value)) {
  23. if (value >= -2147483648 && value <= 2147483647) {
  24. paramType.type = TYPES.Int;
  25. } else {
  26. paramType.type = TYPES.BigInt;
  27. }
  28. } else {
  29. paramType.type = TYPES.Numeric;
  30. //Default to a reasonable numeric precision/scale pending more sophisticated logic
  31. paramType.typeOptions = { precision: 30, scale: getScale(value) };
  32. }
  33. } else if (typeof value === 'boolean') {
  34. paramType.type = TYPES.Bit;
  35. }
  36. if (Buffer.isBuffer(value)) {
  37. paramType.type = TYPES.VarBinary;
  38. }
  39. return paramType;
  40. }
  41. async _run(connection, sql, parameters) {
  42. this.sql = sql;
  43. const { options } = this;
  44. const complete = this._logQuery(sql, debug, parameters);
  45. const query = new Promise((resolve, reject) => {
  46. // TRANSACTION SUPPORT
  47. if (sql.startsWith('BEGIN TRANSACTION')) {
  48. return connection.beginTransaction(error => error ? reject(error) : resolve([]), options.transaction.name, connection.lib.ISOLATION_LEVEL[options.isolationLevel]);
  49. }
  50. if (sql.startsWith('COMMIT TRANSACTION')) {
  51. return connection.commitTransaction(error => error ? reject(error) : resolve([]));
  52. }
  53. if (sql.startsWith('ROLLBACK TRANSACTION')) {
  54. return connection.rollbackTransaction(error => error ? reject(error) : resolve([]), options.transaction.name);
  55. }
  56. if (sql.startsWith('SAVE TRANSACTION')) {
  57. return connection.saveTransaction(error => error ? reject(error) : resolve([]), options.transaction.name);
  58. }
  59. const rows = [];
  60. const request = new connection.lib.Request(sql, (err, rowCount) => err ? reject(err) : resolve([rows, rowCount]));
  61. if (parameters) {
  62. _.forOwn(parameters, (value, key) => {
  63. const paramType = this.getSQLTypeFromJsType(value, connection.lib.TYPES);
  64. request.addParameter(key, paramType.type, value, paramType.typeOptions);
  65. });
  66. }
  67. request.on('row', columns => {
  68. rows.push(columns);
  69. });
  70. connection.execSql(request);
  71. });
  72. let rows, rowCount;
  73. try {
  74. [rows, rowCount] = await query;
  75. } catch (err) {
  76. err.sql = sql;
  77. err.parameters = parameters;
  78. throw this.formatError(err);
  79. }
  80. complete();
  81. if (Array.isArray(rows)) {
  82. rows = rows.map(columns => {
  83. const row = {};
  84. for (const column of columns) {
  85. const typeid = column.metadata.type.id;
  86. const parse = parserStore.get(typeid);
  87. let value = column.value;
  88. if (value !== null & !!parse) {
  89. value = parse(value);
  90. }
  91. row[column.metadata.colName] = value;
  92. }
  93. return row;
  94. });
  95. }
  96. return this.formatResults(rows, rowCount);
  97. }
  98. run(sql, parameters) {
  99. return this.connection.queue.enqueue(() => this._run(this.connection, sql, parameters));
  100. }
  101. static formatBindParameters(sql, values, dialect) {
  102. const bindParam = {};
  103. const replacementFunc = (match, key, values) => {
  104. if (values[key] !== undefined) {
  105. bindParam[key] = values[key];
  106. return `@${key}`;
  107. }
  108. return undefined;
  109. };
  110. sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
  111. return [sql, bindParam];
  112. }
  113. /**
  114. * High level function that handles the results of a query execution.
  115. *
  116. * @param {Array} data - The result of the query execution.
  117. * @param {number} rowCount
  118. * @private
  119. * @example
  120. * Example:
  121. * query.formatResults([
  122. * {
  123. * id: 1, // this is from the main table
  124. * attr2: 'snafu', // this is from the main table
  125. * Tasks.id: 1, // this is from the associated table
  126. * Tasks.title: 'task' // this is from the associated table
  127. * }
  128. * ])
  129. */
  130. formatResults(data, rowCount) {
  131. if (this.isInsertQuery(data)) {
  132. this.handleInsertQuery(data);
  133. return [this.instance || data, rowCount];
  134. }
  135. if (this.isShowTablesQuery()) {
  136. return this.handleShowTablesQuery(data);
  137. }
  138. if (this.isDescribeQuery()) {
  139. const result = {};
  140. for (const _result of data) {
  141. if (_result.Default) {
  142. _result.Default = _result.Default.replace("('", '').replace("')", '').replace(/'/g, '');
  143. }
  144. result[_result.Name] = {
  145. type: _result.Type.toUpperCase(),
  146. allowNull: _result.IsNull === 'YES' ? true : false,
  147. defaultValue: _result.Default,
  148. primaryKey: _result.Constraint === 'PRIMARY KEY',
  149. autoIncrement: _result.IsIdentity === 1,
  150. comment: _result.Comment
  151. };
  152. if (
  153. result[_result.Name].type.includes('CHAR')
  154. && _result.Length
  155. ) {
  156. if (_result.Length === -1) {
  157. result[_result.Name].type += '(MAX)';
  158. } else {
  159. result[_result.Name].type += `(${_result.Length})`;
  160. }
  161. }
  162. }
  163. return result;
  164. }
  165. if (this.isSelectQuery()) {
  166. return this.handleSelectQuery(data);
  167. }
  168. if (this.isShowIndexesQuery()) {
  169. return this.handleShowIndexesQuery(data);
  170. }
  171. if (this.isCallQuery()) {
  172. return data[0];
  173. }
  174. if (this.isBulkUpdateQuery()) {
  175. if (this.options.returning) {
  176. return this.handleSelectQuery(data);
  177. }
  178. return rowCount;
  179. }
  180. if (this.isBulkDeleteQuery()) {
  181. return data[0] ? data[0].AFFECTEDROWS : 0;
  182. }
  183. if (this.isVersionQuery()) {
  184. return data[0].version;
  185. }
  186. if (this.isForeignKeysQuery()) {
  187. return data;
  188. }
  189. if (this.isUpsertQuery()) {
  190. this.handleInsertQuery(data);
  191. return [this.instance || data, data[0].$action === 'INSERT'];
  192. }
  193. if (this.isUpdateQuery()) {
  194. return [this.instance || data, rowCount];
  195. }
  196. if (this.isShowConstraintsQuery()) {
  197. return this.handleShowConstraintsQuery(data);
  198. }
  199. if (this.isRawQuery()) {
  200. return [data, rowCount];
  201. }
  202. return data;
  203. }
  204. handleShowTablesQuery(results) {
  205. return results.map(resultSet => {
  206. return {
  207. tableName: resultSet.TABLE_NAME,
  208. schema: resultSet.TABLE_SCHEMA
  209. };
  210. });
  211. }
  212. handleShowConstraintsQuery(data) {
  213. //Convert snake_case keys to camelCase as it's generated by stored procedure
  214. return data.slice(1).map(result => {
  215. const constraint = {};
  216. for (const key in result) {
  217. constraint[_.camelCase(key)] = result[key];
  218. }
  219. return constraint;
  220. });
  221. }
  222. formatError(err) {
  223. let match;
  224. match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/);
  225. match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/);
  226. if (match && match.length > 1) {
  227. let fields = {};
  228. const uniqueKey = this.model && this.model.uniqueKeys[match[1]];
  229. let message = 'Validation error';
  230. if (uniqueKey && !!uniqueKey.msg) {
  231. message = uniqueKey.msg;
  232. }
  233. if (match[3]) {
  234. const values = match[3].split(',').map(part => part.trim());
  235. if (uniqueKey) {
  236. fields = _.zipObject(uniqueKey.fields, values);
  237. } else {
  238. fields[match[1]] = match[3];
  239. }
  240. }
  241. const errors = [];
  242. _.forOwn(fields, (value, field) => {
  243. errors.push(new sequelizeErrors.ValidationErrorItem(
  244. this.getUniqueConstraintErrorMessage(field),
  245. 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB,
  246. field,
  247. value,
  248. this.instance,
  249. 'not_unique'
  250. ));
  251. });
  252. return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields });
  253. }
  254. match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./) ||
  255. err.message.match(/The DELETE statement conflicted with the REFERENCE constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./) ||
  256. err.message.match(/The (?:INSERT|MERGE|UPDATE) statement conflicted with the FOREIGN KEY constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./);
  257. if (match && match.length > 0) {
  258. return new sequelizeErrors.ForeignKeyConstraintError({
  259. fields: null,
  260. index: match[1],
  261. parent: err
  262. });
  263. }
  264. match = err.message.match(/Could not drop constraint. See previous errors./);
  265. if (match && match.length > 0) {
  266. let constraint = err.sql.match(/(?:constraint|index) \[(.+?)\]/i);
  267. constraint = constraint ? constraint[1] : undefined;
  268. let table = err.sql.match(/table \[(.+?)\]/i);
  269. table = table ? table[1] : undefined;
  270. return new sequelizeErrors.UnknownConstraintError({
  271. message: match[1],
  272. constraint,
  273. table,
  274. parent: err
  275. });
  276. }
  277. return new sequelizeErrors.DatabaseError(err);
  278. }
  279. isShowOrDescribeQuery() {
  280. let result = false;
  281. result = result || this.sql.toLowerCase().startsWith("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'");
  282. result = result || this.sql.toLowerCase().startsWith('select tablename = t.name, name = ind.name,');
  283. result = result || this.sql.toLowerCase().startsWith('exec sys.sp_helpindex @objname');
  284. return result;
  285. }
  286. isShowIndexesQuery() {
  287. return this.sql.toLowerCase().startsWith('exec sys.sp_helpindex @objname');
  288. }
  289. handleShowIndexesQuery(data) {
  290. // Group by index name, and collect all fields
  291. data = data.reduce((acc, item) => {
  292. if (!(item.index_name in acc)) {
  293. acc[item.index_name] = item;
  294. item.fields = [];
  295. }
  296. item.index_keys.split(',').forEach(column => {
  297. let columnName = column.trim();
  298. if (columnName.includes('(-)')) {
  299. columnName = columnName.replace('(-)', '');
  300. }
  301. acc[item.index_name].fields.push({
  302. attribute: columnName,
  303. length: undefined,
  304. order: column.includes('(-)') ? 'DESC' : 'ASC',
  305. collate: undefined
  306. });
  307. });
  308. delete item.index_keys;
  309. return acc;
  310. }, {});
  311. return _.map(data, item => ({
  312. primary: item.index_name.toLowerCase().startsWith('pk'),
  313. fields: item.fields,
  314. name: item.index_name,
  315. tableName: undefined,
  316. unique: item.index_description.toLowerCase().includes('unique'),
  317. type: undefined
  318. }));
  319. }
  320. handleInsertQuery(results, metaData) {
  321. if (this.instance) {
  322. // add the inserted row id to the instance
  323. const autoIncrementAttribute = this.model.autoIncrementAttribute;
  324. let id = null;
  325. let autoIncrementAttributeAlias = null;
  326. if (Object.prototype.hasOwnProperty.call(this.model.rawAttributes, autoIncrementAttribute) &&
  327. this.model.rawAttributes[autoIncrementAttribute].field !== undefined)
  328. autoIncrementAttributeAlias = this.model.rawAttributes[autoIncrementAttribute].field;
  329. id = id || results && results[0][this.getInsertIdField()];
  330. id = id || metaData && metaData[this.getInsertIdField()];
  331. id = id || results && results[0][autoIncrementAttribute];
  332. id = id || autoIncrementAttributeAlias && results && results[0][autoIncrementAttributeAlias];
  333. this.instance[autoIncrementAttribute] = id;
  334. if (this.instance.dataValues) {
  335. for (const key in results[0]) {
  336. if (Object.prototype.hasOwnProperty.call(results[0], key)) {
  337. const record = results[0][key];
  338. const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key);
  339. this.instance.dataValues[attr && attr.fieldName || key] = record;
  340. }
  341. }
  342. }
  343. }
  344. }
  345. }
  346. module.exports = Query;
  347. module.exports.Query = Query;
  348. module.exports.default = Query;