Skip to content

Commit f8647fa

Browse files
committed
wip
1 parent 0754bf9 commit f8647fa

File tree

6 files changed

+361
-144
lines changed

6 files changed

+361
-144
lines changed

src/cursor/abstract_cursor.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,22 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
7979
readPreference?: ReadPreferenceLike;
8080
readConcern?: ReadConcernLike;
8181
batchSize?: number;
82+
/**
83+
* For **`tailable=false` cursor** OR **`tailable=true && awaitData=false` cursor**,
84+
* - the driver MUST set `maxTimeMS` on the `find` command and MUST NOT set `maxTimeMS` on the `getMore` command.
85+
* - If `maxTimeMS` is not set in options, the driver SHOULD refrain from setting **maxTimeMS**
86+
*
87+
* For **`tailable=true && awaitData=true` cursor**
88+
* - the driver MUST provide a cursor level option named `maxAwaitTimeMS`.
89+
* - The `maxTimeMS` option on the `getMore` command MUST be set to the value of the option `maxAwaitTimeMS`.
90+
* - If no `maxAwaitTimeMS` is specified, the driver MUST not set `maxTimeMS` on the `getMore` command.
91+
* - `maxAwaitTimeMS` option is not set on the `aggregate` command nor `$changeStream` pipeline stage
92+
*
93+
* ## `maxCommitTimeMS`
94+
* Note, this option is an alias for the `maxTimeMS` commitTransaction command option.
95+
*/
8296
maxTimeMS?: number;
97+
maxAwaitTimeMS?: number;
8398
/**
8499
* Comment to apply to the operation.
85100
*
@@ -155,7 +170,7 @@ export abstract class AbstractCursor<
155170
}
156171
this[kClient] = client;
157172
this[kNamespace] = namespace;
158-
this[kDocuments] = []; // TODO: https://github.com/microsoft/TypeScript/issues/36230
173+
this[kDocuments] = [];
159174
this[kInitialized] = false;
160175
this[kClosed] = false;
161176
this[kKilled] = false;
@@ -186,6 +201,10 @@ export abstract class AbstractCursor<
186201
this[kOptions].maxTimeMS = options.maxTimeMS;
187202
}
188203

204+
if (typeof options.maxAwaitTimeMS === 'number') {
205+
this[kOptions].maxAwaitTimeMS = options.maxAwaitTimeMS;
206+
}
207+
189208
if (options.session instanceof ClientSession) {
190209
this[kSession] = options.session;
191210
} else {
@@ -617,21 +636,8 @@ export abstract class AbstractCursor<
617636

618637
/** @internal */
619638
_getMore(batchSize: number, callback: Callback<Document>): void {
620-
const cursorId = this[kId];
621-
const cursorNs = this[kNamespace];
622-
const server = this[kServer];
623-
624-
if (cursorId == null) {
625-
callback(new MongoRuntimeError('Unable to iterate cursor with no id'));
626-
return;
627-
}
628-
629-
if (server == null) {
630-
callback(new MongoRuntimeError('Unable to iterate cursor without selected server'));
631-
return;
632-
}
633-
634-
const getMoreOperation = new GetMoreOperation(cursorNs, cursorId, server, {
639+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
640+
const getMoreOperation = new GetMoreOperation(this[kNamespace], this[kId]!, this[kServer]!, {
635641
...this[kOptions],
636642
session: this[kSession],
637643
batchSize

src/operations/command.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface CommandOperationOptions
4444
readConcern?: ReadConcernLike;
4545
/** Collation */
4646
collation?: CollationOptions;
47+
/** TODO This is probably in the wrong place................. specs only mention this being a thing for createIndex/dropIndex */
4748
maxTimeMS?: number;
4849
/**
4950
* Comment to apply to the operation.

src/operations/get_more.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class GetMoreOperation extends AbstractOperation {
3939
cursorId: Long;
4040
override options: GetMoreOptions;
4141

42-
constructor(ns: MongoDBNamespace, cursorId: Long, server: Server, options: GetMoreOptions = {}) {
42+
constructor(ns: MongoDBNamespace, cursorId: Long, server: Server, options: GetMoreOptions) {
4343
super(options);
4444

4545
this.options = options;
@@ -63,6 +63,10 @@ export class GetMoreOperation extends AbstractOperation {
6363
);
6464
}
6565

66+
if (this.cursorId == null || this.cursorId.isZero()) {
67+
return callback(new MongoRuntimeError('Unable to iterate cursor with no id'));
68+
}
69+
6670
const collection = this.ns.collection;
6771
if (collection == null) {
6872
// Cursors should have adopted the namespace returned by MongoDB

test/integration/change-streams/change_stream.test.ts

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ describe('Change Streams', function () {
11111111
changeStream.next((err, doc) => {
11121112
expect(err).to.exist;
11131113
expect(doc).to.not.exist;
1114-
expect(err.message).to.equal('ChangeStream is closed');
1114+
expect(err?.message).to.equal('ChangeStream is closed');
11151115
changeStream.close(() => client.close(done));
11161116
});
11171117
});
@@ -1372,23 +1372,97 @@ describe('Change Streams', function () {
13721372
)
13731373
.run();
13741374

1375+
UnifiedTestSuiteBuilder.describe('entity.watch() server-side options')
1376+
.runOnRequirement({
1377+
topologies: ['replicaset', 'sharded-replicaset', 'sharded', 'load-balanced'],
1378+
minServerVersion: '4.4.0'
1379+
})
1380+
.createEntities([
1381+
{ client: { id: 'client0', observeEvents: ['commandStartedEvent'] } },
1382+
{ database: { id: 'db0', client: 'client0', databaseName: 'watchOpts' } },
1383+
{ collection: { id: 'collection0', database: 'db0', collectionName: 'watchOpts' } }
1384+
])
1385+
.test(
1386+
TestBuilder.it('should use maxAwaitTimeMS to send maxTimeMS on getMore commands')
1387+
.operation({
1388+
object: 'collection0',
1389+
name: 'createChangeStream',
1390+
saveResultAsEntity: 'changeStreamOnClient',
1391+
arguments: { maxAwaitTimeMS: 5000 }
1392+
})
1393+
.operation({
1394+
name: 'insertOne',
1395+
object: 'collection0',
1396+
arguments: { document: { a: 1 } },
1397+
ignoreResultAndError: true
1398+
})
1399+
.operation({
1400+
object: 'changeStreamOnClient',
1401+
name: 'iterateUntilDocumentOrError',
1402+
ignoreResultAndError: true
1403+
})
1404+
.expectEvents({
1405+
client: 'client0',
1406+
events: [
1407+
{ commandStartedEvent: { commandName: 'aggregate' } },
1408+
{ commandStartedEvent: { commandName: 'insert' } },
1409+
{ commandStartedEvent: { commandName: 'getMore', command: { maxTimeMS: 5000 } } }
1410+
]
1411+
})
1412+
.toJSON()
1413+
)
1414+
.test(
1415+
TestBuilder.it('should send maxTimeMS on aggregate command')
1416+
.operation({
1417+
object: 'collection0',
1418+
name: 'createChangeStream',
1419+
saveResultAsEntity: 'changeStreamOnClient',
1420+
arguments: { maxTimeMS: 5000 }
1421+
})
1422+
.operation({
1423+
name: 'insertOne',
1424+
object: 'collection0',
1425+
arguments: { document: { a: 1 } },
1426+
ignoreResultAndError: true
1427+
})
1428+
.operation({
1429+
object: 'changeStreamOnClient',
1430+
name: 'iterateUntilDocumentOrError',
1431+
ignoreResultAndError: true
1432+
})
1433+
.expectEvents({
1434+
client: 'client0',
1435+
events: [
1436+
{ commandStartedEvent: { commandName: 'aggregate', command: { maxTimeMS: 5000 } } },
1437+
{ commandStartedEvent: { commandName: 'insert' } },
1438+
{
1439+
commandStartedEvent: {
1440+
commandName: 'getMore',
1441+
command: { maxTimeMS: { $$exists: false } }
1442+
}
1443+
}
1444+
]
1445+
})
1446+
.toJSON()
1447+
)
1448+
.run();
1449+
13751450
describe('BSON Options', function () {
13761451
let client: MongoClient;
13771452
let db: Db;
13781453
let collection: Collection;
13791454
let cs: ChangeStream;
1455+
13801456
beforeEach(async function () {
13811457
client = await this.configuration.newClient({ monitorCommands: true }).connect();
13821458
db = client.db('db');
13831459
collection = await db.createCollection('collection');
13841460
});
1461+
13851462
afterEach(async function () {
13861463
await db.dropCollection('collection');
13871464
await cs.close();
13881465
await client.close();
1389-
client = undefined;
1390-
db = undefined;
1391-
collection = undefined;
13921466
});
13931467

13941468
context('promoteLongs', () => {
@@ -1452,7 +1526,7 @@ describe('Change Streams', function () {
14521526
it('does not send invalid options on the aggregate command', {
14531527
metadata: { requires: { topology: '!single' } },
14541528
test: async function () {
1455-
const started = [];
1529+
const started: CommandStartedEvent[] = [];
14561530

14571531
client.on('commandStarted', filterForCommands(['aggregate'], started));
14581532
const doc = { invalidBSONOption: true };
@@ -1473,7 +1547,7 @@ describe('Change Streams', function () {
14731547
it('does not send invalid options on the getMore command', {
14741548
metadata: { requires: { topology: '!single' } },
14751549
test: async function () {
1476-
const started = [];
1550+
const started: CommandStartedEvent[] = [];
14771551

14781552
client.on('commandStarted', filterForCommands(['aggregate'], started));
14791553
const doc = { invalidBSONOption: true };
@@ -1503,7 +1577,7 @@ describe('ChangeStream resumability', function () {
15031577
const changeStreamResumeOptions: ChangeStreamOptions = {
15041578
fullDocument: 'updateLookup',
15051579
collation: { locale: 'en', maxVariable: 'punct' },
1506-
maxAwaitTimeMS: 20000,
1580+
maxAwaitTimeMS: 2000,
15071581
batchSize: 200
15081582
};
15091583

0 commit comments

Comments
 (0)