Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-68434

Equality to null can incorrectly use a cached partial ixscan which does not cover the predicate

    • Query Optimization
    • Fully Compatible
    • ALL
    • v6.0, v5.0, v4.4, v4.2
    • Hide
      (function() {
      "use strict";
      
      load("jstests/aggregation/extras/utils.js");  // documentEq
      
      db.test.drop();
      
      const indexList = [
          {"num": 1, },
          {"num": -1},
      ];
      
      const indexOptions = [
          {partialFilterExpression: {$or: [{"num": {$exists: true}}, {"num": {$type: 'number'}}]}},
          {}, 
      ];
      
      const documentList = [
          {_id: 0},
          {_id: 1, num: null},
      ];
      
      assert.commandWorked(db.test.insert(documentList));
      
      for (let i = 0; i < indexList.length; i++) {
          db.test.createIndex(indexList[i], indexOptions[i]);
      }
      
      // Run the first query a few times to ensure an entry is active in the cache.
      for (let i = 0; i < 10; i++) {
          assert.eq(db.test.find({num: 5}).itcount(), 0);
      }
      
      // Now run with eq to null and expect it not to use the cached plan because of the partial filter.
      /** THIS FAILS */
      assert.eq(2, db.test.find({num: null}).itcount(), db.test.aggregate([{$planCacheStats: {}}]).toArray());
      
      })();
      
      Show
      (function() { "use strict" ; load( "jstests/aggregation/extras/utils.js" ); // documentEq db.test.drop(); const indexList = [ { "num" : 1, }, { "num" : -1}, ]; const indexOptions = [ {partialFilterExpression: {$or: [{ "num" : {$exists: true }}, { "num" : {$type: 'number' }}]}}, {}, ]; const documentList = [ {_id: 0}, {_id: 1, num: null }, ]; assert .commandWorked(db.test.insert(documentList)); for (let i = 0; i < indexList.length; i++) { db.test.createIndex(indexList[i], indexOptions[i]); } // Run the first query a few times to ensure an entry is active in the cache. for (let i = 0; i < 10; i++) { assert .eq(db.test.find({num: 5}).itcount(), 0); } // Now run with eq to null and expect it not to use the cached plan because of the partial filter. /** THIS FAILS */ assert .eq(2, db.test.find({num: null }).itcount(), db.test.aggregate([{$planCacheStats: {}}]).toArray()); })();
    • QO 2023-02-06, QO 2023-02-20
    • 16

      Given an index with a partial filter containing a $or and one branch being {<path>: {$exists: true}}. If a plan cache entry gets created (and is active) for a parameterized query such as {<path>: {$eq: 5}}, then a subsequent query of the form {<path>: {$eq: null}} will incorrectly use the cached plan even though $eq with null does not satisfy the $exists filter since it also matches missing. The $or is significant as I was not able to reproduce this with a single $exists partial index filter.

      Attached is a minimal repro script. Note that I verified this happens on 6.0 and master, but not on 5.0. 

            Assignee:
            nicholas.zolnierz@mongodb.com Nicholas Zolnierz
            Reporter:
            nicholas.zolnierz@mongodb.com Nicholas Zolnierz
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

              Created:
              Updated:
              Resolved: