findAndCountAll.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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, Op } = require('@sequelize/core');
  7. describe(Support.getTestDialectTeaser('Include'), () => {
  8. before(function () {
  9. this.clock = sinon.useFakeTimers();
  10. });
  11. after(function () {
  12. this.clock.restore();
  13. });
  14. describe('findAndCountAll', () => {
  15. it('should be able to include two required models with a limit. Result rows should match limit.', async function () {
  16. const Project = this.sequelize.define('Project', {
  17. id: { type: DataTypes.INTEGER, primaryKey: true },
  18. name: DataTypes.STRING(40),
  19. });
  20. const Task = this.sequelize.define('Task', {
  21. name: DataTypes.STRING(40),
  22. fk: DataTypes.INTEGER,
  23. });
  24. const Employee = this.sequelize.define('Employee', {
  25. name: DataTypes.STRING(40),
  26. fk: DataTypes.INTEGER,
  27. });
  28. Project.hasMany(Task, { foreignKey: 'fk', foreignKeyConstraints: false });
  29. Project.hasMany(Employee, { foreignKey: 'fk', foreignKeyConstraints: false });
  30. Task.belongsTo(Project, { foreignKey: 'fk', foreignKeyConstraints: false });
  31. Employee.belongsTo(Project, { foreignKey: 'fk', foreignKeyConstraints: false });
  32. // Sync them
  33. await this.sequelize.sync({ force: true });
  34. // Create an enviroment
  35. await Promise.all([
  36. Project.bulkCreate([
  37. { id: 1, name: 'No tasks' },
  38. { id: 2, name: 'No tasks no employees' },
  39. { id: 3, name: 'No employees' },
  40. { id: 4, name: 'In progress A' },
  41. { id: 5, name: 'In progress B' },
  42. { id: 6, name: 'In progress C' },
  43. ]),
  44. Task.bulkCreate([
  45. { name: 'Important task', fk: 3 },
  46. { name: 'Important task', fk: 4 },
  47. { name: 'Important task', fk: 5 },
  48. { name: 'Important task', fk: 6 },
  49. ]),
  50. Employee.bulkCreate([
  51. { name: 'Jane Doe', fk: 1 },
  52. { name: 'John Doe', fk: 4 },
  53. { name: 'Jane John Doe', fk: 5 },
  54. { name: 'John Jane Doe', fk: 6 },
  55. ]),
  56. ]);
  57. // Find all projects with tasks and employees
  58. const availableProjects = 3;
  59. const limit = 2;
  60. const result = await Project.findAndCountAll({
  61. include: [
  62. {
  63. model: Task,
  64. required: true,
  65. },
  66. {
  67. model: Employee,
  68. required: true,
  69. },
  70. ],
  71. limit,
  72. });
  73. expect(result.count).to.equal(availableProjects);
  74. expect(result.rows.length).to.equal(
  75. limit,
  76. 'Complete set of available rows were not returned.',
  77. );
  78. });
  79. it('should be able to include a required model. Result rows should match count', async function () {
  80. const User = this.sequelize.define(
  81. 'User',
  82. { name: DataTypes.STRING(40) },
  83. { paranoid: true },
  84. );
  85. const SomeConnection = this.sequelize.define(
  86. 'SomeConnection',
  87. {
  88. m: DataTypes.STRING(40),
  89. fk: DataTypes.INTEGER,
  90. u: DataTypes.INTEGER,
  91. },
  92. { paranoid: true },
  93. );
  94. const A = this.sequelize.define('A', { name: DataTypes.STRING(40) }, { paranoid: true });
  95. const B = this.sequelize.define('B', { name: DataTypes.STRING(40) }, { paranoid: true });
  96. const C = this.sequelize.define('C', { name: DataTypes.STRING(40) }, { paranoid: true });
  97. // Associate them
  98. User.hasMany(SomeConnection, { foreignKey: 'u', foreignKeyConstraints: false });
  99. SomeConnection.belongsTo(User, { foreignKey: 'u', foreignKeyConstraints: false });
  100. SomeConnection.belongsTo(A, { foreignKey: 'fk', foreignKeyConstraints: false });
  101. SomeConnection.belongsTo(B, { foreignKey: 'fk', foreignKeyConstraints: false });
  102. SomeConnection.belongsTo(C, { foreignKey: 'fk', foreignKeyConstraints: false });
  103. A.hasMany(SomeConnection, { foreignKey: 'fk', foreignKeyConstraints: false });
  104. B.hasMany(SomeConnection, { foreignKey: 'fk', foreignKeyConstraints: false });
  105. C.hasMany(SomeConnection, { foreignKey: 'fk', foreignKeyConstraints: false });
  106. // Sync them
  107. await this.sequelize.sync({ force: true });
  108. // Create an enviroment
  109. await Promise.all([
  110. User.bulkCreate([
  111. { name: 'Youtube' },
  112. { name: 'Facebook' },
  113. { name: 'Google' },
  114. { name: 'Yahoo' },
  115. { name: '404' },
  116. ]),
  117. SomeConnection.bulkCreate([
  118. // Lets count, m: A and u: 1
  119. { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted
  120. { u: 2, m: 'A', fk: 1 },
  121. { u: 3, m: 'A', fk: 1 },
  122. { u: 4, m: 'A', fk: 1 },
  123. { u: 5, m: 'A', fk: 1 },
  124. { u: 1, m: 'B', fk: 1 },
  125. { u: 2, m: 'B', fk: 1 },
  126. { u: 3, m: 'B', fk: 1 },
  127. { u: 4, m: 'B', fk: 1 },
  128. { u: 5, m: 'B', fk: 1 },
  129. { u: 1, m: 'C', fk: 1 },
  130. { u: 2, m: 'C', fk: 1 },
  131. { u: 3, m: 'C', fk: 1 },
  132. { u: 4, m: 'C', fk: 1 },
  133. { u: 5, m: 'C', fk: 1 },
  134. { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted
  135. { u: 4, m: 'A', fk: 2 },
  136. { u: 2, m: 'A', fk: 2 },
  137. { u: 1, m: 'A', fk: 3 }, // 3
  138. { u: 2, m: 'A', fk: 3 },
  139. { u: 3, m: 'A', fk: 3 },
  140. { u: 2, m: 'B', fk: 2 },
  141. { u: 1, m: 'A', fk: 4 }, // 4
  142. { u: 4, m: 'A', fk: 2 },
  143. ]),
  144. A.bulkCreate([
  145. { name: 'Just' },
  146. { name: 'for' },
  147. { name: 'testing' },
  148. { name: 'proposes' },
  149. { name: 'only' },
  150. ]),
  151. B.bulkCreate([{ name: 'this should not' }, { name: 'be loaded' }]),
  152. C.bulkCreate([{ name: 'because we only want A' }]),
  153. ]);
  154. // Delete some of conns to prove the concept
  155. await SomeConnection.destroy({
  156. where: {
  157. m: 'A',
  158. u: 1,
  159. fk: [1, 2],
  160. },
  161. });
  162. this.clock.tick(1000);
  163. // Last and most important queries ( we connected 4, but deleted 2, witch means we must get 2 only )
  164. const result = await A.findAndCountAll({
  165. include: [
  166. {
  167. model: SomeConnection,
  168. required: true,
  169. where: {
  170. m: 'A', // Pseudo Polymorphy
  171. u: 1,
  172. },
  173. },
  174. ],
  175. limit: 5,
  176. });
  177. expect(result.count).to.equal(2);
  178. expect(result.rows.length).to.equal(2);
  179. });
  180. it('should count on a where and not use an uneeded include', async function () {
  181. const Project = this.sequelize.define('Project', {
  182. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  183. project_name: { type: DataTypes.STRING },
  184. });
  185. const User = this.sequelize.define('User', {
  186. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  187. user_name: { type: DataTypes.STRING },
  188. });
  189. User.hasMany(Project);
  190. await User.sync({ force: true });
  191. await Project.sync({ force: true });
  192. const results = await Promise.all([
  193. User.create(),
  194. Project.create(),
  195. Project.create(),
  196. Project.create(),
  197. ]);
  198. const user = results[0];
  199. const userId = user.id;
  200. await user.setProjects([results[1], results[2], results[3]]);
  201. const result = await User.findAndCountAll({
  202. where: { id: userId },
  203. include: [Project],
  204. distinct: true,
  205. });
  206. expect(result.rows.length).to.equal(1);
  207. expect(result.rows[0].projects.length).to.equal(3);
  208. expect(result.count).to.equal(1);
  209. });
  210. it('should return the correct count and rows when using a required belongsTo and a limit', async function () {
  211. const s = this.sequelize;
  212. const Foo = s.define('Foo', {});
  213. const Bar = s.define('Bar', {});
  214. Foo.hasMany(Bar);
  215. Bar.belongsTo(Foo);
  216. await s.sync({ force: true });
  217. // Make five instances of Foo
  218. await Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]);
  219. // Make four instances of Bar, related to the last four instances of Foo
  220. await Bar.bulkCreate([{ fooId: 2 }, { fooId: 3 }, { fooId: 4 }, { fooId: 5 }]);
  221. // Query for the first two instances of Foo which have related Bars
  222. const result0 = await Foo.findAndCountAll({
  223. include: [{ model: Bar, required: true }],
  224. limit: 2,
  225. });
  226. const items = await Foo.findAll({
  227. include: [{ model: Bar, required: true }],
  228. limit: 2,
  229. });
  230. expect(items.length).to.equal(2);
  231. const result = result0;
  232. expect(result.count).to.equal(4);
  233. // The first two of those should be returned due to the limit (Foo
  234. // instances 2 and 3)
  235. expect(result.rows.length).to.equal(2);
  236. });
  237. it('should return the correct count and rows when using a required belongsTo with a where condition and a limit', async function () {
  238. const Foo = this.sequelize.define('Foo', {});
  239. const Bar = this.sequelize.define('Bar', { m: DataTypes.STRING(40) });
  240. Foo.hasMany(Bar);
  241. Bar.belongsTo(Foo);
  242. await this.sequelize.sync({ force: true });
  243. await Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]);
  244. // Make four instances of Bar, related to the first two instances of Foo
  245. await Bar.bulkCreate([
  246. { fooId: 1, m: 'yes' },
  247. { fooId: 1, m: 'yes' },
  248. { fooId: 1, m: 'no' },
  249. { fooId: 2, m: 'yes' },
  250. ]);
  251. // Query for the first instance of Foo which have related Bars with m === 'yes'
  252. const result = await Foo.findAndCountAll({
  253. include: [{ model: Bar, where: { m: 'yes' } }],
  254. limit: 1,
  255. distinct: true,
  256. });
  257. // There should be 2 instances matching the query (Instances 1 and 2), see the findAll statement
  258. expect(result.count).to.equal(2);
  259. // The first one of those should be returned due to the limit (Foo instance 1)
  260. expect(result.rows.length).to.equal(1);
  261. });
  262. it('should correctly filter, limit and sort when multiple includes and types of associations are present.', async function () {
  263. const TaskTag = this.sequelize.define('TaskTag', {
  264. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  265. name: { type: DataTypes.STRING },
  266. });
  267. const Tag = this.sequelize.define('Tag', {
  268. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  269. name: { type: DataTypes.STRING },
  270. });
  271. const Task = this.sequelize.define('Task', {
  272. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  273. name: { type: DataTypes.STRING },
  274. });
  275. const Project = this.sequelize.define('Project', {
  276. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  277. m: { type: DataTypes.STRING },
  278. });
  279. const User = this.sequelize.define('User', {
  280. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  281. name: { type: DataTypes.STRING },
  282. });
  283. Project.belongsTo(User);
  284. Task.belongsTo(Project);
  285. Task.belongsToMany(Tag, { through: TaskTag });
  286. // Sync them
  287. await this.sequelize.sync({ force: true });
  288. // Create an enviroment
  289. await User.bulkCreate([{ name: 'user-name-1' }, { name: 'user-name-2' }]);
  290. await Project.bulkCreate([
  291. { m: 'A', userId: 1 },
  292. { m: 'A', userId: 2 },
  293. ]);
  294. await Task.bulkCreate([
  295. { projectId: 1, name: 'Just' },
  296. { projectId: 1, name: 'for' },
  297. { projectId: 2, name: 'testing' },
  298. { projectId: 2, name: 'proposes' },
  299. ]);
  300. // Find All Tasks with Project(m=a) and User(name=user-name-2)
  301. const result = await Task.findAndCountAll({
  302. limit: 1,
  303. offset: 0,
  304. order: [['id', 'DESC']],
  305. include: [
  306. {
  307. model: Project,
  308. where: { [Op.and]: [{ m: 'A' }] },
  309. include: [
  310. {
  311. model: User,
  312. where: { [Op.and]: [{ name: 'user-name-2' }] },
  313. },
  314. ],
  315. },
  316. { model: Tag },
  317. ],
  318. });
  319. expect(result.count).to.equal(2);
  320. expect(result.rows.length).to.equal(1);
  321. });
  322. it('should properly work with sequelize.function', async function () {
  323. const sequelize = this.sequelize;
  324. const User = this.sequelize.define('User', {
  325. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  326. first_name: { type: DataTypes.STRING },
  327. last_name: { type: DataTypes.STRING },
  328. });
  329. const Project = this.sequelize.define('Project', {
  330. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  331. name: { type: DataTypes.STRING },
  332. });
  333. User.hasMany(Project);
  334. await this.sequelize.sync({ force: true });
  335. await User.bulkCreate([
  336. { first_name: 'user-fname-1', last_name: 'user-lname-1' },
  337. { first_name: 'user-fname-2', last_name: 'user-lname-2' },
  338. { first_name: 'user-xfname-1', last_name: 'user-xlname-1' },
  339. ]);
  340. await Project.bulkCreate([
  341. { name: 'naam-satya', userId: 1 },
  342. { name: 'guru-satya', userId: 2 },
  343. { name: 'app-satya', userId: 2 },
  344. ]);
  345. const result = await User.findAndCountAll({
  346. limit: 1,
  347. offset: 1,
  348. where: sequelize.or(
  349. { first_name: { [Op.like]: '%user-fname%' } },
  350. { last_name: { [Op.like]: '%user-lname%' } },
  351. ),
  352. include: [
  353. {
  354. model: Project,
  355. required: true,
  356. where: {
  357. name: {
  358. [Op.in]: ['naam-satya', 'guru-satya'],
  359. },
  360. },
  361. },
  362. ],
  363. });
  364. expect(result.count).to.equal(2);
  365. expect(result.rows.length).to.equal(1);
  366. });
  367. });
  368. });