query-interface.test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. 'use strict';
  2. const uniq = require('lodash/uniq');
  3. const chai = require('chai');
  4. const expect = chai.expect;
  5. const Support = require('../../support');
  6. const dialect = Support.getTestDialect();
  7. const { DataTypes } = require('@sequelize/core');
  8. if (dialect.startsWith('postgres')) {
  9. describe('[POSTGRES Specific] QueryInterface', () => {
  10. beforeEach(function () {
  11. this.queryInterface = this.sequelize.queryInterface;
  12. });
  13. describe('fetchDatabaseVersion', () => {
  14. it('reports version', async function () {
  15. const res = await this.queryInterface.fetchDatabaseVersion();
  16. // check that result matches the expected version number format. example 9.5.4
  17. expect(res).to.match(/\d\.\d/);
  18. });
  19. });
  20. describe('renameFunction', () => {
  21. beforeEach(async function () {
  22. // ensure the function names we'll use don't exist before we start.
  23. // then setup our function to rename
  24. await this.queryInterface.dropFunction('rftest1', []).catch(() => {});
  25. await this.queryInterface.dropFunction('rftest2', []).catch(() => {});
  26. await this.queryInterface.createFunction(
  27. 'rftest1',
  28. [],
  29. 'varchar',
  30. 'plpgsql',
  31. "return 'testreturn';",
  32. {},
  33. );
  34. });
  35. it('renames a function', async function () {
  36. await this.queryInterface.renameFunction('rftest1', [], 'rftest2');
  37. const res = await this.sequelize.query('select rftest2();', {
  38. type: this.sequelize.QueryTypes.SELECT,
  39. });
  40. expect(res[0].rftest2).to.be.eql('testreturn');
  41. });
  42. });
  43. describe('createFunction', () => {
  44. beforeEach(async function () {
  45. // make sure we don't have a pre-existing function called create_job
  46. // this is needed to cover the edge case of afterEach not getting called because of an unexpected issue or stopage with the
  47. // test suite causing a failure of afterEach's cleanup to be called.
  48. await this.queryInterface
  49. .dropFunction('create_job', [{ type: 'varchar', name: 'test' }])
  50. // suppress errors here. if create_job doesn't exist thats ok.
  51. .catch(() => {});
  52. });
  53. after(async function () {
  54. // cleanup
  55. await this.queryInterface
  56. .dropFunction('create_job', [{ type: 'varchar', name: 'test' }])
  57. // suppress errors here. if create_job doesn't exist thats ok.
  58. .catch(() => {});
  59. });
  60. it('creates a stored procedure', async function () {
  61. const body = 'return test;';
  62. const options = {};
  63. // make our call to create a function
  64. await this.queryInterface.createFunction(
  65. 'create_job',
  66. [{ type: 'varchar', name: 'test' }],
  67. 'varchar',
  68. 'plpgsql',
  69. body,
  70. options,
  71. );
  72. // validate
  73. const res = await this.sequelize.query("select create_job('test');", {
  74. type: this.sequelize.QueryTypes.SELECT,
  75. });
  76. expect(res[0].create_job).to.be.eql('test');
  77. });
  78. it('treats options as optional', async function () {
  79. const body = 'return test;';
  80. // run with null options parameter
  81. await this.queryInterface.createFunction(
  82. 'create_job',
  83. [{ type: 'varchar', name: 'test' }],
  84. 'varchar',
  85. 'plpgsql',
  86. body,
  87. null,
  88. );
  89. // validate
  90. const res = await this.sequelize.query("select create_job('test');", {
  91. type: this.sequelize.QueryTypes.SELECT,
  92. });
  93. expect(res[0].create_job).to.be.eql('test');
  94. });
  95. it('produces an error when missing expected parameters', async function () {
  96. const body = 'return 1;';
  97. const options = {};
  98. await Promise.all([
  99. // requires functionName
  100. expect(
  101. this.queryInterface.createFunction(
  102. null,
  103. [{ name: 'test' }],
  104. 'integer',
  105. 'plpgsql',
  106. body,
  107. options,
  108. ),
  109. ).to.be.rejectedWith(
  110. /createFunction missing some parameters. Did you pass functionName, returnType, language and body/,
  111. ),
  112. // requires Parameters array
  113. expect(
  114. this.queryInterface.createFunction(
  115. 'create_job',
  116. null,
  117. 'integer',
  118. 'plpgsql',
  119. body,
  120. options,
  121. ),
  122. ).to.be.rejectedWith(/function parameters array required/),
  123. // requires returnType
  124. expect(
  125. this.queryInterface.createFunction(
  126. 'create_job',
  127. [{ type: 'varchar', name: 'test' }],
  128. null,
  129. 'plpgsql',
  130. body,
  131. options,
  132. ),
  133. ).to.be.rejectedWith(
  134. /createFunction missing some parameters. Did you pass functionName, returnType, language and body/,
  135. ),
  136. // requires type in parameter array
  137. expect(
  138. this.queryInterface.createFunction(
  139. 'create_job',
  140. [{ name: 'test' }],
  141. 'integer',
  142. 'plpgsql',
  143. body,
  144. options,
  145. ),
  146. ).to.be.rejectedWith(/function or trigger used with a parameter without any type/),
  147. // requires language
  148. expect(
  149. this.queryInterface.createFunction(
  150. 'create_job',
  151. [{ type: 'varchar', name: 'test' }],
  152. 'varchar',
  153. null,
  154. body,
  155. options,
  156. ),
  157. ).to.be.rejectedWith(
  158. /createFunction missing some parameters. Did you pass functionName, returnType, language and body/,
  159. ),
  160. // requires body
  161. expect(
  162. this.queryInterface.createFunction(
  163. 'create_job',
  164. [{ type: 'varchar', name: 'test' }],
  165. 'varchar',
  166. 'plpgsql',
  167. null,
  168. options,
  169. ),
  170. ).to.be.rejectedWith(
  171. /createFunction missing some parameters. Did you pass functionName, returnType, language and body/,
  172. ),
  173. ]);
  174. });
  175. it('overrides a function', async function () {
  176. const first_body = "return 'first';";
  177. const second_body = "return 'second';";
  178. // create function
  179. await this.queryInterface.createFunction(
  180. 'create_job',
  181. [{ type: 'varchar', name: 'test' }],
  182. 'varchar',
  183. 'plpgsql',
  184. first_body,
  185. null,
  186. );
  187. // override
  188. await this.queryInterface.createFunction(
  189. 'create_job',
  190. [{ type: 'varchar', name: 'test' }],
  191. 'varchar',
  192. 'plpgsql',
  193. second_body,
  194. null,
  195. { force: true },
  196. );
  197. // validate
  198. const res = await this.sequelize.query("select create_job('abc');", {
  199. type: this.sequelize.QueryTypes.SELECT,
  200. });
  201. expect(res[0].create_job).to.be.eql('second');
  202. });
  203. it('produces an error when options.variables is missing expected parameters', function () {
  204. const body = 'return 1;';
  205. expect(
  206. this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], {
  207. variables: 100,
  208. }),
  209. ).to.be.rejectedWith(/expandFunctionVariableList: function variables must be an array/);
  210. expect(
  211. this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], {
  212. variables: [{ name: 'myVar' }],
  213. }),
  214. ).to.be.rejectedWith(/function variable must have a name and type/);
  215. expect(
  216. this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], {
  217. variables: [{ type: 'integer' }],
  218. }),
  219. ).to.be.rejectedWith(/function variable must have a name and type/);
  220. });
  221. it('uses declared variables', async function () {
  222. await this.sequelize.query('DROP FUNCTION IF EXISTS add_one;');
  223. const body = 'RETURN myVar + 1;';
  224. const options = { variables: [{ type: 'integer', name: 'myVar', default: 100 }] };
  225. await this.queryInterface.createFunction(
  226. 'add_one',
  227. [],
  228. 'integer',
  229. 'plpgsql',
  230. body,
  231. [],
  232. options,
  233. );
  234. const res = await this.sequelize.query('select add_one();', {
  235. type: this.sequelize.QueryTypes.SELECT,
  236. });
  237. expect(res[0].add_one).to.be.eql(101);
  238. });
  239. });
  240. describe('dropFunction', () => {
  241. beforeEach(async function () {
  242. const body = 'return test;';
  243. const options = {};
  244. // make sure we have a droptest function in place.
  245. await this.queryInterface
  246. .createFunction(
  247. 'droptest',
  248. [{ type: 'varchar', name: 'test' }],
  249. 'varchar',
  250. 'plpgsql',
  251. body,
  252. options,
  253. )
  254. // suppress errors.. this could fail if the function is already there.. thats ok.
  255. .catch(() => {});
  256. });
  257. it('can drop a function', async function () {
  258. // call drop function
  259. await this.queryInterface.dropFunction('droptest', [{ type: 'varchar', name: 'test' }]);
  260. await expect(
  261. // now call the function we attempted to drop.. if dropFunction worked as expect it should produce an error.
  262. this.sequelize.query("select droptest('test');", {
  263. type: this.sequelize.QueryTypes.SELECT,
  264. }),
  265. // test that we did get the expected error indicating that droptest was properly removed.
  266. ).to.be.rejectedWith(/.*function droptest.* does not exist/);
  267. });
  268. it('produces an error when missing expected parameters', async function () {
  269. await Promise.all([
  270. expect(this.queryInterface.dropFunction()).to.be.rejectedWith(/.*requires functionName/),
  271. expect(this.queryInterface.dropFunction('droptest')).to.be.rejectedWith(
  272. /.*function parameters array required/,
  273. ),
  274. expect(
  275. this.queryInterface.dropFunction('droptest', [{ name: 'test' }]),
  276. ).to.be.rejectedWith(/.*function or trigger used with a parameter without any type/),
  277. ]);
  278. });
  279. });
  280. describe('indexes', () => {
  281. beforeEach(async function () {
  282. await this.queryInterface.dropTable('Group');
  283. await this.queryInterface.createTable('Group', {
  284. username: DataTypes.STRING,
  285. isAdmin: DataTypes.BOOLEAN,
  286. from: DataTypes.STRING,
  287. });
  288. });
  289. it('supports newlines', async function () {
  290. await this.queryInterface.addIndex(
  291. 'Group',
  292. [
  293. this.sequelize.literal(`(
  294. CASE "username"
  295. WHEN 'foo' THEN 'bar'
  296. ELSE 'baz'
  297. END
  298. )`),
  299. ],
  300. { name: 'group_username_case' },
  301. );
  302. const indexes = await this.queryInterface.showIndex('Group');
  303. const indexColumns = uniq(indexes.map(index => index.name));
  304. expect(indexColumns).to.include('group_username_case');
  305. });
  306. it('adds, reads and removes a named functional index to the table', async function () {
  307. await this.queryInterface.addIndex(
  308. 'Group',
  309. [this.sequelize.fn('lower', this.sequelize.col('username'))],
  310. {
  311. name: 'group_username_lower',
  312. },
  313. );
  314. const indexes0 = await this.queryInterface.showIndex('Group');
  315. const indexColumns0 = uniq(indexes0.map(index => index.name));
  316. expect(indexColumns0).to.include('group_username_lower');
  317. await this.queryInterface.removeIndex('Group', 'group_username_lower');
  318. const indexes = await this.queryInterface.showIndex('Group');
  319. const indexColumns = uniq(indexes.map(index => index.name));
  320. expect(indexColumns).to.be.empty;
  321. });
  322. });
  323. });
  324. }