/** * Index Filter commands * * Commands: * - planCacheListFilters * Displays index filters for all query shapes in a collection. * * - planCacheClearFilters * Clears index filter for a single query shape or, * if the query shape is omitted, all filters for the collection. * * - planCacheSetFilter * Sets index filter for a query shape. Overrides existing filter. * * Not a lot of data access in this test suite. Hint commands * manage a non-persistent mapping in the server of * query shape to list of index specs. * * Only time we might need to execute a query is to check the plan * cache state. We would do this with the planCacheListPlans command * on the same query shape with the index filters. * */ var t = db.jstests_index_filter_commands; t.drop(); t.save({a: 1}); // Add 2 indexes. // 1st index is more efficient. // 2nd and 3rd indexes will be used to test index filters. var indexA1 = {a: 1}; var indexA1B1 = {a: 1, b: 1}; var indexA1C1 = {a: 1, c: 1}; t.ensureIndex(indexA1); t.ensureIndex(indexA1B1); t.ensureIndex(indexA1C1); var queryA1 = {a: 1}; var projectionA1 = {_id: 0, a: 1}; var sortA1 = {a: -1}; // // Tests for planCacheListFilters, planCacheClearFilters, planCacheSetFilter // // Utility function to list index filters. function getFilters(collection) { if (collection == undefined) { collection = t; } var res = collection.runCommand('planCacheListFilters'); print('planCacheListFilters() = ' + tojson(res)); assert.commandWorked(res, 'planCacheListFilters failed'); assert(res.hasOwnProperty('filters'), 'filters missing from planCacheListFilters result'); return res.filters; } // If query shape is in plan cache, // planCacheListPlans returns non-empty array of plans. function planCacheContains(shape) { var res = t.runCommand('planCacheListPlans', shape); assert.commandWorked(res); return res.plans.length > 0; } // Utility function to list plans for a query. function getPlans(shape) { var res = t.runCommand('planCacheListPlans', shape); assert.commandWorked(res, 'planCacheListPlans(' + tojson(shape, '', true) + ' failed'); assert(res.hasOwnProperty('plans'), 'plans missing from planCacheListPlans(' + tojson(shape, '', true) + ') result'); return res.plans; } // Attempting to retrieve index filters on a non-existent collection // will return empty results. var missingCollection = db.jstests_index_filter_commands_missing; missingCollection.drop(); assert.eq(0, getFilters(missingCollection), 'planCacheListFilters should return empty array on non-existent collection'); // Retrieve index filters from an empty test collection. var filters = getFilters(); assert.eq(0, filters.length, 'unexpected number of index filters in planCacheListFilters result'); // Check details of winning plan in plan cache before setting index filter. assert.eq(1, t.find(queryA1, projectionA1).sort(sortA1).itcount(), 'unexpected document count'); var shape = {query: queryA1, sort: sortA1, projection: projectionA1}; var planBeforeSetFilter = getPlans(shape)[0]; print('Winning plan (before setting index filters) = ' + tojson(planBeforeSetFilter)); // Check filterSet field in plan details assert.eq(false, planBeforeSetFilter.filterSet, 'missing or invalid filterSet field in plan details'); // Adding index filters to a non-existent collection should be an error. assert.commandFailed(missingCollection.runCommand('planCacheSetFilter', {query: queryA1, sort: sortA1, projection: projectionA1, indexes: [indexA1B1, indexA1C1]})); // Add index filters for simple query. assert.commandWorked(t.runCommand('planCacheSetFilter', {query: queryA1, sort: sortA1, projection: projectionA1, indexes: [indexA1B1, indexA1C1]})); filters = getFilters(); assert.eq(1, filters.length, 'no change in query settings after successfully setting index filters'); assert.eq(queryA1, filters[0].query, 'unexpected query in filters'); assert.eq(sortA1, filters[0].sort, 'unexpected sort in filters'); assert.eq(projectionA1, filters[0].projection, 'unexpected projection in filters'); assert.eq(2, filters[0].indexes.length, 'unexpected number of indexes in filters'); assert.eq(indexA1B1, filters[0].indexes[0], 'unexpected first index'); assert.eq(indexA1C1, filters[0].indexes[1], 'unexpected first index'); // Plans for query shape should be removed after setting index filter. assert(!planCacheContains(shape), 'plan cache for query shape not flushed after updating filter'); // Check details of winning plan in plan cache after setting filter and re-executing query. assert.eq(1, t.find(queryA1, projectionA1).sort(sortA1).itcount(), 'unexpected document count'); planAfterSetFilter = getPlans(shape)[0]; print('Winning plan (after setting index filter) = ' + tojson(planAfterSetFilter)); // Check filterSet field in plan details assert.eq(true, planAfterSetFilter.filterSet, 'missing or invalid filterSet field in plan details'); // Execute query with cursor.hint(). Check that user-provided hint is overridden. // Applying the index filters will remove the user requested index from the list // of indexes provided to the planner. // If the planner still tries to use the user hint, we will get a 'bad hint' error. t.find(queryA1, projectionA1).sort(sortA1).hint(indexA1).itcount(); // Clear filters // Clearing filters on a missing collection should be a no-op. assert.commandWorked(missingCollection.runCommand('planCacheClearFilters')); // Clear the filters set earlier. assert.commandWorked(t.runCommand('planCacheClearFilters')); filters = getFilters(); assert.eq(0, filters.length, 'filters not cleared after successful planCacheClearFilters command'); // Plans should be removed after clearing filters assert(!planCacheContains(shape), 'plan cache for query shape not flushed after clearing filters'); print('Plan details before setting filter = ' + tojson(planBeforeSetFilter.details, '', true)); print('Plan details after setting filter = ' + tojson(planAfterSetFilter.details, '', true)); // // explain.filterSet // cursor.explain() should indicate if index filter has been applied. // The following 3 runners should always provide a value for 'filterSet': // - SingleSolutionRunner // - MultiPlanRunner // - CachedPlanRuner // // No filter set. t.getPlanCache().clear(); // SingleSolutionRunner assert.eq(false, t.find({z: 1}).explain().filterSet, 'missing or invalid filterSet field in SingleSolutionRunner explain'); // MultiPlanRunner assert.eq(false, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet, 'missing or invalid filterSet field in MultiPlanRunner explain'); // CachedPlanRunner assert.eq(false, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet, 'missing or invalid filterSet field in CachedPlanRunner explain'); // Add index filter. assert.commandWorked(t.runCommand('planCacheSetFilter', {query: queryA1, sort: sortA1, projection: projectionA1, indexes: [indexA1B1, indexA1C1]})); // Index filter with non-existent index key pattern to force use of single solution runner. assert.commandWorked(t.runCommand('planCacheSetFilter', {query: {z: 1}, indexes: [{z: 1}]})); t.getPlanCache().clear(); // SingleSolutionRunner assert.eq(true, t.find({z: 1}).explain().filterSet, 'missing or invalid filterSet field in SingleSolutionRunner explain'); // MultiPlanRunner assert.eq(true, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet, 'missing or invalid filterSet field in MultiPlanRunner explain'); // CachedPlanRunner assert.eq(true, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet, 'missing or invalid filterSet field in CachedPlanRunner explain');