'use strict'; const uniq = require('lodash/uniq'); const chai = require('chai'); const expect = chai.expect; const Support = require('../../support'); const dialect = Support.getTestDialect(); const { DataTypes } = require('@sequelize/core'); if (dialect.startsWith('postgres')) { describe('[POSTGRES Specific] QueryInterface', () => { beforeEach(function () { this.queryInterface = this.sequelize.queryInterface; }); describe('fetchDatabaseVersion', () => { it('reports version', async function () { const res = await this.queryInterface.fetchDatabaseVersion(); // check that result matches the expected version number format. example 9.5.4 expect(res).to.match(/\d\.\d/); }); }); describe('renameFunction', () => { beforeEach(async function () { // ensure the function names we'll use don't exist before we start. // then setup our function to rename await this.queryInterface.dropFunction('rftest1', []).catch(() => {}); await this.queryInterface.dropFunction('rftest2', []).catch(() => {}); await this.queryInterface.createFunction( 'rftest1', [], 'varchar', 'plpgsql', "return 'testreturn';", {}, ); }); it('renames a function', async function () { await this.queryInterface.renameFunction('rftest1', [], 'rftest2'); const res = await this.sequelize.query('select rftest2();', { type: this.sequelize.QueryTypes.SELECT, }); expect(res[0].rftest2).to.be.eql('testreturn'); }); }); describe('createFunction', () => { beforeEach(async function () { // make sure we don't have a pre-existing function called create_job // this is needed to cover the edge case of afterEach not getting called because of an unexpected issue or stopage with the // test suite causing a failure of afterEach's cleanup to be called. await this.queryInterface .dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. .catch(() => {}); }); after(async function () { // cleanup await this.queryInterface .dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. .catch(() => {}); }); it('creates a stored procedure', async function () { const body = 'return test;'; const options = {}; // make our call to create a function await this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options, ); // validate const res = await this.sequelize.query("select create_job('test');", { type: this.sequelize.QueryTypes.SELECT, }); expect(res[0].create_job).to.be.eql('test'); }); it('treats options as optional', async function () { const body = 'return test;'; // run with null options parameter await this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, null, ); // validate const res = await this.sequelize.query("select create_job('test');", { type: this.sequelize.QueryTypes.SELECT, }); expect(res[0].create_job).to.be.eql('test'); }); it('produces an error when missing expected parameters', async function () { const body = 'return 1;'; const options = {}; await Promise.all([ // requires functionName expect( this.queryInterface.createFunction( null, [{ name: 'test' }], 'integer', 'plpgsql', body, options, ), ).to.be.rejectedWith( /createFunction missing some parameters. Did you pass functionName, returnType, language and body/, ), // requires Parameters array expect( this.queryInterface.createFunction( 'create_job', null, 'integer', 'plpgsql', body, options, ), ).to.be.rejectedWith(/function parameters array required/), // requires returnType expect( this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], null, 'plpgsql', body, options, ), ).to.be.rejectedWith( /createFunction missing some parameters. Did you pass functionName, returnType, language and body/, ), // requires type in parameter array expect( this.queryInterface.createFunction( 'create_job', [{ name: 'test' }], 'integer', 'plpgsql', body, options, ), ).to.be.rejectedWith(/function or trigger used with a parameter without any type/), // requires language expect( this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], 'varchar', null, body, options, ), ).to.be.rejectedWith( /createFunction missing some parameters. Did you pass functionName, returnType, language and body/, ), // requires body expect( this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', null, options, ), ).to.be.rejectedWith( /createFunction missing some parameters. Did you pass functionName, returnType, language and body/, ), ]); }); it('overrides a function', async function () { const first_body = "return 'first';"; const second_body = "return 'second';"; // create function await this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', first_body, null, ); // override await this.queryInterface.createFunction( 'create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', second_body, null, { force: true }, ); // validate const res = await this.sequelize.query("select create_job('abc');", { type: this.sequelize.QueryTypes.SELECT, }); expect(res[0].create_job).to.be.eql('second'); }); it('produces an error when options.variables is missing expected parameters', function () { const body = 'return 1;'; expect( this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: 100, }), ).to.be.rejectedWith(/expandFunctionVariableList: function variables must be an array/); expect( this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: [{ name: 'myVar' }], }), ).to.be.rejectedWith(/function variable must have a name and type/); expect( this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: [{ type: 'integer' }], }), ).to.be.rejectedWith(/function variable must have a name and type/); }); it('uses declared variables', async function () { await this.sequelize.query('DROP FUNCTION IF EXISTS add_one;'); const body = 'RETURN myVar + 1;'; const options = { variables: [{ type: 'integer', name: 'myVar', default: 100 }] }; await this.queryInterface.createFunction( 'add_one', [], 'integer', 'plpgsql', body, [], options, ); const res = await this.sequelize.query('select add_one();', { type: this.sequelize.QueryTypes.SELECT, }); expect(res[0].add_one).to.be.eql(101); }); }); describe('dropFunction', () => { beforeEach(async function () { const body = 'return test;'; const options = {}; // make sure we have a droptest function in place. await this.queryInterface .createFunction( 'droptest', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options, ) // suppress errors.. this could fail if the function is already there.. thats ok. .catch(() => {}); }); it('can drop a function', async function () { // call drop function await this.queryInterface.dropFunction('droptest', [{ type: 'varchar', name: 'test' }]); await expect( // now call the function we attempted to drop.. if dropFunction worked as expect it should produce an error. this.sequelize.query("select droptest('test');", { type: this.sequelize.QueryTypes.SELECT, }), // test that we did get the expected error indicating that droptest was properly removed. ).to.be.rejectedWith(/.*function droptest.* does not exist/); }); it('produces an error when missing expected parameters', async function () { await Promise.all([ expect(this.queryInterface.dropFunction()).to.be.rejectedWith(/.*requires functionName/), expect(this.queryInterface.dropFunction('droptest')).to.be.rejectedWith( /.*function parameters array required/, ), expect( this.queryInterface.dropFunction('droptest', [{ name: 'test' }]), ).to.be.rejectedWith(/.*function or trigger used with a parameter without any type/), ]); }); }); describe('indexes', () => { beforeEach(async function () { await this.queryInterface.dropTable('Group'); await this.queryInterface.createTable('Group', { username: DataTypes.STRING, isAdmin: DataTypes.BOOLEAN, from: DataTypes.STRING, }); }); it('supports newlines', async function () { await this.queryInterface.addIndex( 'Group', [ this.sequelize.literal(`( CASE "username" WHEN 'foo' THEN 'bar' ELSE 'baz' END )`), ], { name: 'group_username_case' }, ); const indexes = await this.queryInterface.showIndex('Group'); const indexColumns = uniq(indexes.map(index => index.name)); expect(indexColumns).to.include('group_username_case'); }); it('adds, reads and removes a named functional index to the table', async function () { await this.queryInterface.addIndex( 'Group', [this.sequelize.fn('lower', this.sequelize.col('username'))], { name: 'group_username_lower', }, ); const indexes0 = await this.queryInterface.showIndex('Group'); const indexColumns0 = uniq(indexes0.map(index => index.name)); expect(indexColumns0).to.include('group_username_lower'); await this.queryInterface.removeIndex('Group', 'group_username_lower'); const indexes = await this.queryInterface.showIndex('Group'); const indexColumns = uniq(indexes.map(index => index.name)); expect(indexColumns).to.be.empty; }); }); }); }