query-interface.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. 'use strict';
  2. const DataTypes = require('../../data-types');
  3. const QueryTypes = require('../../query-types');
  4. const { QueryInterface } = require('../abstract/query-interface');
  5. const Utils = require('../../utils');
  6. /**
  7. * The interface that Sequelize uses to talk with Postgres database
  8. */
  9. class PostgresQueryInterface extends QueryInterface {
  10. /**
  11. * Ensure enum and their values.
  12. *
  13. * @param {string} tableName Name of table to create
  14. * @param {object} attributes Object representing a list of normalized table attributes
  15. * @param {object} [options]
  16. * @param {Model} [model]
  17. *
  18. * @protected
  19. */
  20. async ensureEnums(tableName, attributes, options, model) {
  21. const keys = Object.keys(attributes);
  22. const keyLen = keys.length;
  23. let sql = '';
  24. let promises = [];
  25. let i = 0;
  26. for (i = 0; i < keyLen; i++) {
  27. const attribute = attributes[keys[i]];
  28. const type = attribute.type;
  29. if (
  30. type instanceof DataTypes.ENUM ||
  31. type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM
  32. ) {
  33. sql = this.queryGenerator.pgListEnums(tableName, attribute.field || keys[i], options);
  34. promises.push(this.sequelize.query(
  35. sql,
  36. { ...options, plain: true, raw: true, type: QueryTypes.SELECT }
  37. ));
  38. }
  39. }
  40. const results = await Promise.all(promises);
  41. promises = [];
  42. let enumIdx = 0;
  43. // This little function allows us to re-use the same code that prepends or appends new value to enum array
  44. const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => {
  45. const valueOptions = { ...options };
  46. valueOptions.before = null;
  47. valueOptions.after = null;
  48. switch (position) {
  49. case 'after':
  50. valueOptions.after = relativeValue;
  51. break;
  52. case 'before':
  53. default:
  54. valueOptions.before = relativeValue;
  55. break;
  56. }
  57. promises.splice(spliceStart, 0, () => {
  58. return this.sequelize.query(this.queryGenerator.pgEnumAdd(
  59. tableName, field, value, valueOptions
  60. ), valueOptions);
  61. });
  62. };
  63. for (i = 0; i < keyLen; i++) {
  64. const attribute = attributes[keys[i]];
  65. const type = attribute.type;
  66. const enumType = type.type || type;
  67. const field = attribute.field || keys[i];
  68. if (
  69. type instanceof DataTypes.ENUM ||
  70. type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM
  71. ) {
  72. // If the enum type doesn't exist then create it
  73. if (!results[enumIdx]) {
  74. promises.push(() => {
  75. return this.sequelize.query(this.queryGenerator.pgEnum(tableName, field, enumType, options), { ...options, raw: true });
  76. });
  77. } else if (!!results[enumIdx] && !!model) {
  78. const enumVals = this.queryGenerator.fromArray(results[enumIdx].enum_value);
  79. const vals = enumType.values;
  80. // Going through already existing values allows us to make queries that depend on those values
  81. // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values
  82. // Then we append the rest of new values AFTER the latest already existing value
  83. // E.g.: [1,2] -> [0,2,1] ==> [1,0,2]
  84. // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4]
  85. // E.g.: [1] -> [0,2,3] ==> [1,0,2,3]
  86. let lastOldEnumValue;
  87. let rightestPosition = -1;
  88. for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) {
  89. const enumVal = enumVals[oldIndex];
  90. const newIdx = vals.indexOf(enumVal);
  91. lastOldEnumValue = enumVal;
  92. if (newIdx === -1) {
  93. continue;
  94. }
  95. const newValuesBefore = vals.slice(0, newIdx);
  96. const promisesLength = promises.length;
  97. // we go in reverse order so we could stop when we meet old value
  98. for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) {
  99. if (~enumVals.indexOf(newValuesBefore[reverseIdx])) {
  100. break;
  101. }
  102. addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength);
  103. }
  104. // we detect the most 'right' position of old value in new enum array so we can append new values to it
  105. if (newIdx > rightestPosition) {
  106. rightestPosition = newIdx;
  107. }
  108. }
  109. if (lastOldEnumValue && rightestPosition < vals.length - 1) {
  110. const remainingEnumValues = vals.slice(rightestPosition + 1);
  111. for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) {
  112. addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after');
  113. }
  114. }
  115. enumIdx++;
  116. }
  117. }
  118. }
  119. const result = await promises
  120. .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve());
  121. // If ENUM processed, then refresh OIDs
  122. if (promises.length) {
  123. await this.sequelize.dialect.connectionManager._refreshDynamicOIDs();
  124. }
  125. return result;
  126. }
  127. /**
  128. * @override
  129. */
  130. async getForeignKeyReferencesForTable(tableName, options) {
  131. const queryOptions = {
  132. ...options,
  133. type: QueryTypes.FOREIGNKEYS
  134. };
  135. // postgres needs some special treatment as those field names returned are all lowercase
  136. // in order to keep same result with other dialects.
  137. const query = this.queryGenerator.getForeignKeyReferencesQuery(tableName, this.sequelize.config.database);
  138. const result = await this.sequelize.query(query, queryOptions);
  139. return result.map(Utils.camelizeObjectKeys);
  140. }
  141. /**
  142. * Drop specified enum from database (Postgres only)
  143. *
  144. * @param {string} [enumName] Enum name to drop
  145. * @param {object} options Query options
  146. *
  147. * @returns {Promise}
  148. */
  149. async dropEnum(enumName, options) {
  150. options = options || {};
  151. return this.sequelize.query(
  152. this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(enumName)),
  153. { ...options, raw: true }
  154. );
  155. }
  156. /**
  157. * Drop all enums from database (Postgres only)
  158. *
  159. * @param {object} options Query options
  160. *
  161. * @returns {Promise}
  162. */
  163. async dropAllEnums(options) {
  164. options = options || {};
  165. const enums = await this.pgListEnums(null, options);
  166. return await Promise.all(enums.map(result => this.sequelize.query(
  167. this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(result.enum_name)),
  168. { ...options, raw: true }
  169. )));
  170. }
  171. /**
  172. * List all enums (Postgres only)
  173. *
  174. * @param {string} [tableName] Table whose enum to list
  175. * @param {object} [options] Query options
  176. *
  177. * @returns {Promise}
  178. */
  179. async pgListEnums(tableName, options) {
  180. options = options || {};
  181. const sql = this.queryGenerator.pgListEnums(tableName);
  182. return this.sequelize.query(sql, { ...options, plain: false, raw: true, type: QueryTypes.SELECT });
  183. }
  184. /**
  185. * Since postgres has a special case for enums, we should drop the related
  186. * enum type within the table and attribute
  187. *
  188. * @override
  189. */
  190. async dropTable(tableName, options) {
  191. await super.dropTable(tableName, options);
  192. const promises = [];
  193. const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' });
  194. if (!instanceTable) {
  195. // Do nothing when model is not available
  196. return;
  197. }
  198. const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName;
  199. const keys = Object.keys(instanceTable.rawAttributes);
  200. const keyLen = keys.length;
  201. for (let i = 0; i < keyLen; i++) {
  202. if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) {
  203. const sql = this.queryGenerator.pgEnumDrop(getTableName, keys[i]);
  204. options.supportsSearchPath = false;
  205. promises.push(this.sequelize.query(sql, { ...options, raw: true }));
  206. }
  207. }
  208. await Promise.all(promises);
  209. }
  210. }
  211. exports.PostgresQueryInterface = PostgresQueryInterface;