Given a compound index with a non-simple collation, a point-query on a prefix field together with a sort on a suffix field can result in a IXSCAN query plan that incorrectly sorts according to the collation. This issue has existed at least since version 3.6.
The attached reproduction script describes the problem. A query which does not specify a collation (or which explicitly specifies a different collation) may still use an index which has a non-matching collation if the query predicates are non-string values. We then call QueryPlannerAnalysis::analyzeSort to determine whether we need to perform an in-memory sort. As part of this process we call QueryPlannerAnalysis::explodeForSort to see whether the plan can be rewritten as a SORT_MERGE. Unfortunately, this method does not enforce the constraint that an index with a different collation than the query cannot provide a sort.
Incorrect execution plan example:
db.c.find({a: 1}).sort({b: 1}).explain() { "queryPlanner" : { "plannerVersion" : 1, "namespace" : "test.c", "indexFilterSet" : false, "parsedQuery" : { "a" : { "$eq" : 1 } }, "queryHash" : "E97AE8CA", "planCacheKey" : "35AFA63E", "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "SORT_MERGE", "sortPattern" : { "b" : 1 }, "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "a" : 1, "b" : 1 }, "indexName" : "a_1_b_1", "collation" : { "locale" : "en", "caseLevel" : false, "caseFirst" : "off", "strength" : 1, "numericOrdering" : false, "alternate" : "non-ignorable", "maxVariable" : "punct", "normalization" : false, "backwards" : false, "version" : "57.1" }, "isMultiKey" : false, "multiKeyPaths" : { "a" : [ ], "b" : [ ] }, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "a" : [ "[1.0, 1.0]" ], "b" : [ "[MinKey, MaxKey]" ] } } } }, "rejectedPlans" : [ ] }, "serverInfo" : { "host" : "redshirt", "port" : 27017, "version" : "0.0.0", "gitVersion" : "unknown" }, "ok" : 1 }
Incorrect output example. Note that the results are sorted on case-insensitive value of field "b".
> db.c.find({a: 1}).sort({b: 1}) { "_id" : ObjectId("5eecc3ecb4eefa8f13223f2c"), "a" : 1, "b" : "aa" } { "_id" : ObjectId("5eecc3f3b4eefa8f13223f2d"), "a" : 1, "b" : "AA" } { "_id" : ObjectId("5eecc3f7b4eefa8f13223f2e"), "a" : 1, "b" : "BB" } { "_id" : ObjectId("5eecc3f9b4eefa8f13223f2f"), "a" : 1, "b" : "bb" }
- related to
-
SERVER-50173 [v4.4] Remove explode_for_sort_collation.js from backports list of test suite sharding_multiversion
- Closed