summaryrefslogtreecommitdiff
path: root/jstests/core/index/index_partial_read_ops.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/core/index/index_partial_read_ops.js')
-rw-r--r--jstests/core/index/index_partial_read_ops.js198
1 files changed, 198 insertions, 0 deletions
diff --git a/jstests/core/index/index_partial_read_ops.js b/jstests/core/index/index_partial_read_ops.js
new file mode 100644
index 00000000000..2bc6578479e
--- /dev/null
+++ b/jstests/core/index/index_partial_read_ops.js
@@ -0,0 +1,198 @@
+// Cannot implicitly shard accessed collections because the explain output from a mongod when run
+// against a sharded collection is wrapped in a "shards" object with keys for each shard.
+// @tags: [
+// assumes_unsharded_collection,
+// does_not_support_stepdowns,
+// requires_fcv_51,
+// # TODO SERVER-30466
+// does_not_support_causal_consistency,
+// ]
+
+// Read ops tests for partial indexes.
+
+// Include helpers for analyzing explain output.
+load("jstests/libs/analyze_plan.js");
+load("jstests/libs/feature_flag_util.js");
+
+(function() {
+"use strict";
+let explain;
+const coll = db.index_partial_read_ops;
+
+(function testBasicPartialFilterExpression() {
+ coll.drop();
+
+ assert.commandWorked(coll.createIndex({x: 1}, {partialFilterExpression: {a: {$lte: 1.5}}}));
+ assert.commandWorked(coll.insert({x: 5, a: 2})); // Not in index.
+ assert.commandWorked(coll.insert({x: 6, a: 1})); // In index.
+
+ //
+ // Verify basic functionality with find().
+ //
+
+ // find() operations that should use index.
+ explain = coll.explain('executionStats').find({x: 6, a: 1}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: {$gt: 1}, a: 1}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: 6, a: {$lte: 1}}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+
+ // find() operations that should not use index.
+ explain = coll.explain('executionStats').find({x: 6, a: {$lt: 1.6}}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: 6}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+
+ //
+ // Verify basic functionality with the count command.
+ //
+
+ // Count operation that should use index.
+ explain = coll.explain('executionStats').count({x: {$gt: 1}, a: 1});
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+
+ // Count operation that should not use index.
+ explain = coll.explain('executionStats').count({x: {$gt: 1}, a: 2});
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+
+ //
+ // Verify basic functionality with the aggregate command.
+ //
+
+ // Aggregate operation that should use index.
+ explain = coll.aggregate([{$match: {x: {$gt: 1}, a: 1}}], {explain: true});
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+
+ // Aggregate operation that should not use index.
+ explain = coll.aggregate([{$match: {x: {$gt: 1}, a: 2}}], {explain: true});
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+
+ //
+ // Verify basic functionality with the findAndModify command.
+ //
+
+ // findAndModify operation that should use index.
+ explain = coll.explain('executionStats')
+ .findAndModify({query: {x: {$gt: 1}, a: 1}, update: {$inc: {x: 1}}});
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+
+ // findAndModify operation that should not use index.
+ explain = coll.explain('executionStats')
+ .findAndModify({query: {x: {$gt: 1}, a: 2}, update: {$inc: {x: 1}}});
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+
+ //
+ // Verify functionality with multiple overlapping partial indexes on the same key pattern.
+ //
+
+ // Remove existing indexes and documents.
+ assert.commandWorked(coll.dropIndexes());
+ assert.commandWorked(coll.remove({}));
+
+ assert.commandWorked(
+ coll.createIndex({a: 1}, {name: "index1", partialFilterExpression: {a: {$gte: 0}}}));
+ assert.commandWorked(
+ coll.createIndex({a: 1}, {name: "index2", partialFilterExpression: {a: {$gte: 10}}}));
+ assert.commandWorked(
+ coll.createIndex({a: 1}, {name: "index3", partialFilterExpression: {a: {$gte: 100}}}));
+
+ assert.commandWorked(coll.insert([{a: 1}, {a: 2}, {a: 3}]));
+ assert.commandWorked(coll.insert([{a: 11}, {a: 12}, {a: 13}]));
+ assert.commandWorked(coll.insert([{a: 101}, {a: 102}, {a: 103}]));
+
+ // Function which verifies that the given query is indexed, that it produces the same output as
+ // a COLLSCAN and the given 'expectedResults' array, and that 'numAlternativePlans' were
+ // generated.
+ function assertIndexedQueryAndResults(query, numAlternativePlans, expectedResults) {
+ const explainOut = coll.explain().find(query).finish();
+ const results = coll.find(query).toArray();
+ assert(isIxscan(db, explainOut), tojson(explainOut));
+ assert.eq(getRejectedPlans(explainOut).length, numAlternativePlans, tojson(explainOut));
+ assert.sameMembers(results, coll.find(query).hint({$natural: 1}).toArray());
+ assert.sameMembers(results.map(doc => (delete doc._id && doc)), expectedResults);
+ }
+
+ // Queries which fall within the covered ranges generate plans for all applicable partial
+ // indexes.
+ assertIndexedQueryAndResults({a: {$gt: 0, $lt: 10}}, 0, [{a: 1}, {a: 2}, {a: 3}]);
+ assertIndexedQueryAndResults({a: {$gt: 10, $lt: 100}}, 1, [{a: 11}, {a: 12}, {a: 13}]);
+ assertIndexedQueryAndResults({a: {$gt: 100, $lt: 1000}}, 2, [{a: 101}, {a: 102}, {a: 103}]);
+ assertIndexedQueryAndResults(
+ {a: {$gt: 0}},
+ 0,
+ [{a: 1}, {a: 2}, {a: 3}, {a: 11}, {a: 12}, {a: 13}, {a: 101}, {a: 102}, {a: 103}]);
+
+ // Queries which fall outside the range of any partial indexes produce a COLLSCAN.
+ assert(isCollscan(db, coll.explain().find({a: {$lt: 0}}).finish()));
+})();
+
+if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) {
+ jsTest.log(
+ "Skipping partialFilterExpression testing for $in, $or and non-top level $and as timeseriesMetricIndexesEnabled is false");
+ return;
+}
+
+(function testFilterWithInExpression() {
+ assert(coll.drop());
+
+ assert.commandWorked(coll.createIndex({x: 1}, {partialFilterExpression: {a: {$in: [3, 5]}}}));
+ assert.commandWorked(coll.insert({x: 1, a: 4})); // Not in index.
+ assert.commandWorked(coll.insert({x: 2, a: 3})); // In index.
+ assert.commandWorked(coll.insert({x: 3, a: 5})); // In index.
+
+ // find() operations that should use index.
+ explain = coll.explain('executionStats').find({x: 2, a: 3}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: {$gt: 1}, a: 5}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+
+ // find() operations that should not use index.
+ explain = coll.explain('executionStats').find({x: 3, a: {$lt: 6}}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: 2}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+})();
+
+(function testFilterWithMultiLevelAndOrExpressions() {
+ assert(coll.drop());
+
+ assert.commandWorked(coll.createIndex(
+ {x: 1}, {partialFilterExpression: {$or: [{a: 3}, {$and: [{a: 5}, {b: 5}]}]}}));
+ assert.commandWorked(coll.insert({x: 1, a: 1})); // Not in index.
+ assert.commandWorked(coll.insert({x: 2, a: 5})); // Not in index.
+ assert.commandWorked(coll.insert({x: 3, a: 5, b: 1})); // Not in index.
+ assert.commandWorked(coll.insert({x: 4, a: 3})); // In index.
+ assert.commandWorked(coll.insert({x: 5, a: 5, b: 5})); // In index.
+
+ // find() operations that should use index.
+ explain = coll.explain('executionStats').find({x: 4, a: 3}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: 5, a: 5, b: 5}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isIxscan(db, getWinningPlan(explain.queryPlanner)), explain.queryPlanner);
+
+ // find() operations that should not use index.
+ explain = coll.explain('executionStats').find({x: 1, a: 1}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: 2, a: 5}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+ explain = coll.explain('executionStats').find({x: 3, a: 5, b: 1}).finish();
+ assert.eq(1, explain.executionStats.nReturned);
+ assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));
+})();
+})();