count.test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const { DataTypes, Op } = require('@sequelize/core');
  5. const Support = require('../support');
  6. const sinon = require('sinon');
  7. const dialectName = Support.sequelize.dialect.name;
  8. describe('Model.count', () => {
  9. context('test-shared models', () => {
  10. beforeEach(async function () {
  11. this.User = this.sequelize.define('User', {
  12. username: DataTypes.STRING,
  13. age: DataTypes.INTEGER,
  14. });
  15. this.Project = this.sequelize.define('Project', {
  16. name: DataTypes.STRING,
  17. });
  18. this.User.hasMany(this.Project);
  19. this.Project.belongsTo(this.User);
  20. await this.sequelize.sync({ force: true });
  21. });
  22. it('counts all created objects', async function () {
  23. await this.User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]);
  24. expect(await this.User.count()).to.equal(2);
  25. });
  26. it('returns multiple rows when using group', async function () {
  27. await this.User.bulkCreate([
  28. { username: 'user1' },
  29. { username: 'user1' },
  30. { username: 'user2' },
  31. ]);
  32. const count = await this.User.count({
  33. attributes: ['username'],
  34. group: ['username'],
  35. });
  36. expect(count).to.have.lengthOf(2);
  37. // The order of count varies across dialects; Hence find element by identified first.
  38. expect(count.find(i => i.username === 'user1')).to.deep.equal({
  39. username: 'user1',
  40. count: 2,
  41. });
  42. expect(count.find(i => i.username === 'user2')).to.deep.equal({
  43. username: 'user2',
  44. count: 1,
  45. });
  46. });
  47. if (dialectName !== 'mssql' && dialectName !== 'db2' && dialectName !== 'ibmi') {
  48. describe('aggregate', () => {
  49. it('allows grouping by aliased attribute', async function () {
  50. await this.User.aggregate('id', 'count', {
  51. attributes: [['id', 'id2']],
  52. group: ['id2'],
  53. });
  54. });
  55. });
  56. }
  57. describe('options sent to aggregate', () => {
  58. let options;
  59. let aggregateSpy;
  60. beforeEach(function () {
  61. options = { where: { username: 'user1' } };
  62. aggregateSpy = sinon.spy(this.User, 'aggregate');
  63. });
  64. afterEach(() => {
  65. expect(aggregateSpy).to.have.been.calledWith(
  66. sinon.match.any,
  67. sinon.match.any,
  68. sinon.match.object.and(sinon.match.has('where', { username: 'user1' })),
  69. );
  70. aggregateSpy.restore();
  71. });
  72. it('modifies option "limit" by setting it to null', async function () {
  73. options.limit = 5;
  74. await this.User.count(options);
  75. expect(aggregateSpy).to.have.been.calledWith(
  76. sinon.match.any,
  77. sinon.match.any,
  78. sinon.match.object.and(sinon.match.has('limit', null)),
  79. );
  80. });
  81. it('modifies option "offset" by setting it to null', async function () {
  82. options.offset = 10;
  83. await this.User.count(options);
  84. expect(aggregateSpy).to.have.been.calledWith(
  85. sinon.match.any,
  86. sinon.match.any,
  87. sinon.match.object.and(sinon.match.has('offset', null)),
  88. );
  89. });
  90. it('modifies option "order" by setting it to null', async function () {
  91. options.order = 'username';
  92. await this.User.count(options);
  93. expect(aggregateSpy).to.have.been.calledWith(
  94. sinon.match.any,
  95. sinon.match.any,
  96. sinon.match.object.and(sinon.match.has('order', null)),
  97. );
  98. });
  99. });
  100. it('allows sql logging', async function () {
  101. let test = false;
  102. await this.User.count({
  103. logging(sql) {
  104. test = true;
  105. expect(sql).to.exist;
  106. expect(sql.toUpperCase()).to.include('SELECT');
  107. },
  108. });
  109. expect(test).to.be.true;
  110. });
  111. it('filters object', async function () {
  112. await this.User.create({ username: 'user1' });
  113. await this.User.create({ username: 'foo' });
  114. const count = await this.User.count({ where: { username: { [Op.like]: '%us%' } } });
  115. expect(count).to.equal(1);
  116. });
  117. it('supports distinct option', async function () {
  118. const Post = this.sequelize.define('Post', {});
  119. const PostComment = this.sequelize.define('PostComment', {});
  120. Post.hasMany(PostComment);
  121. await Post.sync({ force: true });
  122. await PostComment.sync({ force: true });
  123. const post = await Post.create({});
  124. await PostComment.bulkCreate([{ postId: post.id }, { postId: post.id }]);
  125. const count1 = await Post.count({
  126. distinct: false,
  127. include: { model: PostComment, required: false },
  128. });
  129. const count2 = await Post.count({
  130. distinct: true,
  131. include: { model: PostComment, required: false },
  132. });
  133. expect(count1).to.equal(2);
  134. expect(count2).to.equal(1);
  135. });
  136. it('should count rows', async function () {
  137. await this.User.bulkCreate([{ username: 'foo' }, { username: 'bar' }]);
  138. await expect(this.User.count()).to.eventually.equal(2);
  139. });
  140. it('should support include', async function () {
  141. await this.User.bulkCreate([{ username: 'foo' }, { username: 'bar' }]);
  142. const user = await this.User.findOne();
  143. await user.createProject({ name: 'project1' });
  144. await expect(
  145. this.User.count({
  146. include: [
  147. {
  148. model: this.Project,
  149. where: { name: 'project1' },
  150. },
  151. ],
  152. }),
  153. ).to.eventually.equal(1);
  154. });
  155. it('should count groups correctly and return attributes', async function () {
  156. await this.User.bulkCreate([
  157. { username: 'foo' },
  158. { username: 'bar' },
  159. {
  160. username: 'valak',
  161. createdAt: new Date().setFullYear(2015),
  162. },
  163. ]);
  164. const users = await this.User.count({
  165. attributes: ['createdAt'],
  166. group: ['createdAt'],
  167. });
  168. expect(users.length).to.be.eql(2);
  169. expect(users[0].createdAt).to.exist;
  170. expect(users[1].createdAt).to.exist;
  171. });
  172. it('should not return NaN', async function () {
  173. await this.User.bulkCreate([
  174. { username: 'valak', age: 10 },
  175. { username: 'conjuring', age: 20 },
  176. { username: 'scary', age: 10 },
  177. ]);
  178. const result = await this.User.count({
  179. where: { age: 10 },
  180. group: ['age'],
  181. order: ['age'],
  182. });
  183. // TODO: `parseInt` should not be needed, see #10533
  184. expect(Number.parseInt(result[0].count, 10)).to.be.eql(2);
  185. const count0 = await this.User.count({
  186. where: { username: 'fire' },
  187. });
  188. expect(count0).to.be.eql(0);
  189. const count = await this.User.count({
  190. where: { username: 'fire' },
  191. group: 'age',
  192. });
  193. expect(count).to.be.eql([]);
  194. });
  195. it('should be able to specify column for COUNT()', async function () {
  196. await this.User.bulkCreate([
  197. { username: 'ember', age: 10 },
  198. { username: 'angular', age: 20 },
  199. { username: 'mithril', age: 10 },
  200. ]);
  201. const count0 = await this.User.count({ col: 'username' });
  202. expect(count0).to.be.eql(3);
  203. const count = await this.User.count({
  204. col: 'age',
  205. distinct: true,
  206. });
  207. expect(count).to.be.eql(2);
  208. });
  209. it('should be able to specify NO column for COUNT() with DISTINCT', async function () {
  210. await this.User.bulkCreate([
  211. { username: 'ember', age: 10 },
  212. { username: 'angular', age: 20 },
  213. { username: 'mithril', age: 10 },
  214. ]);
  215. const count = await this.User.count({
  216. distinct: true,
  217. });
  218. expect(count).to.be.eql(3);
  219. });
  220. it('should be able to use where clause on included models', async function () {
  221. const countOptions = {
  222. col: 'username',
  223. include: [this.Project],
  224. where: {
  225. '$projects.name$': 'project1',
  226. },
  227. };
  228. await this.User.bulkCreate([{ username: 'foo' }, { username: 'bar' }]);
  229. const user = await this.User.findOne();
  230. await user.createProject({ name: 'project1' });
  231. const count0 = await this.User.count(countOptions);
  232. expect(count0).to.be.eql(1);
  233. countOptions.where['$projects.name$'] = 'project2';
  234. const count = await this.User.count(countOptions);
  235. expect(count).to.be.eql(0);
  236. });
  237. it('should be able to specify column for COUNT() with includes', async function () {
  238. await this.User.bulkCreate([
  239. { username: 'ember', age: 10 },
  240. { username: 'angular', age: 20 },
  241. { username: 'mithril', age: 10 },
  242. ]);
  243. const count0 = await this.User.count({
  244. col: 'username',
  245. distinct: true,
  246. include: [this.Project],
  247. });
  248. expect(count0).to.be.eql(3);
  249. const count = await this.User.count({
  250. col: 'age',
  251. distinct: true,
  252. include: [this.Project],
  253. });
  254. expect(count).to.be.eql(2);
  255. });
  256. it('should work correctly with include and whichever raw option', async function () {
  257. const Post = this.sequelize.define('Post', {});
  258. this.User.hasMany(Post);
  259. await Post.sync({ force: true });
  260. const [user, post] = await Promise.all([this.User.create({}), Post.create({})]);
  261. await user.addPost(post);
  262. const counts = await Promise.all([
  263. this.User.count(),
  264. this.User.count({ raw: undefined }),
  265. this.User.count({ raw: false }),
  266. this.User.count({ raw: true }),
  267. this.User.count({ include: Post }),
  268. this.User.count({ include: Post, raw: undefined }),
  269. this.User.count({ include: Post, raw: false }),
  270. this.User.count({ include: Post, raw: true }),
  271. ]);
  272. expect(counts).to.deep.equal([1, 1, 1, 1, 1, 1, 1, 1]);
  273. });
  274. it('can count grouped rows', async function () {
  275. await this.User.bulkCreate([
  276. { username: 'user1', age: 10 },
  277. { username: 'user2', age: 20 },
  278. { username: 'user3', age: 30 },
  279. { username: 'user4', age: 10 },
  280. { username: 'user5', age: 20 },
  281. { username: 'user6', age: 30 },
  282. ]);
  283. const count = await this.User.count({
  284. attributes: ['age'],
  285. group: ['age'],
  286. countGroupedRows: true,
  287. });
  288. expect(count).to.be.eql(3);
  289. });
  290. });
  291. context('test-specific models', () => {
  292. if (Support.sequelize.dialect.supports.transactions) {
  293. it('supports transactions', async function () {
  294. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  295. this.sequelize,
  296. );
  297. const User = sequelize.define('User', { username: DataTypes.STRING });
  298. await User.sync({ force: true });
  299. const t = await sequelize.startUnmanagedTransaction();
  300. await User.create({ username: 'foo' }, { transaction: t });
  301. const count1 = await User.count();
  302. const count2 = await User.count({ transaction: t });
  303. expect(count1).to.equal(0);
  304. expect(count2).to.equal(1);
  305. await t.rollback();
  306. });
  307. }
  308. });
  309. });