jstests/core/wildcard_index_basic_index_bounds.js
// with one predicate on each expected path, and a rooted $and over all predicates and paths.
with one predicate on each expected path, and a rooted $and over all predicates and paths.
with one predicate on each expected path and a rooted $and over all predicates and paths .
function runWildcardIndexTest(keyPattern, pathProjection, expectedPaths) {
    assert.commandWorked(coll.dropIndexes());
    assert.commandWorked(
        coll.createIndex(keyPattern, pathProjection ? {wildcardProjection: pathProjection} : {}));
    assert(expectedPaths);
    for (let op of operationList) {
        const multiFieldPreds = [];
        const orQueryBounds = [];
        for (let path of pathList) {
            const pointPathBound = `["${path}", "${path}"]`;
            const pathBounds =
                op.subpathBounds ? [pointPathBound, `["${path}.", "${path}/")`] : [pointPathBound];
            const expectedBounds = {$_path: pathBounds, [path]: op.bounds};
            const query = {[path]: op.expression};
            const ixScans =
                getPlanStages(getWinningPlan(coll.find(query).explain().queryPlanner), "IXSCAN");
            if (!expectedPaths.includes(path) || op.bounds === null) {
                assert.eq(ixScans.length,
                          0,
                          () => "Bounds check for operation: " + tojson(op) +
                              " failed. Expected no IXSCAN plans to be generated, but got " +
                              tojson(ixScans));
                continue;
            }
            assert.eq(ixScans.length, FixtureHelpers.numberOfShardsForCollection(coll));
            assert.docEq(ixScans[0].keyPattern, {$_path: 1, [path]: 1});
            assert.docEq(ixScans[0].indexBounds, expectedBounds);
            assertResultsEq(coll.find(query).sort({_id: 1}).hint(keyPattern),
                            coll.find(query).sort({_id: 1}).hint({$natural: 1}));
            orQueryBounds.push(expectedBounds);
            multiFieldPreds.push(query);
        }
        if (multiFieldPreds.length === 0) {
            continue;
        }
        const explainedOr = assert.commandWorked(coll.find({$or: multiFieldPreds}).explain());
        const ixScanBounds = getPlanStages(getWinningPlan(explainedOr.queryPlanner), "IXSCAN")
                                 .map(elem => elem.indexBounds);
        assert.eq(ixScanBounds.length, orQueryBounds.length * numShards);
        for (let offset = 0; offset < ixScanBounds.length; offset += orQueryBounds.length) {
            const ixBounds = ixScanBounds.slice(offset, offset + orQueryBounds.length);
            orQueryBounds.forEach(
                exBound => assert(ixBounds.some(ixBound => !bsonWoCompare(ixBound, exBound))));
        }
        assertResultsEq(coll.find({$or: multiFieldPreds}).sort({_id: 1}).hint(keyPattern),
                        coll.find({$or: multiFieldPreds}).sort({_id: 1}).hint({$natural: 1}));
        const explainedAnd = coll.find({$and: multiFieldPreds}).explain();
        const winningIxScan = getPlanStages(getWinningPlan(explainedAnd.queryPlanner), "IXSCAN");
        let rejectedIxScans = [], rejectedAndSorted = [];
        const rejectedPlans = getRejectedPlans(explainedAnd);
        for (let rejectedPlan of rejectedPlans) {
            rejectedPlan = getRejectedPlan(rejectedPlan);
            rejectedAndSorted = rejectedAndSorted.concat(getPlanStages(rejectedPlan, "AND_SORTED"));
            rejectedIxScans = rejectedIxScans.concat(getPlanStages(rejectedPlan, "IXSCAN"));
        }
        assert.eq(rejectedAndSorted.length, 0);
        assert.eq(winningIxScan.length, numShards);
        assert.eq(rejectedIxScans.length, numShards * (expectedPaths.length - 1));
        for (let ixScan of winningIxScan.concat(rejectedIxScans)) {
            const ixScanPath = JSON.parse(ixScan.indexBounds.$_path[0])[0];
            assert.eq(ixScan.indexBounds[ixScanPath], op.bounds);
            assert(expectedPaths.includes(ixScanPath));
        }
        assertResultsEq(coll.find({$and: multiFieldPreds}).sort({_id: 1}).hint(keyPattern),
                        coll.find({$and: multiFieldPreds}).sort({_id: 1}).hint({$natural: 1}));
    }
}
function runWildcardIndexTest ( keyPattern , pathProjection , expectedPaths ) { assert . commandWorked ( coll . dropIndexes ( ) ) ; assert . commandWorked ( coll . createIndex ( keyPattern , pathProjection ? { wildcardProjection : pathProjection } : { } ) ) ; assert ( expectedPaths ) ; for ( let op of operationList ) { const multiFieldPreds = [ ] ; const orQueryBounds = [ ] ; for ( let path of pathList ) { const pointPathBound = ` ${ path } ${ path } ` ; const pathBounds = op . subpathBounds ? [ pointPathBound , ` ${ path } ${ path } ` ] : [ pointPathBound ] ; const expectedBounds = { $_path : pathBounds , [ path ] : op . bounds } ; const query = { [ path ] : op . expression } ; const ixScans = getPlanStages ( getWinningPlan ( coll . find ( query ) . explain ( ) . queryPlanner ) , "IXSCAN" ) ; if ( ! expectedPaths . includes ( path ) || op . bounds === null ) { assert . eq ( ixScans . length , 0 , ( ) => "Bounds check for operation: " + tojson ( op ) + " failed. Expected no IXSCAN plans to be generated, but got " + tojson ( ixScans ) ) ; continue ; } assert . eq ( ixScans . length , FixtureHelpers . numberOfShardsForCollection ( coll ) ) ; assert . docEq ( ixScans [ 0 ] . keyPattern , { $_path : 1 , [ path ] : 1 } ) ; assert . docEq ( ixScans [ 0 ] . indexBounds , expectedBounds ) ; assertResultsEq ( coll . find ( query ) . sort ( { _id : 1 } ) . hint ( keyPattern ) , coll . find ( query ) . sort ( { _id : 1 } ) . hint ( { $natural : 1 } ) ) ; orQueryBounds . push ( expectedBounds ) ; multiFieldPreds . push ( query ) ; } if ( multiFieldPreds . length === 0 ) { continue ; } const explainedOr = assert . commandWorked ( coll . find ( { $or : multiFieldPreds } ) . explain ( ) ) ; const ixScanBounds = getPlanStages ( getWinningPlan ( explainedOr . queryPlanner ) , "IXSCAN" ) . map ( elem => elem . indexBounds ) ; assert . eq ( ixScanBounds . length , orQueryBounds . length * numShards ) ; for ( let offset = 0 ; offset < ixScanBounds . length ; offset += orQueryBounds . length ) { const ixBounds = ixScanBounds . slice ( offset , offset + orQueryBounds . length ) ; orQueryBounds . forEach ( exBound => assert ( ixBounds . some ( ixBound => ! bsonWoCompare ( ixBound , exBound ) ) ) ) ; } assertResultsEq ( coll . find ( { $or : multiFieldPreds } ) . sort ( { _id : 1 } ) . hint ( keyPattern ) , coll . find ( { $or : multiFieldPreds } ) . sort ( { _id : 1 } ) . hint ( { $natural : 1 } ) ) ; const explainedAnd = coll . find ( { $and : multiFieldPreds } ) . explain ( ) ; const winningIxScan = getPlanStages ( getWinningPlan ( explainedAnd . queryPlanner ) , "IXSCAN" ) ; let rejectedIxScans = [ ] , rejectedAndSorted = [ ] ; const rejectedPlans = getRejectedPlans ( explainedAnd ) ; for ( let rejectedPlan of rejectedPlans ) { rejectedPlan = getRejectedPlan ( rejectedPlan ) ; rejectedAndSorted = rejectedAndSorted . concat ( getPlanStages ( rejectedPlan , "AND_SORTED" ) ) ; rejectedIxScans = rejectedIxScans . concat ( getPlanStages ( rejectedPlan , "IXSCAN" ) ) ; } assert . eq ( rejectedAndSorted . length , 0 ) ; assert . eq ( winningIxScan . length , numShards ) ; assert . eq ( rejectedIxScans . length , numShards * ( expectedPaths . length - 1 ) ) ; for ( let ixScan of winningIxScan . concat ( rejectedIxScans ) ) { const ixScanPath = JSON . parse ( ixScan . indexBounds . $_path [ 0 ] ) [ 0 ] ; assert . eq ( ixScan . indexBounds [ ixScanPath ] , op . bounds ) ; assert ( expectedPaths . includes ( ixScanPath ) ) ; } assertResultsEq ( coll . find ( { $and : multiFieldPreds } ) . sort ( { _id : 1 } ) . hint ( keyPattern ) , coll . find ( { $and : multiFieldPreds } ) . sort ( { _id : 1 } ) . hint ( { $natural : 1 } ) ) ; } }
with one predicate on each expected path, and a rooted $and over all predicates and paths.
with one predicate on each expected path and a rooted $and over all predicates and paths .
// The 'expectedPaths' argument is the set of paths which we expect to be indexed, based on // the keyPattern and projection. Make sure that the caller has provided this argument. // Verify the expected behaviour for every combination of path and operator. // Build up a list of operations that will later be used to test rooted $or. // The bounds on '$_path' will always include a point-interval on the path, i.e. // ["path.to.field", "path.to.field"]. If 'subpathBounds' is 'true' for this // operation, then we add bounds that include all subpaths as well, i.e. // ["path.to.field.", "path.to.field/") // {$_path: pathBounds, path.to.field: [[computed bounds]]} // Explain the query, and determine whether an indexed solution is available. // If we expect the current path to have been excluded based on the $** keyPattern // and projection, or if the current operation is not supported by $** indexes, // confirm that no indexed solution was found. // Verify that the winning plan uses the $** index with the expected bounds. // Verify that the results obtained from the $** index are identical to a COLLSCAN. // We must explicitly hint the wildcard index, because we also sort on {_id: 1} to // ensure that both result sets are in the same order. // Push the query into the $or and $and predicate arrays. // If the current operation could not use the $** index, skip to the next op. // Perform a rooted $or for this operation across all indexed fields; for instance: // {$or: [{a: {$eq: 25}}, {'b.c': {$eq: 25}}, {'b.d.e': {$eq: 25}}]}. // Obtain the list of index bounds from each individual IXSCAN stage across all shards. // We should find that each branch of the $or has used a separate $** sub-index. In the // sharded passthroughs, we expect to have 'orQueryBounds' on each shard. // Verify that the results obtained from the $** index are identical to a COLLSCAN. // Perform an $and for this operation across all indexed fields; for instance: // {$and: [{a: {$gte: 50}}, {'b.c': {$gte: 50}}, {'b.d.e': {$gte: 50}}]}. // Extract information about the rejected plans. We should have one IXSCAN for each $** // candidate that wasn't the winner. Before SERVER-36521 banned them for $** indexes, a // number of AND_SORTED plans would also be generated here; we search for these in order // to verify that no such plans now exist. // Confirm that no AND_SORTED plans were generated. // We should find that one of the available $** subindexes has been chosen as the // winner, and all other candidate $** indexes are present in 'rejectedPlans'. // Verify that each of the IXSCANs have the expected bounds and $_path key. // {$_path: ["['path.to.field', 'path.to.field']"], path.to.field: [[bounds]]} // Verify that the results obtained from the $** index are identical to a COLLSCAN.