Skip to content

Commit a752e75

Browse files
authored
feat(NODE-4079): estimated document count uses count (#3244)
1 parent a2359e4 commit a752e75

20 files changed

+238
-1945
lines changed

src/collection.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,15 @@ export class Collection<TSchema extends Document = Document> {
10431043

10441044
/**
10451045
* Gets an estimate of the count of documents in a collection using collection metadata.
1046+
* This will always run a count command on all server versions.
10461047
*
1048+
* due to an oversight in versions 5.0.0-5.0.8 of MongoDB, the count command,
1049+
* which estimatedDocumentCount uses in its implementation, was not included in v1 of
1050+
* the Stable API, and so users of the Stable API with estimatedDocumentCount are
1051+
* recommended to upgrade their server version to 5.0.9+ or set apiStrict: false to avoid
1052+
* encountering errors.
1053+
*
1054+
* @see {@link https://www.mongodb.com/docs/manual/reference/command/count/#behavior|Count: Behavior}
10471055
* @param options - Optional settings for the command
10481056
* @param callback - An optional callback, a Promise will be returned if none is provided
10491057
*/

src/operations/estimated_document_count.ts

+2-29
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { Document } from '../bson';
22
import type { Collection } from '../collection';
3-
import type { MongoServerError } from '../error';
43
import type { Server } from '../sdam/server';
54
import type { ClientSession } from '../sessions';
6-
import { Callback, maxWireVersion } from '../utils';
5+
import type { Callback } from '../utils';
76
import { CommandOperation, CommandOperationOptions } from './command';
87
import { Aspect, defineAspects } from './operation';
98

@@ -32,32 +31,6 @@ export class EstimatedDocumentCountOperation extends CommandOperation<number> {
3231
server: Server,
3332
session: ClientSession | undefined,
3433
callback: Callback<number>
35-
): void {
36-
if (maxWireVersion(server) < 12) {
37-
return this.executeLegacy(server, session, callback);
38-
}
39-
const pipeline = [{ $collStats: { count: {} } }, { $group: { _id: 1, n: { $sum: '$count' } } }];
40-
41-
const cmd: Document = { aggregate: this.collectionName, pipeline, cursor: {} };
42-
43-
if (typeof this.options.maxTimeMS === 'number') {
44-
cmd.maxTimeMS = this.options.maxTimeMS;
45-
}
46-
47-
super.executeCommand(server, session, cmd, (err, response) => {
48-
if (err && (err as MongoServerError).code !== 26) {
49-
callback(err);
50-
return;
51-
}
52-
53-
callback(undefined, response?.cursor?.firstBatch[0]?.n || 0);
54-
});
55-
}
56-
57-
executeLegacy(
58-
server: Server,
59-
session: ClientSession | undefined,
60-
callback: Callback<number>
6134
): void {
6235
const cmd: Document = { count: this.collectionName };
6336

@@ -71,7 +44,7 @@ export class EstimatedDocumentCountOperation extends CommandOperation<number> {
7144
return;
7245
}
7346

74-
callback(undefined, response.n || 0);
47+
callback(undefined, response?.n || 0);
7548
});
7649
}
7750
}

test/integration/collection-management/collection.test.js

+56-56
Original file line numberDiff line numberDiff line change
@@ -535,81 +535,81 @@ describe('Collection', function () {
535535
});
536536
});
537537

538-
describe('(countDocuments)', function () {
538+
describe('#estimatedDocumentCount', function () {
539539
let client;
540540
let db;
541541
let collection;
542-
beforeEach(function () {
542+
543+
beforeEach(async function () {
543544
client = configuration.newClient({ w: 1 });
544545

545-
return client.connect().then(client => {
546-
db = client.db(configuration.db);
547-
collection = db.collection('test_coll');
548-
});
549-
});
550-
afterEach(function () {
551-
return client.close();
546+
await client.connect();
547+
db = client.db(configuration.db);
548+
collection = db.collection('test_coll');
549+
await collection.insertOne({ a: 'c' });
552550
});
553551

554-
const nonMatchQueryTests = [
555-
{
556-
title: 'should correctly perform estimatedDocumentCount on non-matching query'
557-
},
558-
{
559-
title: 'should correctly perform countDocuments on non-matching query'
560-
}
561-
];
552+
afterEach(async function () {
553+
await collection.drop();
554+
await client.close();
555+
});
562556

563-
nonMatchQueryTests.forEach(test => {
564-
it(test.title, function (done) {
565-
const close = e => client.close(() => done(e));
566-
let thenFunction;
567-
if (
568-
test.title === 'should correctly perform estimatedDocumentCount on non-matching query'
569-
) {
570-
thenFunction = () => collection.estimatedDocumentCount({ a: 'b' });
571-
} else if (test.title === 'should correctly perform countDocuments on non-matching query') {
572-
thenFunction = () => collection.countDocuments({ a: 'b' });
573-
}
574-
Promise.resolve()
575-
.then(thenFunction)
576-
.then(count => expect(count).to.equal(0))
577-
.then(() => close())
578-
.catch(e => close(e));
579-
});
557+
it('returns the total documents in the collection', async function () {
558+
const result = await collection.estimatedDocumentCount();
559+
expect(result).to.equal(1);
580560
});
561+
});
581562

582-
it('countDocuments should return Promise that resolves when no callback passed', function (done) {
583-
const docsPromise = collection.countDocuments();
584-
const close = e => client.close(() => done(e));
563+
describe('#countDocuments', function () {
564+
let client;
565+
let db;
566+
let collection;
585567

586-
expect(docsPromise).to.exist.and.to.be.an.instanceof(Promise);
568+
beforeEach(async function () {
569+
client = configuration.newClient({ w: 1 });
570+
await client.connect();
571+
db = client.db(configuration.db);
572+
collection = db.collection('test_coll');
573+
await collection.insertOne({ a: 'c' });
574+
});
587575

588-
docsPromise
589-
.then(result => expect(result).to.equal(0))
590-
.then(() => close())
591-
.catch(e => close(e));
576+
afterEach(async function () {
577+
await collection.drop();
578+
await client.close();
592579
});
593580

594-
it('countDocuments should not return a promise if callback given', function (done) {
595-
const close = e => client.close(() => done(e));
581+
context('when passing a non-matching query', function () {
582+
it('returns 0', async function () {
583+
const result = await collection.countDocuments({ a: 'b' });
584+
expect(result).to.equal(0);
585+
});
586+
});
596587

597-
const notPromise = collection.countDocuments({ a: 1 }, () => {
598-
expect(notPromise).to.be.undefined;
599-
close();
588+
context('when no callback passed', function () {
589+
it('returns a promise', function () {
590+
const docsPromise = collection.countDocuments();
591+
expect(docsPromise).to.exist.and.to.be.an.instanceof(Promise);
592+
return docsPromise.then(result => expect(result).to.equal(1));
600593
});
601594
});
602595

603-
it('countDocuments should correctly call the given callback', function (done) {
604-
const docs = [{ a: 1 }, { a: 2 }];
605-
const close = e => client.close(() => done(e));
596+
context('when a callback is passed', function () {
597+
it('does not return a promise', function (done) {
598+
const notPromise = collection.countDocuments({ a: 1 }, () => {
599+
expect(notPromise).to.be.undefined;
600+
done();
601+
});
602+
});
606603

607-
collection.insertMany(docs).then(() =>
608-
collection.countDocuments({ a: 1 }, (err, data) => {
609-
expect(data).to.equal(1);
610-
close(err);
611-
})
612-
);
604+
it('calls the callback', function (done) {
605+
const docs = [{ a: 1 }, { a: 2 }];
606+
collection.insertMany(docs).then(() =>
607+
collection.countDocuments({ a: 1 }, (err, data) => {
608+
expect(data).to.equal(1);
609+
done(err);
610+
})
611+
);
612+
});
613613
});
614614
});
615615

test/integration/read-write-concern/readconcern.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ describe('ReadConcern', function () {
234234
done();
235235
});
236236
} else if (test.commandName === 'count') {
237-
collection.estimatedDocumentCount({ a: 1 }, err => {
237+
collection.estimatedDocumentCount(err => {
238238
expect(err).to.not.exist;
239239
validateTestResults(started, succeeded, test.commandName, test.readConcern.level);
240240
done();

test/spec/atlas-data-lake-testing/estimatedDocumentCount.json

+2-17
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,9 @@
1515
{
1616
"command_started_event": {
1717
"command": {
18-
"aggregate": "driverdata",
19-
"pipeline": [
20-
{
21-
"$collStats": {
22-
"count": {}
23-
}
24-
},
25-
{
26-
"$group": {
27-
"_id": 1,
28-
"n": {
29-
"$sum": "$count"
30-
}
31-
}
32-
}
33-
]
18+
"count": "driverdata"
3419
},
35-
"command_name": "aggregate",
20+
"command_name": "count",
3621
"database_name": "test"
3722
}
3823
}

test/spec/atlas-data-lake-testing/estimatedDocumentCount.yml

+3-6
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ tests:
1313
-
1414
command_started_event:
1515
command:
16-
aggregate: *collection_name
17-
pipeline:
18-
- $collStats: { count: {} }
19-
- $group: { _id: 1, n: { $sum: $count }}
20-
command_name: aggregate
21-
database_name: *database_name
16+
count: *collection_name
17+
command_name: count
18+
database_name: *database_name

0 commit comments

Comments
 (0)