Skip to content

Commit

Permalink
feat(NODE-4079): estimated document count uses count (#3244)
Browse files Browse the repository at this point in the history
  • Loading branch information
durran committed May 18, 2022
1 parent a2359e4 commit a752e75
Show file tree
Hide file tree
Showing 20 changed files with 238 additions and 1,945 deletions.
8 changes: 8 additions & 0 deletions src/collection.ts
Expand Up @@ -1043,7 +1043,15 @@ export class Collection<TSchema extends Document = Document> {

/**
* Gets an estimate of the count of documents in a collection using collection metadata.
* This will always run a count command on all server versions.
*
* due to an oversight in versions 5.0.0-5.0.8 of MongoDB, the count command,
* which estimatedDocumentCount uses in its implementation, was not included in v1 of
* the Stable API, and so users of the Stable API with estimatedDocumentCount are
* recommended to upgrade their server version to 5.0.9+ or set apiStrict: false to avoid
* encountering errors.
*
* @see {@link https://www.mongodb.com/docs/manual/reference/command/count/#behavior|Count: Behavior}
* @param options - Optional settings for the command
* @param callback - An optional callback, a Promise will be returned if none is provided
*/
Expand Down
31 changes: 2 additions & 29 deletions src/operations/estimated_document_count.ts
@@ -1,9 +1,8 @@
import type { Document } from '../bson';
import type { Collection } from '../collection';
import type { MongoServerError } from '../error';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { Callback, maxWireVersion } from '../utils';
import type { Callback } from '../utils';
import { CommandOperation, CommandOperationOptions } from './command';
import { Aspect, defineAspects } from './operation';

Expand Down Expand Up @@ -32,32 +31,6 @@ export class EstimatedDocumentCountOperation extends CommandOperation<number> {
server: Server,
session: ClientSession | undefined,
callback: Callback<number>
): void {
if (maxWireVersion(server) < 12) {
return this.executeLegacy(server, session, callback);
}
const pipeline = [{ $collStats: { count: {} } }, { $group: { _id: 1, n: { $sum: '$count' } } }];

const cmd: Document = { aggregate: this.collectionName, pipeline, cursor: {} };

if (typeof this.options.maxTimeMS === 'number') {
cmd.maxTimeMS = this.options.maxTimeMS;
}

super.executeCommand(server, session, cmd, (err, response) => {
if (err && (err as MongoServerError).code !== 26) {
callback(err);
return;
}

callback(undefined, response?.cursor?.firstBatch[0]?.n || 0);
});
}

executeLegacy(
server: Server,
session: ClientSession | undefined,
callback: Callback<number>
): void {
const cmd: Document = { count: this.collectionName };

Expand All @@ -71,7 +44,7 @@ export class EstimatedDocumentCountOperation extends CommandOperation<number> {
return;
}

callback(undefined, response.n || 0);
callback(undefined, response?.n || 0);
});
}
}
Expand Down
112 changes: 56 additions & 56 deletions test/integration/collection-management/collection.test.js
Expand Up @@ -535,81 +535,81 @@ describe('Collection', function () {
});
});

describe('(countDocuments)', function () {
describe('#estimatedDocumentCount', function () {
let client;
let db;
let collection;
beforeEach(function () {

beforeEach(async function () {
client = configuration.newClient({ w: 1 });

return client.connect().then(client => {
db = client.db(configuration.db);
collection = db.collection('test_coll');
});
});
afterEach(function () {
return client.close();
await client.connect();
db = client.db(configuration.db);
collection = db.collection('test_coll');
await collection.insertOne({ a: 'c' });
});

const nonMatchQueryTests = [
{
title: 'should correctly perform estimatedDocumentCount on non-matching query'
},
{
title: 'should correctly perform countDocuments on non-matching query'
}
];
afterEach(async function () {
await collection.drop();
await client.close();
});

nonMatchQueryTests.forEach(test => {
it(test.title, function (done) {
const close = e => client.close(() => done(e));
let thenFunction;
if (
test.title === 'should correctly perform estimatedDocumentCount on non-matching query'
) {
thenFunction = () => collection.estimatedDocumentCount({ a: 'b' });
} else if (test.title === 'should correctly perform countDocuments on non-matching query') {
thenFunction = () => collection.countDocuments({ a: 'b' });
}
Promise.resolve()
.then(thenFunction)
.then(count => expect(count).to.equal(0))
.then(() => close())
.catch(e => close(e));
});
it('returns the total documents in the collection', async function () {
const result = await collection.estimatedDocumentCount();
expect(result).to.equal(1);
});
});

it('countDocuments should return Promise that resolves when no callback passed', function (done) {
const docsPromise = collection.countDocuments();
const close = e => client.close(() => done(e));
describe('#countDocuments', function () {
let client;
let db;
let collection;

expect(docsPromise).to.exist.and.to.be.an.instanceof(Promise);
beforeEach(async function () {
client = configuration.newClient({ w: 1 });
await client.connect();
db = client.db(configuration.db);
collection = db.collection('test_coll');
await collection.insertOne({ a: 'c' });
});

docsPromise
.then(result => expect(result).to.equal(0))
.then(() => close())
.catch(e => close(e));
afterEach(async function () {
await collection.drop();
await client.close();
});

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

const notPromise = collection.countDocuments({ a: 1 }, () => {
expect(notPromise).to.be.undefined;
close();
context('when no callback passed', function () {
it('returns a promise', function () {
const docsPromise = collection.countDocuments();
expect(docsPromise).to.exist.and.to.be.an.instanceof(Promise);
return docsPromise.then(result => expect(result).to.equal(1));
});
});

it('countDocuments should correctly call the given callback', function (done) {
const docs = [{ a: 1 }, { a: 2 }];
const close = e => client.close(() => done(e));
context('when a callback is passed', function () {
it('does not return a promise', function (done) {
const notPromise = collection.countDocuments({ a: 1 }, () => {
expect(notPromise).to.be.undefined;
done();
});
});

collection.insertMany(docs).then(() =>
collection.countDocuments({ a: 1 }, (err, data) => {
expect(data).to.equal(1);
close(err);
})
);
it('calls the callback', function (done) {
const docs = [{ a: 1 }, { a: 2 }];
collection.insertMany(docs).then(() =>
collection.countDocuments({ a: 1 }, (err, data) => {
expect(data).to.equal(1);
done(err);
})
);
});
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/integration/read-write-concern/readconcern.test.js
Expand Up @@ -234,7 +234,7 @@ describe('ReadConcern', function () {
done();
});
} else if (test.commandName === 'count') {
collection.estimatedDocumentCount({ a: 1 }, err => {
collection.estimatedDocumentCount(err => {
expect(err).to.not.exist;
validateTestResults(started, succeeded, test.commandName, test.readConcern.level);
done();
Expand Down
19 changes: 2 additions & 17 deletions test/spec/atlas-data-lake-testing/estimatedDocumentCount.json
Expand Up @@ -15,24 +15,9 @@
{
"command_started_event": {
"command": {
"aggregate": "driverdata",
"pipeline": [
{
"$collStats": {
"count": {}
}
},
{
"$group": {
"_id": 1,
"n": {
"$sum": "$count"
}
}
}
]
"count": "driverdata"
},
"command_name": "aggregate",
"command_name": "count",
"database_name": "test"
}
}
Expand Down
9 changes: 3 additions & 6 deletions test/spec/atlas-data-lake-testing/estimatedDocumentCount.yml
Expand Up @@ -13,9 +13,6 @@ tests:
-
command_started_event:
command:
aggregate: *collection_name
pipeline:
- $collStats: { count: {} }
- $group: { _id: 1, n: { $sum: $count }}
command_name: aggregate
database_name: *database_name
count: *collection_name
command_name: count
database_name: *database_name

0 comments on commit a752e75

Please sign in to comment.