// Integration testing for the plan cache and index filter commands with collation. // // @tags: [ // assumes_read_concern_unchanged, // # This test attempts to perform queries and introspect the server's plan cache entries. The // # former operation may be routed to a secondary in the replica set, whereas the latter must be // # routed to the primary. // assumes_read_preference_unchanged, // assumes_unsharded_collection, // does_not_support_stepdowns, // sbe_incompatible, // ] (function() { 'use strict'; var coll = db.collation_plan_cache; coll.drop(); function dumpPlanCacheState() { return coll.aggregate([{$planCacheStats: {}}]).toArray(); } function getPlansByQuery(match) { return coll.aggregate([{$planCacheStats: {}}, {$match: match}]).toArray(); } assert.commandWorked(coll.insert({a: 'foo', b: 5})); // We need two indexes that each query can use so that a plan cache entry is created. assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: 'en_US'}})); assert.commandWorked(coll.createIndex({a: 1, b: 1}, {collation: {locale: 'en_US'}})); // We need an index with a different collation, so that string comparisons affect the query // shape. assert.commandWorked(coll.createIndex({b: 1}, {collation: {locale: 'fr_CA'}})); // Run a query so that an entry is inserted into the cache. assert.commandWorked( coll.runCommand("find", {filter: {a: 'foo', b: 5}, collation: {locale: "en_US"}}), 'find command failed'); // The query shape should have been added. var shapes = coll.aggregate([{$planCacheStats: {}}]).toArray(); assert.eq(1, shapes.length, 'unexpected cache size after running query'); assert.eq(shapes[0].createdFromQuery.query, {a: 'foo', b: 5}, shapes); assert.eq(shapes[0].createdFromQuery.sort, {}, shapes); assert.eq(shapes[0].createdFromQuery.projection, {}, shapes); assert.eq(shapes[0].createdFromQuery.collation, { locale: 'en_US', caseLevel: false, caseFirst: 'off', strength: 3, numericOrdering: false, alternate: 'non-ignorable', maxVariable: 'punct', normalization: false, backwards: false, version: '57.1' }, shapes); coll.getPlanCache().clear(); // Run a query so that an entry is inserted into the cache. assert.commandWorked( coll.runCommand("find", {filter: {a: 'foo', b: 5}, collation: {locale: "en_US"}})); // The query should have cached plans. assert.lt(0, getPlansByQuery({ 'createdFromQuery.query': {a: 'foo', b: 5}, 'createdFromQuery.collation.locale': 'en_US' }).length, dumpPlanCacheState()); // A query with a different collation should have no cached plans. assert.eq(0, getPlansByQuery({ 'createdFromQuery.query': {a: 'foo', b: 5}, 'createdFromQuery.sort': {}, 'createdFromQuery.projection': {}, 'createdFromQuery.collation.locale': 'fr_CA' }).length, dumpPlanCacheState()); // A query with different string locations should have no cached plans. assert.eq(0, getPlansByQuery({ 'createdFromQuery.query': {a: 'foo', b: 'bar'}, 'createdFromQuery.sort': {}, 'createdFromQuery.projection': {}, 'createdFromQuery.collation': {locale: 'en_US'} }).length, dumpPlanCacheState()); coll.getPlanCache().clear(); // clearPlansByQuery(). // Passing a query with an empty collation object should throw. assert.throws(function() { coll.getPlanCache().clearPlansByQuery( {query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: {}}); }, [], 'empty collation object should throw'); // Passing a query with an invalid collation object should throw. assert.throws(function() { coll.getPlanCache().clearPlansByQuery( {query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: {bad: "value"}}); }, [], 'invalid collation object should throw'); // Run a query so that an entry is inserted into the cache. assert.commandWorked( coll.runCommand("find", {filter: {a: 'foo', b: 5}, collation: {locale: "en_US"}}), 'find command failed'); assert.eq(1, coll.aggregate([{$planCacheStats: {}}]).itcount(), dumpPlanCacheState()); // Dropping a query shape with a different collation should have no effect. coll.getPlanCache().clearPlansByQuery( {query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: {locale: 'fr_CA'}}); assert.eq(1, coll.aggregate([{$planCacheStats: {}}]).itcount(), dumpPlanCacheState()); // Dropping a query shape with different string locations should have no effect. coll.getPlanCache().clearPlansByQuery( {query: {a: 'foo', b: 'bar'}, sort: {}, projection: {}, collation: {locale: 'en_US'}}); assert.eq(1, coll.aggregate([{$planCacheStats: {}}]).itcount(), dumpPlanCacheState()); // Dropping query shape. coll.getPlanCache().clearPlansByQuery( {query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: {locale: 'en_US'}}); assert.eq(0, coll.aggregate([{$planCacheStats: {}}]).itcount(), dumpPlanCacheState()); // 'collation' parameter is not allowed with 'query' parameter for 'planCacheClear'. assert.commandFailedWithCode(coll.runCommand('planCacheClear', {collation: {locale: "en_US"}}), ErrorCodes.BadValue); // Index filter commands. // planCacheSetFilter should fail if 'collation' is an empty object. assert.commandFailed( coll.runCommand('planCacheSetFilter', {query: {a: 'foo', b: 5}, collation: {}, indexes: [{a: 1, b: 1}]}), 'planCacheSetFilter should fail on empty collation object'); // planCacheSetFilter should fail if 'collation' is an invalid object. assert.commandFailed( coll.runCommand('planCacheSetFilter', {query: {a: 'foo', b: 5}, collation: {bad: "value"}, indexes: [{a: 1, b: 1}]}), 'planCacheSetFilter should fail on invalid collation object'); // Set a plan cache filter. assert.commandWorked( coll.runCommand( 'planCacheSetFilter', {query: {a: 'foo', b: 5}, collation: {locale: 'en_US'}, indexes: [{a: 1, b: 1}]}), 'planCacheSetFilter failed'); // Check the plan cache filter was added. var res = coll.runCommand('planCacheListFilters'); assert.commandWorked(res, 'planCacheListFilters failed'); assert.eq(1, res.filters.length, 'unexpected number of plan cache filters'); assert.eq(res.filters[0], { query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: { locale: 'en_US', caseLevel: false, caseFirst: 'off', strength: 3, numericOrdering: false, alternate: 'non-ignorable', maxVariable: 'punct', normalization: false, backwards: false, version: '57.1' }, indexes: [{a: 1, b: 1}] }, 'unexpected plan cache filter'); // planCacheClearFilters should fail if 'collation' is an empty object. assert.commandFailed( coll.runCommand('planCacheClearFilters', {query: {a: 'foo', b: 5}, collation: {}}), 'planCacheClearFilters should fail on empty collation object'); // planCacheSetFilter should fail if 'collation' is an invalid object. assert.commandFailed( coll.runCommand('planCacheClearFilters', {query: {a: 'foo', b: 5}, collation: {bad: 'value'}}), 'planCacheClearFilters should fail on invalid collation object'); // Clearing a plan cache filter with no collation should have no effect. assert.commandWorked(coll.runCommand('planCacheClearFilters', {query: {a: 'foo', b: 5}})); assert.eq(1, coll.runCommand('planCacheListFilters').filters.length, 'unexpected number of plan cache filters'); // Clearing a plan cache filter with a different collation should have no effect. assert.commandWorked(coll.runCommand('planCacheClearFilters', {query: {a: 'foo', b: 5}, collation: {locale: 'fr_CA'}})); assert.eq(1, coll.runCommand('planCacheListFilters').filters.length, 'unexpected number of plan cache filters'); // Clearing a plan cache filter with different string locations should have no effect. assert.commandWorked(coll.runCommand('planCacheClearFilters', {query: {a: 'foo', b: 'bar', collation: {locale: 'en_US'}}})); assert.eq(1, coll.runCommand('planCacheListFilters').filters.length, 'unexpected number of plan cache filters'); // Clear plan cache filter. assert.commandWorked(coll.runCommand('planCacheClearFilters', {query: {a: 'foo', b: 5}, collation: {locale: 'en_US'}})); assert.eq(0, coll.runCommand('planCacheListFilters').filters.length, 'unexpected number of plan cache filters'); })();