summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/query_oplogreplay.js45
-rw-r--r--src/mongo/db/query/new_find.cpp41
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));