searchPath.test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. 'use strict';
  2. const { expect } = require('chai');
  3. const Support = require('../support');
  4. const { DataTypes, Op } = require('@sequelize/core');
  5. const SEARCH_PATH_ONE = 'schema_one,public';
  6. const SEARCH_PATH_TWO = 'schema_two,public';
  7. let locationId;
  8. describe('SearchPath in Model Methods', () => {
  9. if (!Support.sequelize.dialect.supports.searchPath) {
  10. return;
  11. }
  12. const vars = Support.beforeAll2(() => {
  13. const sequelize = Support.createSequelizeInstance({
  14. prependSearchPath: true,
  15. });
  16. return { sequelize };
  17. });
  18. after(() => {
  19. return vars.sequelize.close();
  20. });
  21. beforeEach('build restaurant tables', async function () {
  22. const { sequelize } = vars;
  23. this.Restaurant = sequelize.define(
  24. 'restaurant',
  25. {
  26. foo: DataTypes.STRING,
  27. bar: DataTypes.STRING,
  28. },
  29. { tableName: 'restaurants' },
  30. );
  31. this.Location = sequelize.define(
  32. 'location',
  33. {
  34. name: DataTypes.STRING,
  35. type: DataTypes.ENUM('a', 'b'),
  36. },
  37. { tableName: 'locations' },
  38. );
  39. this.Employee = sequelize.define(
  40. 'employee',
  41. {
  42. first_name: DataTypes.STRING,
  43. last_name: DataTypes.STRING,
  44. },
  45. { tableName: 'employees' },
  46. );
  47. this.Restaurant.belongsTo(this.Location, {
  48. foreignKey: 'location_id',
  49. foreignKeyConstraints: false,
  50. });
  51. this.Employee.belongsTo(this.Restaurant, {
  52. foreignKey: 'restaurant_id',
  53. foreignKeyConstraints: false,
  54. });
  55. this.Restaurant.hasMany(this.Employee, {
  56. foreignKey: 'restaurant_id',
  57. foreignKeyConstraints: false,
  58. });
  59. const Restaurant = this.Restaurant;
  60. await sequelize.createSchema('schema_one');
  61. await sequelize.createSchema('schema_two');
  62. await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_ONE });
  63. await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_TWO });
  64. });
  65. describe('enum case', () => {
  66. it('able to refresh enum when searchPath is used', async function () {
  67. await this.Location.sync({ force: true });
  68. });
  69. });
  70. describe('Add data via model.create, retrieve via model.findOne', () => {
  71. it('should be able to insert data into the table in schema_one using create', async function () {
  72. const Restaurant = this.Restaurant;
  73. await Restaurant.create(
  74. {
  75. foo: 'one',
  76. location_id: locationId,
  77. },
  78. { searchPath: SEARCH_PATH_ONE },
  79. );
  80. const obj0 = await Restaurant.findOne({
  81. where: { foo: 'one' },
  82. searchPath: SEARCH_PATH_ONE,
  83. });
  84. expect(obj0).to.not.be.null;
  85. expect(obj0.foo).to.equal('one');
  86. const restaurantId = obj0.id;
  87. const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_ONE });
  88. expect(obj).to.not.be.null;
  89. expect(obj.foo).to.equal('one');
  90. });
  91. it('should fail to insert data into schema_two using create', async function () {
  92. const Restaurant = this.Restaurant;
  93. try {
  94. await Restaurant.create(
  95. {
  96. foo: 'test',
  97. },
  98. { searchPath: SEARCH_PATH_TWO },
  99. );
  100. } catch (error) {
  101. expect(error).to.not.be.null;
  102. }
  103. });
  104. it('should be able to insert data into the table in schema_two using create', async function () {
  105. const Restaurant = this.Restaurant;
  106. await Restaurant.create(
  107. {
  108. foo: 'two',
  109. location_id: locationId,
  110. },
  111. { searchPath: SEARCH_PATH_TWO },
  112. );
  113. const obj0 = await Restaurant.findOne({
  114. where: { foo: 'two' },
  115. searchPath: SEARCH_PATH_TWO,
  116. });
  117. expect(obj0).to.not.be.null;
  118. expect(obj0.foo).to.equal('two');
  119. const restaurantId = obj0.id;
  120. const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_TWO });
  121. expect(obj).to.not.be.null;
  122. expect(obj.foo).to.equal('two');
  123. });
  124. it('should fail to find schema_one object in schema_two', async function () {
  125. const Restaurant = this.Restaurant;
  126. const RestaurantObj = await Restaurant.findOne({
  127. where: { foo: 'one' },
  128. searchPath: SEARCH_PATH_TWO,
  129. });
  130. expect(RestaurantObj).to.be.null;
  131. });
  132. it('should fail to find schema_two object in schema_one', async function () {
  133. const Restaurant = this.Restaurant;
  134. const RestaurantObj = await Restaurant.findOne({
  135. where: { foo: 'two' },
  136. searchPath: SEARCH_PATH_ONE,
  137. });
  138. expect(RestaurantObj).to.be.null;
  139. });
  140. });
  141. describe('Add data via instance.save, retrieve via model.findAll', () => {
  142. it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', async function () {
  143. const Restaurant = this.Restaurant;
  144. let restaurauntModel = Restaurant.build({ bar: 'one.1' });
  145. await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE });
  146. restaurauntModel = Restaurant.build({ bar: 'one.2' });
  147. await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE });
  148. restaurauntModel = Restaurant.build({ bar: 'two.1' });
  149. await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO });
  150. restaurauntModel = Restaurant.build({ bar: 'two.2' });
  151. await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO });
  152. restaurauntModel = Restaurant.build({ bar: 'two.3' });
  153. await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO });
  154. const restaurantsOne0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE });
  155. expect(restaurantsOne0).to.not.be.null;
  156. expect(restaurantsOne0.length).to.equal(2);
  157. for (const restaurant of restaurantsOne0) {
  158. expect(restaurant.bar).to.contain('one');
  159. }
  160. const restaurantsOne = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE });
  161. expect(restaurantsOne).to.not.be.null;
  162. expect(restaurantsOne.rows.length).to.equal(2);
  163. expect(restaurantsOne.count).to.equal(2);
  164. for (const restaurant of restaurantsOne.rows) {
  165. expect(restaurant.bar).to.contain('one');
  166. }
  167. const restaurantsTwo0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO });
  168. expect(restaurantsTwo0).to.not.be.null;
  169. expect(restaurantsTwo0.length).to.equal(3);
  170. for (const restaurant of restaurantsTwo0) {
  171. expect(restaurant.bar).to.contain('two');
  172. }
  173. const restaurantsTwo = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO });
  174. expect(restaurantsTwo).to.not.be.null;
  175. expect(restaurantsTwo.rows.length).to.equal(3);
  176. expect(restaurantsTwo.count).to.equal(3);
  177. for (const restaurant of restaurantsTwo.rows) {
  178. expect(restaurant.bar).to.contain('two');
  179. }
  180. });
  181. });
  182. describe('Add data via instance.save, retrieve via model.count and model.find', () => {
  183. it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', async function () {
  184. const Restaurant = this.Restaurant;
  185. let restaurauntModel = Restaurant.build({ bar: 'one.1' });
  186. await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE });
  187. restaurauntModel = Restaurant.build({ bar: 'one.2' });
  188. await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE });
  189. restaurauntModel = Restaurant.build({ bar: 'two.1' });
  190. await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO });
  191. restaurauntModel = Restaurant.build({ bar: 'two.2' });
  192. await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO });
  193. restaurauntModel = Restaurant.build({ bar: 'two.3' });
  194. await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO });
  195. const restaurantsOne = await Restaurant.findAll({
  196. where: { bar: { [Op.like]: 'one%' } },
  197. searchPath: SEARCH_PATH_ONE,
  198. });
  199. expect(restaurantsOne).to.not.be.null;
  200. expect(restaurantsOne.length).to.equal(2);
  201. for (const restaurant of restaurantsOne) {
  202. expect(restaurant.bar).to.contain('one');
  203. }
  204. const count0 = await Restaurant.count({ searchPath: SEARCH_PATH_ONE });
  205. expect(count0).to.not.be.null;
  206. expect(count0).to.equal(2);
  207. const restaurantsTwo = await Restaurant.findAll({
  208. where: { bar: { [Op.like]: 'two%' } },
  209. searchPath: SEARCH_PATH_TWO,
  210. });
  211. expect(restaurantsTwo).to.not.be.null;
  212. expect(restaurantsTwo.length).to.equal(3);
  213. for (const restaurant of restaurantsTwo) {
  214. expect(restaurant.bar).to.contain('two');
  215. }
  216. const count = await Restaurant.count({ searchPath: SEARCH_PATH_TWO });
  217. expect(count).to.not.be.null;
  218. expect(count).to.equal(3);
  219. });
  220. });
  221. describe('Get associated data in public schema via include', () => {
  222. beforeEach(async function () {
  223. const Location = this.Location;
  224. await Location.sync({ force: true });
  225. await Location.create({ name: 'HQ' });
  226. const obj = await Location.findOne({ where: { name: 'HQ' } });
  227. expect(obj).to.not.be.null;
  228. expect(obj.name).to.equal('HQ');
  229. locationId = obj.id;
  230. });
  231. it('should be able to insert and retrieve associated data into the table in schema_one', async function () {
  232. const Restaurant = this.Restaurant;
  233. const Location = this.Location;
  234. await Restaurant.create(
  235. {
  236. foo: 'one',
  237. location_id: locationId,
  238. },
  239. { searchPath: SEARCH_PATH_ONE },
  240. );
  241. const obj = await Restaurant.findOne({
  242. where: { foo: 'one' },
  243. include: [
  244. {
  245. model: Location,
  246. as: 'location',
  247. },
  248. ],
  249. searchPath: SEARCH_PATH_ONE,
  250. });
  251. expect(obj).to.not.be.null;
  252. expect(obj.foo).to.equal('one');
  253. expect(obj.location).to.not.be.null;
  254. expect(obj.location.name).to.equal('HQ');
  255. });
  256. it('should be able to insert and retrieve associated data into the table in schema_two', async function () {
  257. const Restaurant = this.Restaurant;
  258. const Location = this.Location;
  259. await Restaurant.create(
  260. {
  261. foo: 'two',
  262. location_id: locationId,
  263. },
  264. { searchPath: SEARCH_PATH_TWO },
  265. );
  266. const obj = await Restaurant.findOne({
  267. where: { foo: 'two' },
  268. include: [
  269. {
  270. model: Location,
  271. as: 'location',
  272. },
  273. ],
  274. searchPath: SEARCH_PATH_TWO,
  275. });
  276. expect(obj).to.not.be.null;
  277. expect(obj.foo).to.equal('two');
  278. expect(obj.location).to.not.be.null;
  279. expect(obj.location.name).to.equal('HQ');
  280. });
  281. });
  282. describe('Get schema specific associated data via include', () => {
  283. beforeEach(async function () {
  284. const Employee = this.Employee;
  285. await Employee.sync({ force: true, searchPath: SEARCH_PATH_ONE });
  286. await Employee.sync({ force: true, searchPath: SEARCH_PATH_TWO });
  287. });
  288. it('should be able to insert and retrieve associated data into the table in schema_one', async function () {
  289. const Restaurant = this.Restaurant;
  290. const Employee = this.Employee;
  291. await Restaurant.create(
  292. {
  293. foo: 'one',
  294. },
  295. { searchPath: SEARCH_PATH_ONE },
  296. );
  297. const obj1 = await Restaurant.findOne({
  298. where: { foo: 'one' },
  299. searchPath: SEARCH_PATH_ONE,
  300. });
  301. expect(obj1).to.not.be.null;
  302. expect(obj1.foo).to.equal('one');
  303. const restaurantId = obj1.id;
  304. await Employee.create(
  305. {
  306. first_name: 'Restaurant',
  307. last_name: 'one',
  308. restaurant_id: restaurantId,
  309. },
  310. { searchPath: SEARCH_PATH_ONE },
  311. );
  312. const obj0 = await Restaurant.findOne({
  313. where: { foo: 'one' },
  314. searchPath: SEARCH_PATH_ONE,
  315. include: [
  316. {
  317. model: Employee,
  318. as: 'employees',
  319. },
  320. ],
  321. });
  322. expect(obj0).to.not.be.null;
  323. expect(obj0.employees).to.not.be.null;
  324. expect(obj0.employees.length).to.equal(1);
  325. expect(obj0.employees[0].last_name).to.equal('one');
  326. const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_ONE });
  327. expect(employees.length).to.equal(1);
  328. expect(employees[0].last_name).to.equal('one');
  329. const obj = await Employee.findOne({
  330. where: { last_name: 'one' },
  331. searchPath: SEARCH_PATH_ONE,
  332. include: [
  333. {
  334. model: Restaurant,
  335. as: 'restaurant',
  336. },
  337. ],
  338. });
  339. expect(obj).to.not.be.null;
  340. expect(obj.restaurant).to.not.be.null;
  341. expect(obj.restaurant.foo).to.equal('one');
  342. const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_ONE });
  343. expect(restaurant).to.not.be.null;
  344. expect(restaurant.foo).to.equal('one');
  345. });
  346. it('should be able to insert and retrieve associated data into the table in schema_two', async function () {
  347. const Restaurant = this.Restaurant;
  348. const Employee = this.Employee;
  349. await Restaurant.create(
  350. {
  351. foo: 'two',
  352. },
  353. { searchPath: SEARCH_PATH_TWO },
  354. );
  355. const obj1 = await Restaurant.findOne({
  356. where: { foo: 'two' },
  357. searchPath: SEARCH_PATH_TWO,
  358. });
  359. expect(obj1).to.not.be.null;
  360. expect(obj1.foo).to.equal('two');
  361. const restaurantId = obj1.id;
  362. await Employee.create(
  363. {
  364. first_name: 'Restaurant',
  365. last_name: 'two',
  366. restaurant_id: restaurantId,
  367. },
  368. { searchPath: SEARCH_PATH_TWO },
  369. );
  370. const obj0 = await Restaurant.findOne({
  371. where: { foo: 'two' },
  372. searchPath: SEARCH_PATH_TWO,
  373. include: [
  374. {
  375. model: Employee,
  376. as: 'employees',
  377. },
  378. ],
  379. });
  380. expect(obj0).to.not.be.null;
  381. expect(obj0.employees).to.not.be.null;
  382. expect(obj0.employees.length).to.equal(1);
  383. expect(obj0.employees[0].last_name).to.equal('two');
  384. const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_TWO });
  385. expect(employees.length).to.equal(1);
  386. expect(employees[0].last_name).to.equal('two');
  387. const obj = await Employee.findOne({
  388. where: { last_name: 'two' },
  389. searchPath: SEARCH_PATH_TWO,
  390. include: [
  391. {
  392. model: Restaurant,
  393. as: 'restaurant',
  394. },
  395. ],
  396. });
  397. expect(obj).to.not.be.null;
  398. expect(obj.restaurant).to.not.be.null;
  399. expect(obj.restaurant.foo).to.equal('two');
  400. const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_TWO });
  401. expect(restaurant).to.not.be.null;
  402. expect(restaurant.foo).to.equal('two');
  403. });
  404. });
  405. describe('concurency tests', () => {
  406. it('should build and persist instances to 2 schemas consequelizely in any order', async function () {
  407. const Restaurant = this.Restaurant;
  408. let restaurauntModelSchema1 = Restaurant.build({ bar: 'one.1' });
  409. const restaurauntModelSchema2 = Restaurant.build({ bar: 'two.1' });
  410. await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE });
  411. restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' });
  412. await restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO });
  413. await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE });
  414. const restaurantsOne = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE });
  415. expect(restaurantsOne).to.not.be.null;
  416. expect(restaurantsOne.length).to.equal(2);
  417. for (const restaurant of restaurantsOne) {
  418. expect(restaurant.bar).to.contain('one');
  419. }
  420. const restaurantsTwo = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO });
  421. expect(restaurantsTwo).to.not.be.null;
  422. expect(restaurantsTwo.length).to.equal(1);
  423. for (const restaurant of restaurantsTwo) {
  424. expect(restaurant.bar).to.contain('two');
  425. }
  426. });
  427. });
  428. describe('Edit data via instance.update, retrieve updated instance via model.findAll', () => {
  429. it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', async function () {
  430. const Restaurant = this.Restaurant;
  431. const rnt = await Restaurant.create(
  432. { foo: 'one', bar: '1' },
  433. { searchPath: SEARCH_PATH_ONE },
  434. );
  435. await Promise.all([
  436. await rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_ONE }),
  437. Restaurant.create({ foo: 'one', bar: '2' }, { searchPath: SEARCH_PATH_ONE }).then(rnt =>
  438. rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_ONE }),
  439. ),
  440. Restaurant.create({ foo: 'two', bar: '1' }, { searchPath: SEARCH_PATH_TWO }).then(rnt =>
  441. rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_TWO }),
  442. ),
  443. Restaurant.create({ foo: 'two', bar: '2' }, { searchPath: SEARCH_PATH_TWO }).then(rnt =>
  444. rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_TWO }),
  445. ),
  446. ]);
  447. await Promise.all([
  448. (async () => {
  449. const restaurantsOne = await Restaurant.findAll({
  450. where: { bar: 'x.1' },
  451. searchPath: SEARCH_PATH_ONE,
  452. });
  453. expect(restaurantsOne.length).to.equal(1);
  454. expect(restaurantsOne[0].foo).to.equal('one');
  455. expect(restaurantsOne[0].bar).to.equal('x.1');
  456. })(),
  457. (async () => {
  458. const restaurantsTwo = await Restaurant.findAll({
  459. where: { bar: 'x.2' },
  460. searchPath: SEARCH_PATH_TWO,
  461. });
  462. expect(restaurantsTwo.length).to.equal(1);
  463. expect(restaurantsTwo[0].foo).to.equal('two');
  464. expect(restaurantsTwo[0].bar).to.equal('x.2');
  465. })(),
  466. ]);
  467. });
  468. });
  469. });