Skip to content

Commit a1fffeb

Browse files
fix(NODE-6878): documents.clear() throws a TypeError after cursor is rewound (#4488)
Co-authored-by: Bailey Pearson <[email protected]>
1 parent 8c86e30 commit a1fffeb

File tree

2 files changed

+33
-6
lines changed

2 files changed

+33
-6
lines changed

src/cmap/wire_protocol/responses.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
parseToElementsToArray,
99
parseUtf8ValidationOption,
1010
pluckBSONSerializeOptions,
11+
serialize,
1112
type Timestamp
1213
} from '../../bson';
1314
import { MONGODB_ERROR_CODES, MongoUnexpectedServerResponseError } from '../../error';
@@ -230,11 +231,9 @@ export class CursorResponse extends MongoDBResponse {
230231
* This supports a feature of the FindCursor.
231232
* It is an optimization to avoid an extra getMore when the limit has been reached
232233
*/
233-
static emptyGetMore: CursorResponse = {
234-
id: new Long(0),
235-
length: 0,
236-
shift: () => null
237-
} as unknown as CursorResponse;
234+
static get emptyGetMore(): CursorResponse {
235+
return new CursorResponse(serialize({ ok: 1, cursor: { id: 0n, nextBatch: [] } }));
236+
}
238237

239238
static override is(value: unknown): value is CursorResponse {
240239
return value instanceof CursorResponse || value === CursorResponse.emptyGetMore;

test/integration/crud/find.test.js

+29-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { assert: test } = require('../shared');
33
const { expect } = require('chai');
44
const sinon = require('sinon');
55
const { setTimeout } = require('timers');
6-
const { Code, ObjectId, Long, Binary, ReturnDocument } = require('../../mongodb');
6+
const { Code, ObjectId, Long, Binary, ReturnDocument, CursorResponse } = require('../../mongodb');
77

88
describe('Find', function () {
99
let client;
@@ -2388,4 +2388,32 @@ describe('Find', function () {
23882388
});
23892389
});
23902390
});
2391+
2392+
it(
2393+
'regression test (NODE-6878): CursorResponse.emptyGetMore contains all CursorResponse fields',
2394+
{ requires: { topology: 'sharded' } },
2395+
async function () {
2396+
const collection = client.db('rewind-regression').collection('bar');
2397+
2398+
await collection.deleteMany({});
2399+
await collection.insertMany(Array.from({ length: 4 }, (_, i) => ({ x: i })));
2400+
2401+
const getMoreSpy = sinon.spy(CursorResponse, 'emptyGetMore', ['get']);
2402+
2403+
const cursor = collection.find({}, { batchSize: 1, limit: 3 });
2404+
// emptyGetMore is used internally after limit + 1 documents have been iterated
2405+
await cursor.next();
2406+
await cursor.next();
2407+
await cursor.next();
2408+
await cursor.next();
2409+
2410+
// assert that `emptyGetMore` is called. if it is not, this test
2411+
// always passes, even without the fix in NODE-6878.
2412+
expect(getMoreSpy.get).to.have.been.called;
2413+
2414+
cursor.rewind();
2415+
2416+
await cursor.toArray();
2417+
}
2418+
);
23912419
});

0 commit comments

Comments
 (0)