'use strict'; const { expect } = require('chai'); const Support = require('../support'); const { DataTypes, Op } = require('@sequelize/core'); const SEARCH_PATH_ONE = 'schema_one,public'; const SEARCH_PATH_TWO = 'schema_two,public'; let locationId; describe('SearchPath in Model Methods', () => { if (!Support.sequelize.dialect.supports.searchPath) { return; } const vars = Support.beforeAll2(() => { const sequelize = Support.createSequelizeInstance({ prependSearchPath: true, }); return { sequelize }; }); after(() => { return vars.sequelize.close(); }); beforeEach('build restaurant tables', async function () { const { sequelize } = vars; this.Restaurant = sequelize.define( 'restaurant', { foo: DataTypes.STRING, bar: DataTypes.STRING, }, { tableName: 'restaurants' }, ); this.Location = sequelize.define( 'location', { name: DataTypes.STRING, type: DataTypes.ENUM('a', 'b'), }, { tableName: 'locations' }, ); this.Employee = sequelize.define( 'employee', { first_name: DataTypes.STRING, last_name: DataTypes.STRING, }, { tableName: 'employees' }, ); this.Restaurant.belongsTo(this.Location, { foreignKey: 'location_id', foreignKeyConstraints: false, }); this.Employee.belongsTo(this.Restaurant, { foreignKey: 'restaurant_id', foreignKeyConstraints: false, }); this.Restaurant.hasMany(this.Employee, { foreignKey: 'restaurant_id', foreignKeyConstraints: false, }); const Restaurant = this.Restaurant; await sequelize.createSchema('schema_one'); await sequelize.createSchema('schema_two'); await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_ONE }); await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_TWO }); }); describe('enum case', () => { it('able to refresh enum when searchPath is used', async function () { await this.Location.sync({ force: true }); }); }); describe('Add data via model.create, retrieve via model.findOne', () => { it('should be able to insert data into the table in schema_one using create', async function () { const Restaurant = this.Restaurant; await Restaurant.create( { foo: 'one', location_id: locationId, }, { searchPath: SEARCH_PATH_ONE }, ); const obj0 = await Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, }); expect(obj0).to.not.be.null; expect(obj0.foo).to.equal('one'); const restaurantId = obj0.id; const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_ONE }); expect(obj).to.not.be.null; expect(obj.foo).to.equal('one'); }); it('should fail to insert data into schema_two using create', async function () { const Restaurant = this.Restaurant; try { await Restaurant.create( { foo: 'test', }, { searchPath: SEARCH_PATH_TWO }, ); } catch (error) { expect(error).to.not.be.null; } }); it('should be able to insert data into the table in schema_two using create', async function () { const Restaurant = this.Restaurant; await Restaurant.create( { foo: 'two', location_id: locationId, }, { searchPath: SEARCH_PATH_TWO }, ); const obj0 = await Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, }); expect(obj0).to.not.be.null; expect(obj0.foo).to.equal('two'); const restaurantId = obj0.id; const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_TWO }); expect(obj).to.not.be.null; expect(obj.foo).to.equal('two'); }); it('should fail to find schema_one object in schema_two', async function () { const Restaurant = this.Restaurant; const RestaurantObj = await Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_TWO, }); expect(RestaurantObj).to.be.null; }); it('should fail to find schema_two object in schema_one', async function () { const Restaurant = this.Restaurant; const RestaurantObj = await Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_ONE, }); expect(RestaurantObj).to.be.null; }); }); describe('Add data via instance.save, retrieve via model.findAll', () => { it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', async function () { const Restaurant = this.Restaurant; let restaurauntModel = Restaurant.build({ bar: 'one.1' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); restaurauntModel = Restaurant.build({ bar: 'one.2' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); restaurauntModel = Restaurant.build({ bar: 'two.1' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); restaurauntModel = Restaurant.build({ bar: 'two.2' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); restaurauntModel = Restaurant.build({ bar: 'two.3' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); const restaurantsOne0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); expect(restaurantsOne0).to.not.be.null; expect(restaurantsOne0.length).to.equal(2); for (const restaurant of restaurantsOne0) { expect(restaurant.bar).to.contain('one'); } const restaurantsOne = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE }); expect(restaurantsOne).to.not.be.null; expect(restaurantsOne.rows.length).to.equal(2); expect(restaurantsOne.count).to.equal(2); for (const restaurant of restaurantsOne.rows) { expect(restaurant.bar).to.contain('one'); } const restaurantsTwo0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); expect(restaurantsTwo0).to.not.be.null; expect(restaurantsTwo0.length).to.equal(3); for (const restaurant of restaurantsTwo0) { expect(restaurant.bar).to.contain('two'); } const restaurantsTwo = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO }); expect(restaurantsTwo).to.not.be.null; expect(restaurantsTwo.rows.length).to.equal(3); expect(restaurantsTwo.count).to.equal(3); for (const restaurant of restaurantsTwo.rows) { expect(restaurant.bar).to.contain('two'); } }); }); describe('Add data via instance.save, retrieve via model.count and model.find', () => { it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', async function () { const Restaurant = this.Restaurant; let restaurauntModel = Restaurant.build({ bar: 'one.1' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); restaurauntModel = Restaurant.build({ bar: 'one.2' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); restaurauntModel = Restaurant.build({ bar: 'two.1' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); restaurauntModel = Restaurant.build({ bar: 'two.2' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); restaurauntModel = Restaurant.build({ bar: 'two.3' }); await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); const restaurantsOne = await Restaurant.findAll({ where: { bar: { [Op.like]: 'one%' } }, searchPath: SEARCH_PATH_ONE, }); expect(restaurantsOne).to.not.be.null; expect(restaurantsOne.length).to.equal(2); for (const restaurant of restaurantsOne) { expect(restaurant.bar).to.contain('one'); } const count0 = await Restaurant.count({ searchPath: SEARCH_PATH_ONE }); expect(count0).to.not.be.null; expect(count0).to.equal(2); const restaurantsTwo = await Restaurant.findAll({ where: { bar: { [Op.like]: 'two%' } }, searchPath: SEARCH_PATH_TWO, }); expect(restaurantsTwo).to.not.be.null; expect(restaurantsTwo.length).to.equal(3); for (const restaurant of restaurantsTwo) { expect(restaurant.bar).to.contain('two'); } const count = await Restaurant.count({ searchPath: SEARCH_PATH_TWO }); expect(count).to.not.be.null; expect(count).to.equal(3); }); }); describe('Get associated data in public schema via include', () => { beforeEach(async function () { const Location = this.Location; await Location.sync({ force: true }); await Location.create({ name: 'HQ' }); const obj = await Location.findOne({ where: { name: 'HQ' } }); expect(obj).to.not.be.null; expect(obj.name).to.equal('HQ'); locationId = obj.id; }); it('should be able to insert and retrieve associated data into the table in schema_one', async function () { const Restaurant = this.Restaurant; const Location = this.Location; await Restaurant.create( { foo: 'one', location_id: locationId, }, { searchPath: SEARCH_PATH_ONE }, ); const obj = await Restaurant.findOne({ where: { foo: 'one' }, include: [ { model: Location, as: 'location', }, ], searchPath: SEARCH_PATH_ONE, }); expect(obj).to.not.be.null; expect(obj.foo).to.equal('one'); expect(obj.location).to.not.be.null; expect(obj.location.name).to.equal('HQ'); }); it('should be able to insert and retrieve associated data into the table in schema_two', async function () { const Restaurant = this.Restaurant; const Location = this.Location; await Restaurant.create( { foo: 'two', location_id: locationId, }, { searchPath: SEARCH_PATH_TWO }, ); const obj = await Restaurant.findOne({ where: { foo: 'two' }, include: [ { model: Location, as: 'location', }, ], searchPath: SEARCH_PATH_TWO, }); expect(obj).to.not.be.null; expect(obj.foo).to.equal('two'); expect(obj.location).to.not.be.null; expect(obj.location.name).to.equal('HQ'); }); }); describe('Get schema specific associated data via include', () => { beforeEach(async function () { const Employee = this.Employee; await Employee.sync({ force: true, searchPath: SEARCH_PATH_ONE }); await Employee.sync({ force: true, searchPath: SEARCH_PATH_TWO }); }); it('should be able to insert and retrieve associated data into the table in schema_one', async function () { const Restaurant = this.Restaurant; const Employee = this.Employee; await Restaurant.create( { foo: 'one', }, { searchPath: SEARCH_PATH_ONE }, ); const obj1 = await Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, }); expect(obj1).to.not.be.null; expect(obj1.foo).to.equal('one'); const restaurantId = obj1.id; await Employee.create( { first_name: 'Restaurant', last_name: 'one', restaurant_id: restaurantId, }, { searchPath: SEARCH_PATH_ONE }, ); const obj0 = await Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, include: [ { model: Employee, as: 'employees', }, ], }); expect(obj0).to.not.be.null; expect(obj0.employees).to.not.be.null; expect(obj0.employees.length).to.equal(1); expect(obj0.employees[0].last_name).to.equal('one'); const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_ONE }); expect(employees.length).to.equal(1); expect(employees[0].last_name).to.equal('one'); const obj = await Employee.findOne({ where: { last_name: 'one' }, searchPath: SEARCH_PATH_ONE, include: [ { model: Restaurant, as: 'restaurant', }, ], }); expect(obj).to.not.be.null; expect(obj.restaurant).to.not.be.null; expect(obj.restaurant.foo).to.equal('one'); const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_ONE }); expect(restaurant).to.not.be.null; expect(restaurant.foo).to.equal('one'); }); it('should be able to insert and retrieve associated data into the table in schema_two', async function () { const Restaurant = this.Restaurant; const Employee = this.Employee; await Restaurant.create( { foo: 'two', }, { searchPath: SEARCH_PATH_TWO }, ); const obj1 = await Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, }); expect(obj1).to.not.be.null; expect(obj1.foo).to.equal('two'); const restaurantId = obj1.id; await Employee.create( { first_name: 'Restaurant', last_name: 'two', restaurant_id: restaurantId, }, { searchPath: SEARCH_PATH_TWO }, ); const obj0 = await Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, include: [ { model: Employee, as: 'employees', }, ], }); expect(obj0).to.not.be.null; expect(obj0.employees).to.not.be.null; expect(obj0.employees.length).to.equal(1); expect(obj0.employees[0].last_name).to.equal('two'); const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_TWO }); expect(employees.length).to.equal(1); expect(employees[0].last_name).to.equal('two'); const obj = await Employee.findOne({ where: { last_name: 'two' }, searchPath: SEARCH_PATH_TWO, include: [ { model: Restaurant, as: 'restaurant', }, ], }); expect(obj).to.not.be.null; expect(obj.restaurant).to.not.be.null; expect(obj.restaurant.foo).to.equal('two'); const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_TWO }); expect(restaurant).to.not.be.null; expect(restaurant.foo).to.equal('two'); }); }); describe('concurency tests', () => { it('should build and persist instances to 2 schemas consequelizely in any order', async function () { const Restaurant = this.Restaurant; let restaurauntModelSchema1 = Restaurant.build({ bar: 'one.1' }); const restaurauntModelSchema2 = Restaurant.build({ bar: 'two.1' }); await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' }); await restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); const restaurantsOne = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); expect(restaurantsOne).to.not.be.null; expect(restaurantsOne.length).to.equal(2); for (const restaurant of restaurantsOne) { expect(restaurant.bar).to.contain('one'); } const restaurantsTwo = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); expect(restaurantsTwo).to.not.be.null; expect(restaurantsTwo.length).to.equal(1); for (const restaurant of restaurantsTwo) { expect(restaurant.bar).to.contain('two'); } }); }); describe('Edit data via instance.update, retrieve updated instance via model.findAll', () => { it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', async function () { const Restaurant = this.Restaurant; const rnt = await Restaurant.create( { foo: 'one', bar: '1' }, { searchPath: SEARCH_PATH_ONE }, ); await Promise.all([ await rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_ONE }), Restaurant.create({ foo: 'one', bar: '2' }, { searchPath: SEARCH_PATH_ONE }).then(rnt => rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_ONE }), ), Restaurant.create({ foo: 'two', bar: '1' }, { searchPath: SEARCH_PATH_TWO }).then(rnt => rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_TWO }), ), Restaurant.create({ foo: 'two', bar: '2' }, { searchPath: SEARCH_PATH_TWO }).then(rnt => rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_TWO }), ), ]); await Promise.all([ (async () => { const restaurantsOne = await Restaurant.findAll({ where: { bar: 'x.1' }, searchPath: SEARCH_PATH_ONE, }); expect(restaurantsOne.length).to.equal(1); expect(restaurantsOne[0].foo).to.equal('one'); expect(restaurantsOne[0].bar).to.equal('x.1'); })(), (async () => { const restaurantsTwo = await Restaurant.findAll({ where: { bar: 'x.2' }, searchPath: SEARCH_PATH_TWO, }); expect(restaurantsTwo.length).to.equal(1); expect(restaurantsTwo[0].foo).to.equal('two'); expect(restaurantsTwo[0].bar).to.equal('x.2'); })(), ]); }); }); });