separate.test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const sinon = require('sinon');
  5. const Support = require('../support');
  6. const { DataTypes } = require('@sequelize/core');
  7. const current = Support.sequelize;
  8. if (current.dialect.supports.groupedLimit) {
  9. describe(Support.getTestDialectTeaser('Include'), () => {
  10. describe('separate', () => {
  11. it('should run a hasMany association in a separate query', async function () {
  12. const User = this.sequelize.define('User', {});
  13. const Task = this.sequelize.define('Task', {});
  14. const sqlSpy = sinon.spy();
  15. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  16. await this.sequelize.sync({ force: true });
  17. await Promise.all([
  18. User.create(
  19. {
  20. id: 1,
  21. tasks: [{}, {}, {}],
  22. },
  23. {
  24. include: [User.Tasks],
  25. },
  26. ),
  27. User.create(
  28. {
  29. id: 2,
  30. tasks: [{}],
  31. },
  32. {
  33. include: [User.Tasks],
  34. },
  35. ),
  36. ]);
  37. const users = await User.findAll({
  38. include: [{ association: User.Tasks, separate: true }],
  39. order: [['id', 'ASC']],
  40. logging: sqlSpy,
  41. });
  42. expect(users[0].get('tasks')).to.be.ok;
  43. expect(users[0].get('tasks').length).to.equal(3);
  44. expect(users[1].get('tasks')).to.be.ok;
  45. expect(users[1].get('tasks').length).to.equal(1);
  46. expect(users[0].get('tasks')[0].createdAt).to.be.ok;
  47. expect(users[0].get('tasks')[0].updatedAt).to.be.ok;
  48. expect(sqlSpy).to.have.been.calledTwice;
  49. });
  50. it('should work even if the id was not included', async function () {
  51. const User = this.sequelize.define('User', {
  52. name: DataTypes.STRING,
  53. });
  54. const Task = this.sequelize.define('Task', {});
  55. const sqlSpy = sinon.spy();
  56. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  57. await this.sequelize.sync({ force: true });
  58. await User.create(
  59. {
  60. id: 1,
  61. tasks: [{}, {}, {}],
  62. },
  63. {
  64. include: [User.Tasks],
  65. },
  66. );
  67. const users = await User.findAll({
  68. attributes: ['name'],
  69. include: [{ association: User.Tasks, separate: true }],
  70. order: [['id', 'ASC']],
  71. logging: sqlSpy,
  72. });
  73. expect(users[0].get('tasks')).to.be.ok;
  74. expect(users[0].get('tasks').length).to.equal(3);
  75. expect(sqlSpy).to.have.been.calledTwice;
  76. });
  77. it('should work even if include does not specify foreign key attribute with custom sourceKey', async function () {
  78. const User = this.sequelize.define('User', {
  79. name: DataTypes.STRING,
  80. userExtraId: {
  81. type: DataTypes.INTEGER,
  82. unique: true,
  83. },
  84. });
  85. const Task = this.sequelize.define('Task', {
  86. title: DataTypes.STRING,
  87. });
  88. const sqlSpy = sinon.spy();
  89. User.Tasks = User.hasMany(Task, {
  90. as: 'tasks',
  91. foreignKey: 'userId',
  92. sourceKey: 'userExtraId',
  93. });
  94. await this.sequelize.sync({ force: true });
  95. await User.create(
  96. {
  97. id: 1,
  98. userExtraId: 222,
  99. tasks: [{}, {}, {}],
  100. },
  101. {
  102. include: [User.Tasks],
  103. },
  104. );
  105. const users = await User.findAll({
  106. attributes: ['name'],
  107. include: [
  108. {
  109. attributes: ['title'],
  110. association: User.Tasks,
  111. separate: true,
  112. },
  113. ],
  114. order: [['id', 'ASC']],
  115. logging: sqlSpy,
  116. });
  117. expect(users[0].get('tasks')).to.be.ok;
  118. expect(users[0].get('tasks').length).to.equal(3);
  119. expect(sqlSpy).to.have.been.calledTwice;
  120. });
  121. it('should not break a nested include with null values', async function () {
  122. const User = this.sequelize.define('User', {});
  123. const Team = this.sequelize.define('Team', {});
  124. const Company = this.sequelize.define('Company', {});
  125. User.Team = User.belongsTo(Team);
  126. Team.Company = Team.belongsTo(Company);
  127. await this.sequelize.sync({ force: true });
  128. await User.create({});
  129. await User.findAll({
  130. include: [{ association: User.Team, include: [Team.Company] }],
  131. });
  132. });
  133. it('should run a hasMany association with limit in a separate query', async function () {
  134. const User = this.sequelize.define('User', {});
  135. const Task = this.sequelize.define('Task', {
  136. userId: {
  137. type: DataTypes.INTEGER,
  138. field: 'user_id',
  139. },
  140. });
  141. const sqlSpy = sinon.spy();
  142. User.Tasks = User.hasMany(Task, { as: 'tasks', foreignKey: 'userId' });
  143. await this.sequelize.sync({ force: true });
  144. await Promise.all([
  145. User.create(
  146. {
  147. id: 1,
  148. tasks: [{}, {}, {}],
  149. },
  150. {
  151. include: [User.Tasks],
  152. },
  153. ),
  154. User.create(
  155. {
  156. id: 2,
  157. tasks: [{}, {}, {}, {}],
  158. },
  159. {
  160. include: [User.Tasks],
  161. },
  162. ),
  163. ]);
  164. const users = await User.findAll({
  165. include: [{ association: User.Tasks, limit: 2 }],
  166. order: [['id', 'ASC']],
  167. logging: sqlSpy,
  168. });
  169. expect(users[0].get('tasks')).to.be.ok;
  170. expect(users[0].get('tasks').length).to.equal(2);
  171. expect(users[1].get('tasks')).to.be.ok;
  172. expect(users[1].get('tasks').length).to.equal(2);
  173. expect(sqlSpy).to.have.been.calledTwice;
  174. });
  175. it('should run a nested (from a non-separate include) hasMany association in a separate query', async function () {
  176. const User = this.sequelize.define('User', {});
  177. const Company = this.sequelize.define('Company');
  178. const Task = this.sequelize.define('Task', {});
  179. const sqlSpy = sinon.spy();
  180. User.Company = User.belongsTo(Company, { as: 'company' });
  181. Company.Tasks = Company.hasMany(Task, { as: 'tasks' });
  182. await this.sequelize.sync({ force: true });
  183. await Promise.all([
  184. User.create(
  185. {
  186. id: 1,
  187. company: {
  188. tasks: [{}, {}, {}],
  189. },
  190. },
  191. {
  192. include: [{ association: User.Company, include: [Company.Tasks] }],
  193. },
  194. ),
  195. User.create(
  196. {
  197. id: 2,
  198. company: {
  199. tasks: [{}],
  200. },
  201. },
  202. {
  203. include: [{ association: User.Company, include: [Company.Tasks] }],
  204. },
  205. ),
  206. ]);
  207. const users = await User.findAll({
  208. include: [
  209. {
  210. association: User.Company,
  211. include: [{ association: Company.Tasks, separate: true }],
  212. },
  213. ],
  214. order: [['id', 'ASC']],
  215. logging: sqlSpy,
  216. });
  217. expect(users[0].get('company').get('tasks')).to.be.ok;
  218. expect(users[0].get('company').get('tasks').length).to.equal(3);
  219. expect(users[1].get('company').get('tasks')).to.be.ok;
  220. expect(users[1].get('company').get('tasks').length).to.equal(1);
  221. expect(sqlSpy).to.have.been.calledTwice;
  222. });
  223. it('should work having a separate include between a parent and child include', async function () {
  224. const User = this.sequelize.define('User', {});
  225. const Project = this.sequelize.define('Project');
  226. const Company = this.sequelize.define('Company');
  227. const Task = this.sequelize.define('Task', {});
  228. const sqlSpy = sinon.spy();
  229. Company.Users = Company.hasMany(User, { as: 'users' });
  230. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  231. Task.Project = Task.belongsTo(Project, { as: 'project' });
  232. await this.sequelize.sync({ force: true });
  233. await Promise.all([
  234. Company.create(
  235. {
  236. id: 1,
  237. users: [
  238. {
  239. tasks: [{ project: {} }, { project: {} }, { project: {} }],
  240. },
  241. ],
  242. },
  243. {
  244. include: [
  245. {
  246. association: Company.Users,
  247. include: [
  248. {
  249. association: User.Tasks,
  250. include: [Task.Project],
  251. },
  252. ],
  253. },
  254. ],
  255. },
  256. ),
  257. ]);
  258. const companies = await Company.findAll({
  259. include: [
  260. {
  261. association: Company.Users,
  262. include: [
  263. {
  264. association: User.Tasks,
  265. separate: true,
  266. include: [Task.Project],
  267. },
  268. ],
  269. },
  270. ],
  271. order: [['id', 'ASC']],
  272. logging: sqlSpy,
  273. });
  274. expect(sqlSpy).to.have.been.calledTwice;
  275. expect(companies[0].users[0].tasks[0].project).to.be.ok;
  276. });
  277. it('should run two nested hasMany association in a separate queries', async function () {
  278. const User = this.sequelize.define('User', {});
  279. const Project = this.sequelize.define('Project', {});
  280. const Task = this.sequelize.define('Task', {});
  281. const sqlSpy = sinon.spy();
  282. User.Projects = User.hasMany(Project, { as: 'projects' });
  283. Project.Tasks = Project.hasMany(Task, { as: 'tasks' });
  284. await this.sequelize.sync({ force: true });
  285. await Promise.all([
  286. User.create(
  287. {
  288. id: 1,
  289. projects: [
  290. {
  291. id: 1,
  292. tasks: [{}, {}, {}],
  293. },
  294. {
  295. id: 2,
  296. tasks: [{}],
  297. },
  298. ],
  299. },
  300. {
  301. include: [{ association: User.Projects, include: [Project.Tasks] }],
  302. },
  303. ),
  304. User.create(
  305. {
  306. id: 2,
  307. projects: [
  308. {
  309. id: 3,
  310. tasks: [{}, {}],
  311. },
  312. ],
  313. },
  314. {
  315. include: [{ association: User.Projects, include: [Project.Tasks] }],
  316. },
  317. ),
  318. ]);
  319. const users = await User.findAll({
  320. include: [
  321. {
  322. association: User.Projects,
  323. separate: true,
  324. include: [{ association: Project.Tasks, separate: true }],
  325. },
  326. ],
  327. order: [['id', 'ASC']],
  328. logging: sqlSpy,
  329. });
  330. const u1projects = users[0].get('projects');
  331. expect(u1projects).to.be.ok;
  332. expect(u1projects[0].get('tasks')).to.be.ok;
  333. expect(u1projects[1].get('tasks')).to.be.ok;
  334. expect(u1projects.length).to.equal(2);
  335. // WTB ES2015 syntax ...
  336. expect(u1projects.find(p => p.id === 1).get('tasks').length).to.equal(3);
  337. expect(u1projects.find(p => p.id === 2).get('tasks').length).to.equal(1);
  338. expect(users[1].get('projects')).to.be.ok;
  339. expect(users[1].get('projects')[0].get('tasks')).to.be.ok;
  340. expect(users[1].get('projects').length).to.equal(1);
  341. expect(users[1].get('projects')[0].get('tasks').length).to.equal(2);
  342. expect(sqlSpy).to.have.been.calledThrice;
  343. });
  344. it('should work with two schema models in a hasMany association', async function () {
  345. const User = this.sequelize.define('User', {}, { schema: 'archive' });
  346. const Task = this.sequelize.define(
  347. 'Task',
  348. {
  349. id: { type: DataTypes.INTEGER, primaryKey: true },
  350. title: DataTypes.STRING,
  351. },
  352. { schema: 'archive' },
  353. );
  354. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  355. await this.sequelize.createSchema('archive');
  356. await this.sequelize.sync({ force: true });
  357. await Promise.all([
  358. User.create(
  359. {
  360. id: 1,
  361. tasks: [
  362. { id: 1, title: 'b' },
  363. { id: 2, title: 'd' },
  364. { id: 3, title: 'c' },
  365. { id: 4, title: 'a' },
  366. ],
  367. },
  368. {
  369. include: [User.Tasks],
  370. },
  371. ),
  372. User.create(
  373. {
  374. id: 2,
  375. tasks: [
  376. { id: 5, title: 'a' },
  377. { id: 6, title: 'c' },
  378. { id: 7, title: 'b' },
  379. ],
  380. },
  381. {
  382. include: [User.Tasks],
  383. },
  384. ),
  385. ]);
  386. const result = await User.findAll({
  387. include: [{ model: Task, limit: 2, as: 'tasks', order: [['id', 'ASC']] }],
  388. order: [['id', 'ASC']],
  389. });
  390. expect(result[0].tasks.length).to.equal(2);
  391. expect(result[0].tasks[0].title).to.equal('b');
  392. expect(result[0].tasks[1].title).to.equal('d');
  393. expect(result[1].tasks.length).to.equal(2);
  394. expect(result[1].tasks[0].title).to.equal('a');
  395. expect(result[1].tasks[1].title).to.equal('c');
  396. await this.sequelize.queryInterface.dropAllTables({ schema: 'archive' });
  397. await this.sequelize.dropSchema('archive');
  398. const schemas = await this.sequelize.queryInterface.listSchemas();
  399. expect(schemas).to.not.include('archive');
  400. });
  401. it('should work with required non-separate parent and required child', async function () {
  402. const User = this.sequelize.define('User', {});
  403. const Task = this.sequelize.define('Task', {});
  404. const Company = this.sequelize.define('Company', {});
  405. Task.User = Task.belongsTo(User);
  406. User.Tasks = User.hasMany(Task);
  407. User.Company = User.belongsTo(Company);
  408. await this.sequelize.sync({ force: true });
  409. const task = await Task.create({ id: 1 });
  410. const user = await task.createUser({ id: 2 });
  411. await user.createCompany({ id: 3 });
  412. const results = await Task.findAll({
  413. include: [
  414. {
  415. association: Task.User,
  416. required: true,
  417. include: [
  418. {
  419. association: User.Tasks,
  420. attributes: ['userId'],
  421. separate: true,
  422. include: [
  423. {
  424. association: Task.User,
  425. attributes: ['id'],
  426. required: true,
  427. include: [
  428. {
  429. association: User.Company,
  430. },
  431. ],
  432. },
  433. ],
  434. },
  435. ],
  436. },
  437. ],
  438. });
  439. expect(results.length).to.equal(1);
  440. expect(results[0].id).to.equal(1);
  441. expect(results[0].user.id).to.equal(2);
  442. expect(results[0].user.tasks.length).to.equal(1);
  443. expect(results[0].user.tasks[0].user.id).to.equal(2);
  444. expect(results[0].user.tasks[0].user.company.id).to.equal(3);
  445. });
  446. });
  447. });
  448. }