89 lines
4.5 KiB
JavaScript
89 lines
4.5 KiB
JavaScript
/**
|
|
* A property-based test to assert the correctness of partial indexes. Generates a filter, indexes,
|
|
* and queries, then creates partial indexes using the filter and prefixes every query with
|
|
* {$match: filter}. This way, every query is eligible to use the indexes, rather than leaving it
|
|
* up to chance.
|
|
* Queries with similar shapes are run consecutively, to trigger plan cache interactions.
|
|
*
|
|
* @tags: [
|
|
* query_intensive_pbt,
|
|
* # This test runs commands that are not allowed with security token: setParameter.
|
|
* not_allowed_with_signed_security_token,
|
|
* assumes_no_implicit_collection_creation_after_drop,
|
|
* # Incompatible with setParameter
|
|
* does_not_support_stepdowns,
|
|
* # Change in read concern can slow down queries enough to hit a timeout.
|
|
* assumes_read_concern_unchanged,
|
|
* does_not_support_causal_consistency,
|
|
* # Runs queries that may return many results, requiring getmores
|
|
* requires_getmore,
|
|
* multiversion_incompatible,
|
|
* # Time series collections do not support indexing array values in measurement fields.
|
|
* exclude_from_timeseries_crud_passthrough,
|
|
* ]
|
|
*/
|
|
import {createCacheCorrectnessProperty} from "jstests/libs/property_test_helpers/common_properties.js";
|
|
import {getDatasetModel} from "jstests/libs/property_test_helpers/models/document_models.js";
|
|
import {getIndexModel} from "jstests/libs/property_test_helpers/models/index_models.js";
|
|
import {getPartialFilterPredicateArb} from "jstests/libs/property_test_helpers/models/match_models.js";
|
|
import {getQueryAndOptionsModel} from "jstests/libs/property_test_helpers/models/query_models.js";
|
|
import {partialIndexCounterexamples} from "jstests/libs/property_test_helpers/pbt_resolved_bugs.js";
|
|
import {concreteQueryFromFamily, testProperty} from "jstests/libs/property_test_helpers/property_testing_utils.js";
|
|
import {isSlowBuild} from "jstests/libs/query/aggregation_pipeline_utils.js";
|
|
import {fc} from "jstests/third_party/fast_check/fc-3.1.0.js";
|
|
|
|
if (isSlowBuild(db)) {
|
|
jsTestLog("Exiting early because debug is on, opt is off, or a sanitizer is enabled.");
|
|
quit();
|
|
}
|
|
|
|
const numRuns = 100;
|
|
const numQueriesPerRun = 20;
|
|
|
|
const controlColl = db.partial_index_pbt_control;
|
|
const experimentColl = db.partial_index_pbt_experiment;
|
|
// Use the cache correctness property so we can test interactions between the plan cache and
|
|
// partial indexes.
|
|
const correctnessProperty = createCacheCorrectnessProperty(controlColl, experimentColl);
|
|
|
|
const workloadModel = fc
|
|
.record({
|
|
// This filter will be used for the partial index filter, and to prefix queries with
|
|
// {$match: filter} so that every query is eligible to use the partial indexes.
|
|
partialFilterPredShape: getPartialFilterPredicateArb(),
|
|
docs: getDatasetModel(),
|
|
indexes: fc.array(getIndexModel({allowPartialIndexes: false, allowSparse: false}), {
|
|
minLength: 0,
|
|
maxLength: 15,
|
|
size: "+2",
|
|
}),
|
|
queries: fc.array(getQueryAndOptionsModel(), {minLength: 1, maxLength: numQueriesPerRun, size: "+2"}),
|
|
})
|
|
.map(({partialFilterPredShape, docs, indexes, queries}) => {
|
|
// The predicate model generates a family of predicates of the same shape, with different
|
|
// parameter options at the leaf nodes. For all indexes, we use the first predicate from the
|
|
// family as the partial filter expression.
|
|
const firstPartialFilterPred = concreteQueryFromFamily(partialFilterPredShape, 0);
|
|
const partialIndexes = indexes.map(({def, options}) => {
|
|
return {
|
|
def,
|
|
options: Object.assign({}, options, {partialFilterExpression: firstPartialFilterPred}),
|
|
};
|
|
});
|
|
|
|
// For queries, we can include the entire predicate family in the $match. When the property
|
|
// asks for similar query shapes with different parameters plugged in, the $match will
|
|
// behave correctly. In general our queries are modeled as families of shapes, so including
|
|
// the predicate family in the $match rather than one specific predicate makes sense.
|
|
const match = {$match: partialFilterPredShape};
|
|
const queriesWithMatch = queries.map((query) => ({
|
|
"pipeline": [match, ...query.pipeline],
|
|
"options": query.options,
|
|
}));
|
|
return {collSpec: {isTS: false, docs, indexes: partialIndexes}, queries: queriesWithMatch};
|
|
});
|
|
|
|
// Test with a regular collection.
|
|
testProperty(correctnessProperty, {controlColl, experimentColl}, workloadModel, numRuns, partialIndexCounterexamples);
|
|
// TODO SERVER-103381 extend this test to use time-series collections.
|