diff options
author | David Storch <david.storch@10gen.com> | 2014-04-15 09:58:30 -0400 |
---|---|---|
committer | Matt Kangas <matt.kangas@mongodb.com> | 2014-04-15 18:44:28 -0400 |
commit | 12279052c851ae5a1ed43d98433ced9db7682d3a (patch) | |
tree | 77fad4f4bf02f8a51c66e9c179dec4e53e8e2d48 | |
parent | 5636fd6504b1b5a649468e62f48b7bae00f24b0f (diff) | |
download | mongo-12279052c851ae5a1ed43d98433ced9db7682d3a.tar.gz |
SERVER-13566 oplog start stage uses only 'ts' field as a filter
(cherry picked from commit 29d7dcd55a761cfa6a1a7e1586b80f9d1083b4f4)
-rw-r--r-- | jstests/core/query_oplogreplay.js | 45 | ||||
-rw-r--r-- | src/mongo/db/query/new_find.cpp | 41 |
2 files changed, 85 insertions, 1 deletions
diff --git a/jstests/core/query_oplogreplay.js b/jstests/core/query_oplogreplay.js new file mode 100644 index 00000000000..ab291af2be1 --- /dev/null +++ b/jstests/core/query_oplogreplay.js @@ -0,0 +1,45 @@ +// Test queries that set the OplogReplay flag. + +var t = db.jstests_query_oplogreplay; +t.drop(); + +for (var i = 0; i < 100; i++) { + t.save({_id: i, ts: i}); +} + +// Missing 'ts' field. +assert.throws(function() { + t.find().addOption(DBQuery.Option.oplogReplay).next(); +}); +assert.throws(function() { + t.find({_id: 3}).addOption(DBQuery.Option.oplogReplay).next(); +}); + +// 'ts' field is not top-level. +assert.throws(function() { + t.find({$or: [{ts: {$gt: 3}}, {foo: 3}]}) + .addOption(DBQuery.Option.oplogReplay).next(); +}); +assert.throws(function() { + t.find({$nor: [{ts: {$gt: 4}}, {foo: 4}]}) + .addOption(DBQuery.Option.oplogReplay).next(); +}); + +// Predicate over 'ts' is not $gt or $gte. +assert.throws(function() { + t.find({ts: {$lt: 4}}).addOption(DBQuery.Option.oplogReplay).next(); +}); +assert.throws(function() { + t.find({ts: {$lt: 4}, _id: 3}).addOption(DBQuery.Option.oplogReplay).next(); +}); + +// Query on just the 'ts' field. +var cursor = t.find({ts: {$gt: 20}}).addOption(DBQuery.Option.oplogReplay); +assert.eq(21, cursor.next()["_id"]); +assert.eq(22, cursor.next()["_id"]); + +// Query over both 'ts' and '_id' should only pay attention to the 'ts' +// field for finding the oplog start (SERVER-13566). +cursor = t.find({ts: {$gte: 20}, _id: 25}).addOption(DBQuery.Option.oplogReplay); +assert.eq(25, cursor.next()["_id"]); +assert(!cursor.hasNext()); diff --git a/src/mongo/db/query/new_find.cpp b/src/mongo/db/query/new_find.cpp index 6c309be0aaa..00520827c32 100644 --- a/src/mongo/db/query/new_find.cpp +++ b/src/mongo/db/query/new_find.cpp @@ -91,6 +91,19 @@ namespace { return n >= pq.getNumToReturn(); } + /** + * Returns true if 'me' is a GTE or GE predicate over the "ts" field. + * Such predicates can be used for the oplog start hack. + */ + bool isOplogTsPred(const mongo::MatchExpression* me) { + if (mongo::MatchExpression::GT != me->matchType() + && mongo::MatchExpression::GTE != me->matchType()) { + return false; + } + + return mongoutils::str::equals(me->path().rawData(), "ts"); + } + } // namespace namespace mongo { @@ -317,9 +330,35 @@ namespace mongo { return Status(ErrorCodes::InternalError, "getOplogStartHack called with a NULL collection" ); + // A query can only do oplog start finding if it has a top-level $gt or $gte predicate over + // the "ts" field (the operation's timestamp). Find that predicate and pass it to + // the OplogStart stage. + MatchExpression* tsExpr = NULL; + if (MatchExpression::AND == cq->root()->matchType()) { + // The query has an AND at the top-level. See if any of the children + // of the AND are $gt or $gte predicates over 'ts'. + for (size_t i = 0; i < cq->root()->numChildren(); ++i) { + MatchExpression* me = cq->root()->getChild(i); + if (isOplogTsPred(me)) { + tsExpr = me; + break; + } + } + } + else if (isOplogTsPred(cq->root())) { + // The root of the tree is a $gt or $gte predicate over 'ts'. + tsExpr = cq->root(); + } + + if (NULL == tsExpr) { + return Status(ErrorCodes::OplogOperationUnsupported, + "OplogReplay query does not contain top-level " + "$gt or $gte over the 'ts' field."); + } + // Make an oplog start finding stage. WorkingSet* oplogws = new WorkingSet(); - OplogStart* stage = new OplogStart(cq->ns(), cq->root(), oplogws); + OplogStart* stage = new OplogStart(cq->ns(), tsExpr, oplogws); // Takes ownership of ws and stage. auto_ptr<InternalRunner> runner(new InternalRunner(collection, stage, oplogws)); |