@@ -244,8 +244,7 @@ std::string DocumentSourceChangeStream::getNsRegexForChangeStream(const Namespac
244
244
245
245
BSONObj DocumentSourceChangeStream::buildMatchFilter (
246
246
const boost::intrusive_ptr<ExpressionContext>& expCtx,
247
- Timestamp startFrom,
248
- bool startFromInclusive,
247
+ Timestamp startFromInclusive,
249
248
bool showMigrationEvents) {
250
249
auto nss = expCtx->ns ;
251
250
@@ -297,6 +296,11 @@ BSONObj DocumentSourceChangeStream::buildMatchFilter(
297
296
// 2.1) Normal CRUD ops.
298
297
auto normalOpTypeMatch = BSON (" op" << NE << " n" );
299
298
299
+ // TODO SERVER-44039: we continue to generate 'kNewShardDetected' events for compatibility
300
+ // with 4.2, even though we no longer rely on them to detect new shards. We may wish to remove
301
+ // this mechanism in 4.6, or retain it for future cases where a change stream is targeted to a
302
+ // subset of shards. See SERVER-44039 for details.
303
+
300
304
// 2.2) A chunk gets migrated to a new shard that doesn't have any chunks.
301
305
auto chunkMigratedNewShardMatch = BSON (" op"
302
306
<< " n"
@@ -326,7 +330,7 @@ BSONObj DocumentSourceChangeStream::buildMatchFilter(
326
330
// Only include CRUD operations tagged "fromMigrate" when the "showMigrationEvents" option is
327
331
// set - exempt all other operations and commands with that tag. Include the resume token, if
328
332
// resuming, so we can verify it was still present in the oplog.
329
- return BSON (" $and" << BSON_ARRAY (BSON (" ts" << (startFromInclusive ? GTE : GT) << startFrom )
333
+ return BSON (" $and" << BSON_ARRAY (BSON (" ts" << GTE << startFromInclusive )
330
334
<< BSON (OR (opMatch, commandAndApplyOpsMatch))));
331
335
}
332
336
@@ -388,6 +392,7 @@ list<intrusive_ptr<DocumentSource>> buildPipeline(const intrusive_ptr<Expression
388
392
}
389
393
}
390
394
395
+ // If we do not have a 'resumeAfter' starting point, check for 'startAtOperationTime'.
391
396
if (auto startAtOperationTime = spec.getStartAtOperationTime ()) {
392
397
uassert (40674 ,
393
398
" Only one type of resume option is allowed, but multiple were found." ,
@@ -396,33 +401,37 @@ list<intrusive_ptr<DocumentSource>> buildPipeline(const intrusive_ptr<Expression
396
401
resumeStage = DocumentSourceShardCheckResumability::create (expCtx, *startFrom);
397
402
}
398
403
399
- // There might not be a starting point if we're on mongos, otherwise we should either have a
400
- // 'resumeAfter' starting point, or should start from the latest majority committed operation.
404
+ // We can only run on a replica set, or through mongoS. Confirm that this is the case.
401
405
auto replCoord = repl::ReplicationCoordinator::get (expCtx->opCtx );
402
406
uassert (
403
407
40573 ,
404
408
" The $changeStream stage is only supported on replica sets" ,
405
409
expCtx->inMongos ||
406
410
(replCoord &&
407
411
replCoord->getReplicationMode () == repl::ReplicationCoordinator::Mode::modeReplSet));
408
- if (!startFrom && !expCtx->inMongos ) {
409
- startFrom = replCoord->getMyLastAppliedOpTime ().getTimestamp ();
412
+
413
+ // If we do not have an explicit starting point, we should start from the latest majority
414
+ // committed operation. If we are on mongoS and do not have a starting point, set it to the
415
+ // current clusterTime so that all shards start in sync. We always start one tick beyond the
416
+ // most recent operation, to ensure that the stream does not return it.
417
+ if (!startFrom) {
418
+ const auto currentTime = !expCtx->inMongos
419
+ ? LogicalTime{replCoord->getMyLastAppliedOpTime ().getTimestamp ()}
420
+ : LogicalClock::get (expCtx->opCtx )->getClusterTime ();
421
+ startFrom = currentTime.addTicks (1 ).asTimestamp ();
410
422
}
411
423
412
- if (startFrom) {
413
- const bool startFromInclusive = (resumeStage != nullptr );
414
- stages.push_back (DocumentSourceOplogMatch::create (
415
- DocumentSourceChangeStream::buildMatchFilter (
416
- expCtx, *startFrom, startFromInclusive, showMigrationEvents),
417
- expCtx));
418
-
419
- // If we haven't already populated the initial PBRT, then we are starting from a specific
420
- // timestamp rather than a resume token. Initialize the PBRT to a high water mark token.
421
- if (expCtx->initialPostBatchResumeToken .isEmpty ()) {
422
- Timestamp startTime{startFrom->getSecs (), startFrom->getInc () + (!startFromInclusive)};
423
- expCtx->initialPostBatchResumeToken =
424
- ResumeToken::makeHighWaterMarkToken (startTime).toDocument ().toBson ();
425
- }
424
+ // We must always build the DSOplogMatch stage even on mongoS, since our validation logic relies
425
+ // upon the fact that it is always the first stage in the pipeline.
426
+ stages.push_back (DocumentSourceOplogMatch::create (
427
+ DocumentSourceChangeStream::buildMatchFilter (expCtx, *startFrom, showMigrationEvents),
428
+ expCtx));
429
+
430
+ // If we haven't already populated the initial PBRT, then we are starting from a specific
431
+ // timestamp rather than a resume token. Initialize the PBRT to a high water mark token.
432
+ if (expCtx->initialPostBatchResumeToken .isEmpty ()) {
433
+ expCtx->initialPostBatchResumeToken =
434
+ ResumeToken::makeHighWaterMarkToken (*startFrom).toDocument ().toBson ();
426
435
}
427
436
428
437
// Obtain the current FCV and use it to create the DocumentSourceChangeStreamTransform stage.
@@ -516,12 +525,14 @@ void DocumentSourceChangeStream::assertIsLegalSpecification(
516
525
(expCtx->ns .isAdminDB () && expCtx->ns .isCollectionlessAggregateNS ()));
517
526
518
527
// Prevent $changeStream from running on internal databases. A stream may run against the
519
- // 'admin' database iff 'allChangesForCluster' is true.
528
+ // 'admin' database iff 'allChangesForCluster' is true. A stream may run against the 'config'
529
+ // database iff 'allowToRunOnConfigDB' is true.
530
+ const bool isNotBannedInternalDB =
531
+ !expCtx->ns .isLocal () && (!expCtx->ns .isConfigDB () || spec.getAllowToRunOnConfigDB ());
520
532
uassert (ErrorCodes::InvalidNamespace,
521
533
str::stream () << " $changeStream may not be opened on the internal " << expCtx->ns .db ()
522
534
<< " database" ,
523
- expCtx->ns .isAdminDB () ? spec.getAllChangesForCluster ()
524
- : (!expCtx->ns .isLocal () && !expCtx->ns .isConfigDB ()));
535
+ expCtx->ns .isAdminDB () ? spec.getAllChangesForCluster () : isNotBannedInternalDB);
525
536
526
537
// Prevent $changeStream from running on internal collections in any database.
527
538
uassert (ErrorCodes::InvalidNamespace,
0 commit comments