Skip to content

Commit c948d9c

Browse files
refactor: move CountDocument logic into collection API (mongodb#4148)
Co-authored-by: Bailey Pearson <[email protected]>
1 parent 8eebe6a commit c948d9c

File tree

6 files changed

+113
-60
lines changed

6 files changed

+113
-60
lines changed

src/collection.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import type {
2525
import type { AggregateOptions } from './operations/aggregate';
2626
import { BulkWriteOperation } from './operations/bulk_write';
2727
import { CountOperation, type CountOptions } from './operations/count';
28-
import { CountDocumentsOperation, type CountDocumentsOptions } from './operations/count_documents';
2928
import {
3029
DeleteManyOperation,
3130
DeleteOneOperation,
@@ -101,6 +100,14 @@ export interface ModifyResult<TSchema = Document> {
101100
ok: 0 | 1;
102101
}
103102

103+
/** @public */
104+
export interface CountDocumentsOptions extends AggregateOptions {
105+
/** The number of documents to skip. */
106+
skip?: number;
107+
/** The maximum amount of documents to consider. */
108+
limit?: number;
109+
}
110+
104111
/** @public */
105112
export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOptions {
106113
/** Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) */
@@ -764,10 +771,23 @@ export class Collection<TSchema extends Document = Document> {
764771
filter: Filter<TSchema> = {},
765772
options: CountDocumentsOptions = {}
766773
): Promise<number> {
767-
return await executeOperation(
768-
this.client,
769-
new CountDocumentsOperation(this as TODO_NODE_3286, filter, resolveOptions(this, options))
770-
);
774+
const pipeline = [];
775+
pipeline.push({ $match: filter });
776+
777+
if (typeof options.skip === 'number') {
778+
pipeline.push({ $skip: options.skip });
779+
}
780+
781+
if (typeof options.limit === 'number') {
782+
pipeline.push({ $limit: options.limit });
783+
}
784+
785+
pipeline.push({ $group: { _id: 1, n: { $sum: 1 } } });
786+
787+
const cursor = this.aggregate<{ n: number }>(pipeline, options);
788+
const doc = await cursor.next();
789+
await cursor.close();
790+
return doc?.n ?? 0;
771791
}
772792

773793
/**

src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,12 @@ export type {
305305
MongoDBResponse,
306306
MongoDBResponseConstructor
307307
} from './cmap/wire_protocol/responses';
308-
export type { CollectionOptions, CollectionPrivate, ModifyResult } from './collection';
308+
export type {
309+
CollectionOptions,
310+
CollectionPrivate,
311+
CountDocumentsOptions,
312+
ModifyResult
313+
} from './collection';
309314
export type {
310315
COMMAND_FAILED,
311316
COMMAND_STARTED,
@@ -463,7 +468,6 @@ export type {
463468
OperationParent
464469
} from './operations/command';
465470
export type { CountOptions } from './operations/count';
466-
export type { CountDocumentsOptions } from './operations/count_documents';
467471
export type {
468472
ClusteredCollectionOptions,
469473
CreateCollectionOptions,

src/operations/count_documents.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

test/integration/crud/abstract_operation.test.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ describe('abstract operation', function () {
5353
subclassType: mongodb.CountOperation,
5454
correctCommandName: 'count'
5555
},
56-
{
57-
subclassCreator: () => new mongodb.CountDocumentsOperation(collection, { a: 1 }, {}),
58-
subclassType: mongodb.CountDocumentsOperation,
59-
correctCommandName: 'aggregate'
60-
},
6156
{
6257
subclassCreator: () => new mongodb.CreateCollectionOperation(db, 'name'),
6358
subclassType: mongodb.CreateCollectionOperation,
@@ -322,11 +317,7 @@ describe('abstract operation', function () {
322317
it(`operation.commandName equals key in command document`, async function () {
323318
const subclassInstance = subclassCreator();
324319
const yieldDoc =
325-
subclassType.name === 'ProfilingLevelOperation'
326-
? { ok: 1, was: 1 }
327-
: subclassType.name === 'CountDocumentsOperation'
328-
? { shift: () => ({ n: 1 }) }
329-
: { ok: 1 };
320+
subclassType.name === 'ProfilingLevelOperation' ? { ok: 1, was: 1 } : { ok: 1 };
330321
const cmdCallerStub = sinon.stub(Server.prototype, 'command').resolves(yieldDoc);
331322
if (sameServerOnlyOperationSubclasses.includes(subclassType.name.toString())) {
332323
await subclassInstance.execute(constructorServer, client.session);

test/integration/crud/crud_api.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,87 @@ describe('CRUD API', function () {
156156
});
157157
});
158158

159+
describe('countDocuments()', () => {
160+
let client: MongoClient;
161+
let events;
162+
let collection: Collection<{ _id: number }>;
163+
164+
beforeEach(async function () {
165+
client = this.configuration.newClient({ monitorCommands: true });
166+
events = [];
167+
client.on('commandSucceeded', commandSucceeded =>
168+
commandSucceeded.commandName === 'aggregate' ? events.push(commandSucceeded) : null
169+
);
170+
client.on('commandFailed', commandFailed =>
171+
commandFailed.commandName === 'aggregate' ? events.push(commandFailed) : null
172+
);
173+
174+
collection = client.db('countDocuments').collection('countDocuments');
175+
await collection.drop().catch(() => null);
176+
await collection.insertMany([{ _id: 1 }, { _id: 2 }]);
177+
});
178+
179+
afterEach(async () => {
180+
await collection.drop().catch(() => null);
181+
await client.close();
182+
});
183+
184+
describe('when the aggregation operation succeeds', () => {
185+
it('the cursor for countDocuments is closed', async function () {
186+
const spy = sinon.spy(Collection.prototype, 'aggregate');
187+
const result = await collection.countDocuments({});
188+
expect(result).to.deep.equal(2);
189+
expect(events[0]).to.be.instanceOf(CommandSucceededEvent);
190+
expect(spy.returnValues[0]).to.have.property('closed', true);
191+
expect(spy.returnValues[0]).to.have.nested.property('session.hasEnded', true);
192+
});
193+
});
194+
195+
describe('when the aggregation operation fails', () => {
196+
beforeEach(async function () {
197+
if (semver.lt(this.configuration.version, '4.2.0')) {
198+
if (this.currentTest) {
199+
this.currentTest.skipReason = `Cannot run fail points on server version: ${this.configuration.version}`;
200+
}
201+
this.skip();
202+
}
203+
204+
const failPoint: FailPoint = {
205+
configureFailPoint: 'failCommand',
206+
mode: 'alwaysOn',
207+
data: {
208+
failCommands: ['aggregate'],
209+
// 1 == InternalError, but this value not important to the test
210+
errorCode: 1
211+
}
212+
};
213+
await client.db().admin().command(failPoint);
214+
});
215+
216+
afterEach(async function () {
217+
if (semver.lt(this.configuration.version, '4.2.0')) {
218+
return;
219+
}
220+
221+
const failPoint: FailPoint = {
222+
configureFailPoint: 'failCommand',
223+
mode: 'off',
224+
data: { failCommands: ['aggregate'] }
225+
};
226+
await client.db().admin().command(failPoint);
227+
});
228+
229+
it('the cursor for countDocuments is closed', async function () {
230+
const spy = sinon.spy(Collection.prototype, 'aggregate');
231+
const error = await collection.countDocuments({}).catch(error => error);
232+
expect(error).to.be.instanceOf(MongoServerError);
233+
expect(events.at(0)).to.be.instanceOf(CommandFailedEvent);
234+
expect(spy.returnValues.at(0)).to.have.property('closed', true);
235+
expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true);
236+
});
237+
});
238+
});
239+
159240
context('when creating a cursor with find', () => {
160241
let collection;
161242

test/mongodb.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ export * from '../src/operations/bulk_write';
157157
export * from '../src/operations/collections';
158158
export * from '../src/operations/command';
159159
export * from '../src/operations/count';
160-
export * from '../src/operations/count_documents';
161160
export * from '../src/operations/create_collection';
162161
export * from '../src/operations/delete';
163162
export * from '../src/operations/distinct';

0 commit comments

Comments
 (0)