/**
 *    Copyright (C) 2018-present MongoDB, Inc.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the Server Side Public License, version 1,
 *    as published by MongoDB, Inc.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    Server Side Public License for more details.
 *
 *    You should have received a copy of the Server Side Public License
 *    along with this program. If not, see
 *    <http://www.mongodb.com/licensing/server-side-public-license>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the Server Side Public License in all respects for
 *    all of the code used other than as permitted herein. If you modify file(s)
 *    with this exception, you may extend this exception to your version of the
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 *    delete this exception statement from your version. If you delete this
 *    exception statement from all source files in the program, then also delete
 *    it in the license file.
 */

#include "mongo/platform/basic.h"

#include "mongo/db/index/wildcard_key_generator.h"
#include "mongo/db/query/collation/collator_interface_mock.h"
#include "mongo/db/query/planner_wildcard_helpers.h"
#include "mongo/db/query/query_planner_test_fixture.h"
#include "mongo/unittest/death_test.h"

namespace mongo {

namespace wcp = ::mongo::wildcard_planning;

const std::string kIndexName = "indexName";

/**
 * A specialization of the QueryPlannerTest fixture which makes it easy to present the planner with
 * a view of the available $** indexes.
 */
class QueryPlannerWildcardTest : public QueryPlannerTest {
protected:
    void setUp() final {
        QueryPlannerTest::setUp();

        // We're interested in testing plans that use a $** index, so don't generate collection
        // scans.
        params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN;
    }

    void addWildcardIndex(BSONObj keyPattern,
                          const std::set<std::string>& multikeyPathSet = {},
                          BSONObj wildcardProjection = BSONObj{},
                          MatchExpression* partialFilterExpr = nullptr,
                          CollatorInterface* collator = nullptr,
                          const std::string& indexName = kIndexName) {
        // Convert the set of std::string to a set of FieldRef.
        std::set<FieldRef> multikeyFieldRefs;
        for (auto&& path : multikeyPathSet) {
            ASSERT_TRUE(multikeyFieldRefs.emplace(path).second);
        }
        ASSERT_EQ(multikeyPathSet.size(), multikeyFieldRefs.size());

        const bool isMultikey = !multikeyPathSet.empty();
        BSONObj infoObj = BSON("wildcardProjection" << wildcardProjection);

        _proj = WildcardKeyGenerator::createProjectionExecutor(keyPattern, wildcardProjection);

        params.indices.push_back({std::move(keyPattern),
                                  IndexType::INDEX_WILDCARD,
                                  IndexDescriptor::kLatestIndexVersion,
                                  isMultikey,
                                  {},  // multikeyPaths
                                  std::move(multikeyFieldRefs),
                                  false,  // sparse
                                  false,  // unique
                                  IndexEntry::Identifier{indexName},
                                  partialFilterExpr,
                                  std::move(infoObj),
                                  collator,
                                  _proj.get_ptr()});
    }

    boost::optional<WildcardProjection> _proj;
};

//
// General planning tests.
//

DEATH_TEST_F(QueryPlannerWildcardTest, CannotExpandPreExpandedWildcardIndexEntry, "Invariant") {
    addWildcardIndex(BSON("$**" << 1));
    ASSERT_EQ(params.indices.size(), 2U);

    // Expand the $** index and add the expanded entry to the list of available indices.
    std::vector<IndexEntry> expandedIndex;
    wcp::expandWildcardIndexEntry(params.indices.back(), {"a"}, &expandedIndex);
    ASSERT_EQ(expandedIndex.size(), 1U);
    params.indices.push_back(expandedIndex.front());

    // Now run a query. This will invariant when the planner expands the expanded index.
    runQuery(fromjson("{a: 1}"));
}

//
// Null comparison and existence tests.
//

TEST_F(QueryPlannerWildcardTest, ExistsTrueQueriesUseWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$exists: true}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ExistsFalseQueriesDontUseWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$exists: false}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, EqualsNullQueriesDontUseWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$eq: null}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, NotEqualsNullQueriesUseWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$ne: null}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {x: {$ne: null}}, node: {ixscan: {pattern: {$_path: 1, x: 1},"
        "bounds: {'$_path': [['x','x',true,true], ['x.','x/',true,false]],"
        "x: [['MinKey','MaxKey',true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, NotEqualsNullQueriesDoNotUseWildcardIndexesWhenMultiKey) {
    addWildcardIndex(BSON("$**" << 1), {"x"});

    runQuery(fromjson("{'x': {$ne: null}}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, NotEqualsNullInElemMatchQueriesUseWildcardIndexesWhenMultiKey) {
    addWildcardIndex(BSON("$**" << 1), {"x"});

    runQuery(fromjson("{'x': {$elemMatch: {$ne: null}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {$_path: 1, 'x': 1},"
        "bounds: {'$_path': [['x','x',true,true], ['x.','x/',true,false]],"
        "'x': [['MinKey', 'MaxKey',true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, NotEqualsNullInElemMatchObjectSparseMultiKeyAboveElemMatch) {
    addWildcardIndex(BSON("$**" << 1), {"a", "a.b"});

    runQuery(fromjson("{'a.b': {$elemMatch: {'c.d': {$ne: null}}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {'$_path': 1, 'a.b.c.d': 1},"
        "bounds: {'$_path': [['a.b.c.d','a.b.c.d',true,true], ['a.b.c.d.','a.b.c.d/',true,false]],"
        "'a.b.c.d': [['MinKey', 'MaxKey', true, true]]"
        "}}}}}");
}

TEST_F(QueryPlannerWildcardTest, NotEqualsNullInElemMatchObjectSparseMultiKeyBelowElemMatch) {
    // "a.b.c" being multikey will prevent us from using the index since $elemMatch doesn't do
    // implicit array traversal.
    addWildcardIndex(BSON("$**" << 1), {"a.b.c"});

    runQuery(fromjson("{'a.b': {$elemMatch: {'c.d': {$ne: null}}}}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, NotEqualsNullAndExistsQueriesUseWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$ne: null, $exists: true}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, EqualsNullAndExistsQueriesUseWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$eq: null, $exists: true}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, EmptyBoundsWithWildcardIndexes) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$lte: 5, $gte: 10}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

//
// Multikey planning tests.
//

TEST_F(QueryPlannerWildcardTest, MultiplePredicatesOverMultikeyFieldNoElemMatch) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$gt: 0, $lt: 9}}"));

    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$gt: 0}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[-Infinity,9,true,false]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {a: {$lt: 9}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[0,Infinity,false,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, MultiplePredicatesOverMultikeyFieldWithElemMatch) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$elemMatch: {$gt: 0, $lt: 9}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {$gt: 0, $lt: 9}}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, MultiplePredicatesOverNonMultikeyFieldWithMultikeyIndex) {
    addWildcardIndex(BSON("$**" << 1), {"b"});
    runQuery(fromjson("{a: {$gt: 0, $lt: 9}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, MultiplePredicatesOverNestedFieldWithFirstComponentMultikey) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{'a.b': {$gt: 0, $lt: 9}}"));

    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b': {$gt: 0}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[-Infinity,9,true,false]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.b': {$lt: 9}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[0,Infinity,false,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, MultiplePredicatesOverNestedFieldWithElemMatchObject) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$elemMatch: {b: {$gt: 0, $lt: 9}}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {b: {$gt: 0, $lt: 9}}}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest,
       MultiplePredicatesOverNestedFieldWithElemMatchObjectBothComponentsMultikey) {
    addWildcardIndex(BSON("$**" << 1), {"a", "a.b"});
    runQuery(fromjson("{a: {$elemMatch: {b: {$gt: 0, $lt: 9}}}}"));

    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {b: {$gt: 0, $lt: 9}}}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[-Infinity,9,true,false]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {b: {$gt: 0, $lt: 9}}}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[0,Infinity,false,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, MultiplePredicatesOverNestedFieldWithTwoElemMatches) {
    addWildcardIndex(BSON("$**" << 1), {"a", "a.b"});
    runQuery(fromjson("{a: {$elemMatch: {b: {$elemMatch: {$gt: 0, $lt: 9}}}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {b: {$elemMatch: {$gt: 0, $lt: 9}}}}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ElemMatchOnInnermostMultikeyPathPermitsTightBounds) {
    addWildcardIndex(BSON("$**" << 1), {"a", "a.b", "a.b.c"});
    runQuery(fromjson("{'a.b.c': {$elemMatch: {'d.e.f': {$gt: 0, $lt: 9}}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.c': {$elemMatch: {'d.e.f': {$gt: 0, $lt: 9}}}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b.c.d.e.f': 1},"
        "bounds: {'$_path': [['a.b.c.d.e.f','a.b.c.d.e.f',true,true]],"
        "'a.b.c.d.e.f': [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, AllPredsEligibleForIndexUseGenerateCandidatePlans) {
    addWildcardIndex(BSON("a.$**" << 1), {"a.b", "a.c"});
    runQuery(
        fromjson("{'a.b': {$gt: 0, $lt: 9}, 'a.c': {$gt: 11, $lt: 20}, d: {$gt: 31, $lt: 40}}"));

    assertNumSolutions(4U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b':{$gt:0,$lt: 9},'a.c':{$gt:11},d:{$gt:31,$lt:40}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.c': 1},"
        "bounds: {'$_path': [['a.c','a.c',true,true]], 'a.c': [[-Infinity,20,true,false]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.b':{$gt:0,$lt: 9},'a.c':{$lt:20},d:{$gt:31,$lt:40}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.c': 1},"
        "bounds: {'$_path': [['a.c','a.c',true,true]], 'a.c': [[11,Infinity,false,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.b':{$gt:0},'a.c':{$gt:11,$lt:20},d:{$gt:31,$lt:40}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[-Infinity,9,true,false]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.b':{$lt:9},'a.c':{$gt:11,$lt:20},d:{$gt:31,$lt:40}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[0,Infinity,false,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, RangeIndexScan) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$gt: 0, $lt: 9}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, RangeIndexScanEmptyRange) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$gt: 9, $lt: 0}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], 'a': []}}}}}");
}

TEST_F(QueryPlannerWildcardTest, RangeIndexScanMinKeyMaxKey) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$gt: {$minKey: 1}, $lt: {$maxKey: 1}}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true], ['a.', 'a/', true, false]], 'a': [['MinKey', "
        "'MaxKey', false, false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, RangeIndexScanNestedField) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{'a.b': {$gt: 0, $lt: 9}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[0,9,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, EqualityIndexScan) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$eq: 5}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, EqualityIndexScanOverNestedField) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{'a.b': {$eq: 5}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[5,5,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ExprEqCannotUseIndex) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$_internalExprEq: 1}}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, ExprEqCannotUseSparseIndexForEqualityToNull) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$_internalExprEq: null}}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, PrefixRegex) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: /^foo/}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]],"
        "a: [['foo','fop',true,false], [/^foo/,/^foo/,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, NonPrefixRegex) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: /foo/}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: {a: /foo/}, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]],"
        "a: [['',{},true,false], [/foo/,/foo/,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, GreaterThan) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$gt: 5}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,Infinity,false,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, GreaterThanEqualTo) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$gte: 5}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,Infinity,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, LessThan) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$lt: 5}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[-Infinity,5,true,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, LessThanEqualTo) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$lte: 5}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[-Infinity,5,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, Mod) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$mod: [2, 0]}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: {a: {$mod: [2, 0]}}, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[NaN,Infinity, true, true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ExistsTrue) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{x: {$exists: true}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, x: 1},"
        "bounds: {'$_path': [['x','x',true,true],['x.','x/',true,false]], x: "
        "[['MinKey','MaxKey',true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ExistsFalseDoesNotUseIndex) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{x: {$exists: false}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, AndEqualityWithTwoPredicatesIndexesOnePath) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: 5, b: 10}"));

    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {b: {$eq: 10}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, OrEqualityWithTwoPredicatesUsesTwoPaths) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{$or: [{a: 5}, {b: 10}]}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {or: {nodes: ["
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}, "
        "{ixscan: {filter: null, pattern: {'$_path': 1, b: 1},"
        "bounds: {'$_path': [['b','b',true,true]], b: [[10,10,true,true]]}}}]}}}}");
}

TEST_F(QueryPlannerWildcardTest, OrWithOneRegularAndOneWildcardIndexPathUsesTwoIndexes) {
    addWildcardIndex(BSON("a.$**" << 1));
    addIndex(BSON("b" << 1));
    runQuery(fromjson("{$or: [{a: 5}, {b: 10}]}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {or: {nodes: ["
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}, "
        "{ixscan: {filter: null, pattern: {b: 1},"
        "bounds: {b: [[10,10,true,true]]}}}]}}}}");
}

TEST_F(QueryPlannerWildcardTest, BasicSkip) {
    addWildcardIndex(BSON("$**" << 1));
    runQuerySkipNToReturn(BSON("a" << 5), 8, 0);

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {skip: {n: 8, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, CoveredSkip) {
    addWildcardIndex(BSON("$**" << 1));
    runQuerySortProjSkipNToReturn(fromjson("{a: 5}"), BSONObj(), fromjson("{_id: 0, a: 1}"), 8, 0);

    assertNumSolutions(1U);
    assertSolutionExists(
        "{proj: {spec: {_id: 0, a: 1}, node: {skip: {n: 8, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, BasicLimit) {
    addWildcardIndex(BSON("$**" << 1));
    runQuerySkipNToReturn(BSON("a" << 5), 0, -5);

    assertNumSolutions(1U);
    assertSolutionExists(
        "{limit: {n: 5, node: {fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, BasicCovering) {
    addWildcardIndex(BSON("$**" << 1));
    runQuerySortProj(fromjson("{ x : {$gt: 1}}"), BSONObj(), fromjson("{_id: 0, x: 1}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{proj: {spec: {_id: 0, x: 1}, node: {ixscan: {filter: null, pattern: {'$_path': 1, x: 1},"
        "bounds: {'$_path': [['x','x',true,true]], x: [[1,Infinity,false,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, DottedFieldCovering) {
    addWildcardIndex(BSON("$**" << 1));
    runQuerySortProj(fromjson("{'a.b': 5}"), BSONObj(), fromjson("{_id: 0, 'a.b': 1}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{proj: {spec: {_id: 0, 'a.b': 1}, node: {ixscan: {filter: null, pattern: {'$_path': 1, "
        "'a.b': 1},"
        "bounds: {'$_path': [['a.b','a.b',true,true]], 'a.b': [[5,5,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, CoveredIxscanForCountOnIndexedPath) {
    params.options = QueryPlannerParams::IS_COUNT;
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: 5}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{ixscan: {filter: null, pattern: {'$_path': 1, 'a': 1},"
        "bounds: {'$_path': [['a','a',true,true]], 'a': [[5,5,true,true]]}}}");
}

TEST_F(QueryPlannerWildcardTest, InBasic) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$in: [1, 2]}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, "
        "node: {ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[1,1,true,true],[2,2,true,true]]}}}}}");
}

//
// Array tests.
//

TEST_F(QueryPlannerWildcardTest, EqualsEmptyArray) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: []}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: "
        "[[undefined,undefined,true,true],[[],[],true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, EqualsNonEmptyArray) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: [1]}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, EqualsNestedEmptyArray) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: [[]]}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, EqualsArrayWithValue) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: [[1]]}"));

    assertHasOnlyCollscan();
}

//
// $in tests.
//

TEST_F(QueryPlannerWildcardTest, InEmptyArray) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$in: [[]]}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: "
        "[[undefined,undefined,true,true],[[],[],true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, InNonEmptyArray) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$in: [[1]]}}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, InNestedEmptyArray) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$in: [[[]]]}}"));

    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, InArrayWithValue) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$in: [[[1]]]}}"));

    assertHasOnlyCollscan();
}

// Logically equivalent to the preceding $in query.
// Indexed solution should be the same.
TEST_F(QueryPlannerWildcardTest, InBasicOrEquivalent) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{$or: [{a: 1}, {a: 2}]}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, "
        "node: {ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[1,1,true,true],[2,2,true,true]]}}}}}");
}

//
// Partial index tests.
//

TEST_F(QueryPlannerWildcardTest, PartialIndexCanAnswerPredicateOnFilteredField) {
    auto filterObj = fromjson("{a: {$gt: 0}}");
    auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
    addWildcardIndex(BSON("$**" << 1), {}, BSONObj{}, filterExpr.get());

    runQuery(fromjson("{a: {$gte: 5}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,Infinity,true,true]]}}}}}");

    runQuery(fromjson("{a: 5}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,5,true,true]]}}}}}");

    runQuery(fromjson("{a: {$gte: 1, $lte: 10}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[1,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, PartialIndexDoesNotAnswerPredicatesExcludedByFilter) {
    // Must keep 'filterObj' around since match expressions will store pointers into the BSON they
    // were parsed from.
    auto filterObj = fromjson("{a: {$gt: 0}}");
    auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
    addWildcardIndex(BSON("$**" << 1), {}, BSONObj{}, filterExpr.get());

    runQuery(fromjson("{a: {$gte: -1}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{a: {$lte: 10}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{a: {$eq: 0}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, PartialIndexCanAnswerPredicateOnUnrelatedField) {
    auto filterObj = fromjson("{a: {$gt: 0}}");
    auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
    addWildcardIndex(BSON("$**" << 1), {}, BSONObj{}, filterExpr.get());

    // Test when the field query is not included by the partial filter expression.
    runQuery(fromjson("{b: {$gte: -1}, a: {$gte: 5}}"));
    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$gte: 5}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, b: 1},"
        "bounds: {'$_path': [['b','b',true,true]], b: [[-1,Infinity,true,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {b: {$gte: -1}}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[5,Infinity,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, PartialIndexWithExistsTrueFilterCanAnswerExistenceQuery) {
    auto filterObj = fromjson("{x: {$exists: true}}");
    auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
    addWildcardIndex(BSON("$**" << 1), {}, BSONObj{}, filterExpr.get());
    runQuery(fromjson("{x: {$exists: true}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, x: 1},"
        "bounds: {'$_path': [['x','x',true,true],['x.','x/',true,false]], x: "
        "[['MinKey','MaxKey',true,true]]}}}}}");
}

//
// Index intersection tests.
//

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotParticipateInIndexIntersection) {
    // Enable both AND_SORTED and AND_HASH index intersection for this test.
    params.options |= QueryPlannerParams::INDEX_INTERSECTION;
    internalQueryPlannerEnableHashIntersection.store(true);

    // Add two standard single-field indexes.
    addIndex(BSON("a" << 1));
    addIndex(BSON("b" << 1));

    // Run a point query on both fields and confirm that an AND_SORTED plan is generated.
    runQuery(fromjson("{a:10, b:10}"));
    // Three plans are generated: one IXSCAN for each index, and an AND_SORTED on both.
    assertNumSolutions(3U);
    assertSolutionExists(
        "{fetch: {filter: {a:10}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {b:10}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {a:10, b:10}, node: {andSorted: {nodes: [{ixscan: {filter: null, "
        "pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");

    // Run a range query on both fields and confirm that an AND_HASH plan is generated.
    runQuery(fromjson("{a:{$gt: 10}, b:{$gt: 10}}"));
    // Three plans are generated: one IXSCAN for each index, and an AND_HASH on both.
    assertNumSolutions(3U);
    assertSolutionExists(
        "{fetch: {filter: {a:{$gt: 10}}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {b:{$gt: 10}}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {a:{$gt: 10}, b:{$gt: 10}}, node: {andHash: {nodes: [{ixscan: "
        "{filter: null, pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");

    // Now add a $** index and re-run the tests.
    addWildcardIndex(BSON("$**" << 1));

    // First re-run the AND_SORTED test.
    runQuery(fromjson("{a:10, b:10}"));
    // Solution count has increased from 3 to 5, as $** 'duplicates' the {a:1} and {b:1} IXSCANS.
    assertNumSolutions(5U);
    assertSolutionExists(
        "{fetch: {filter: {a:10}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {b:10}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
    // The previous AND_SORTED solution is still present...
    assertSolutionExists(
        "{fetch: {filter: {a:10, b:10}, node: {andSorted: {nodes: [{ixscan: {filter: null, "
        "pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
    // ... but there are no additional AND_SORTED solutions contributed by the $** index.
    assertSolutionExists(
        "{fetch: {filter: {a:10}, node: {ixscan: {filter: null, pattern: {$_path:1, b:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {b:10}, node: {ixscan: {filter: null, pattern: {$_path:1, a:1}}}}}");

    // Now re-run the AND_HASH test.
    runQuery(fromjson("{a:{$gt: 10}, b:{$gt: 10}}"));
    // Solution count has increased from 3 to 5, as $** 'duplicates' the {a:1} and {b:1} IXSCANS.
    assertNumSolutions(5U);
    assertSolutionExists(
        "{fetch: {filter: {a:{$gt:10}}, node: {ixscan: {filter: null, pattern: {b:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {b:{$gt:10}}, node: {ixscan: {filter: null, pattern: {a:1}}}}}");
    // The previous AND_HASH solution is still present...
    assertSolutionExists(
        "{fetch: {filter: {a:{$gt:10}, b:{$gt:10}}, node: {andHash: {nodes: [{ixscan: "
        "{filter: null, pattern: {a:1}}},{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
    // ... but there are no additional AND_HASH solutions contributed by the $** index.
    assertSolutionExists(
        "{fetch: {filter:{a:{$gt:10}}, node: {ixscan: {filter: null, pattern: {$_path:1, b:1}}}}}");
    assertSolutionExists(
        "{fetch: {filter:{b:{$gt:10}}, node: {ixscan: {filter: null, pattern: {$_path:1, a:1}}}}}");
}

//
// Wildcard and $text index tests.
//

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupplyCandidatePlanForTextSearch) {
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("a" << 1 << "_fts"
                      << "text"
                      << "_ftsx" << 1));

    // Confirm that the wildcard index generates candidate plans for queries which do not include a
    // $text predicate.
    runQuery(fromjson("{a: 10, b: 10}"));
    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {b: 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, a: 1}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {a: 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, b: 1}}}}}");

    // Confirm that the wildcard index does not produce any candidate plans when a query includes a
    // $text predicate, even for non-$text predicates which may be present in the query.
    runQuery(fromjson("{a: 10, b: 10, $text: {$search: 'banana'}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {b: 10}, node: {text: {prefix: {a: 10}, search: 'banana'}}}}");
}

//
// Negation tests.
//

TEST_F(QueryPlannerWildcardTest, WildcardDoesNotSupportNegationPredicate) {
    // Wildcard indexes can't support negation queries because they are sparse, and {a: {$ne: 5}}
    // will match documents which don't have an "a" field.
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$ne: 5}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{a: {$not: {$gt: 3, $lt: 5}}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest,
       WildcardDoesNotSupportNegationPredicateInsideElemMatchMultiKeyPath) {
    // Logically, there's no reason a (sparse) wildcard index could not support a negation inside a
    // "$elemMatch value", but it is not something we've implemented.
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$elemMatch: {$ne: 5}}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{a: {$elemMatch: {$not: {$gt: 3, $lt: 5}}}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, WildcardDoesNotSupportNegationPredicateInsideElemMatch) {
    // Test the case where we use $elemMatch on a path which isn't even multikey. In this case,
    // we'd know up front that the results would be empty, but this is not an optimization we
    // support.
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$elemMatch: {$ne: 5}}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{a: {$elemMatch: {$not: {$gt: 3, $lt: 5}}}}"));
    assertHasOnlyCollscan();
}

//
// Hinting with all paths index tests.
//

TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHint) {
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("x" << 1));

    runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("$**" << 1));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintByName) {
    std::string wildcard = "wildcard";
    CollatorInterface* nullCollator = nullptr;
    addWildcardIndex(BSON("$**" << 1), {}, {}, {}, nullCollator, wildcard);
    addIndex(BSON("x" << 1));

    runQueryHint(fromjson("{x: {$eq: 1}}"),
                 BSON("$hint"
                      << "wildcard"));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintWithPath) {
    addWildcardIndex(BSON("x.$**" << 1));
    addIndex(BSON("x" << 1));

    runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("x.$**" << 1));

    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintWithOr) {
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("x" << 1 << "y" << 1));

    runQueryHint(fromjson("{$or: [{x: 1}, {y: 1}]}"), BSON("$**" << 1));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {or: {nodes: [{ixscan: {pattern: {$_path: 1, x: 1}}},"
        " {ixscan: {pattern: {$_path: 1, y: 1}}}]}}}}");
}

TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintWithCompoundIndex) {
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("x" << 1 << "y" << 1));

    runQueryHint(fromjson("{x: 1, y: 1}"), BSON("$**" << 1));

    assertNumSolutions(2U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, y: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, QueryNotInWildcardIndexHint) {
    addWildcardIndex(BSON("a.$**" << 1));
    addIndex(BSON("x" << 1));

    runInvalidQueryHint(fromjson("{x: {$eq: 1}}"), BSON("a.$**" << 1));
    assertNoSolutions();
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotExist) {
    addIndex(BSON("x" << 1));

    runInvalidQueryHint(fromjson("{x: {$eq: 1}}"), BSON("$**" << 1));
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexHintWithPartialFilter) {
    auto filterObj = fromjson("{a: {$gt: 100}}");
    auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
    addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get());

    runInvalidQueryHint(fromjson("{a: {$eq: 1}}"), BSON("$**" << 1));
    assertNoSolutions();
}

TEST_F(QueryPlannerWildcardTest, MultipleWildcardIndexesHintWithPartialFilter) {
    auto filterObj = fromjson("{a: {$gt: 100}, b: {$gt: 100}}");
    auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj);
    addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get());

    runInvalidQueryHint(fromjson("{a: {$eq: 1}, b: {$eq: 1}}"), BSON("$**" << 1));
    assertNoSolutions();
}

//
// Object comparison tests.
//

TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportObjectEquality) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {abc: 1}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{$or: [{z: {abc: 1}}]}"));
    assertHasOnlyCollscan();

    // We can only use the index for the predicate on 'x'.
    runQuery(fromjson("{x: 5, y: {abc: 1}}"));
    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportObjectInequality) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$lt: {abc: 1}}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$lte: {abc: 1}}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$gte: {abc: 1}}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$gt: {abc: 1}}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {ne: {abc: 1}}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{x: {$lt: [1, 2, 'a string']}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$lte: [1, 2, 'a string']}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$gte: [1, 2, 'a string']}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$gt: [1, 2, 'a string']}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {ne: [1, 2, 'a string']}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{$or: [{z: {$ne: {abc: 1}}}]}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{$and: [{x: 5}, {$or: [{x: 1}, {y: {abc: 1}}]}]}"));
    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

//
// Unsupported values tests.
//

TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportInWithUnsupportedValues) {
    addWildcardIndex(BSON("$**" << 1));

    runQuery(fromjson("{x: {$in: [1, 2, 3, {abc: 1}]}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$in: [1, 2, 3, ['a', 'b', 'c']]}}"));
    assertHasOnlyCollscan();
    runQuery(fromjson("{x: {$in: [1, 2, 3, null]}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexesSupportElemMatchWithNull) {
    addWildcardIndex(BSON("$**" << 1));

    // Simple case.
    runQuery(fromjson("{x: {$elemMatch: {$lt: 5, $gt: 0}}}"));
    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");

    // null inside an $in inside an $elemMatch is supported by the wildcard index, since it means
    // we're searching for an explicit null value.
    runQuery(fromjson("{x: {$elemMatch: {$in: [1, 2, 3, null]}}}"));
    assertNumSolutions(1U);
    assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportElemMatchWithUnsupportedValues) {
    runQuery(fromjson("{x: {$elemMatch: {$eq: ['a', 'b', 'c']}}}"));
    assertHasOnlyCollscan();

    // An object or array inside an $in inside a $elemMatch is not supported by the index.
    runQuery(fromjson("{x: {$elemMatch: {$in: [1, 2, 3, {a: 1}]}}}"));
    assertHasOnlyCollscan();

    runQuery(fromjson("{x: {$elemMatch: {$in: [1, 2, 3, ['a', 'b', 'c']]}}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportElemMatchObject) {
    runQuery(fromjson("{x: {$elemMatch: {a: 1}}}"));
    assertHasOnlyCollscan();
}

//
// Sorting tests.
//

TEST_F(QueryPlannerWildcardTest, WildcardIndexCanProvideNonBlockingSort) {
    addWildcardIndex(BSON("$**" << 1));

    runQuerySortProj(fromjson("{a: 1}"), BSON("a" << 1), BSONObj());
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {'$_path': 1, a: 1}, "
        "bounds: {'$_path': [['a','a',true,true]], a: [[1,1,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest,
       WildcardIndexCanProvideNonBlockingSortWhenFilterIncludesAdditionalFields) {
    addWildcardIndex(BSON("$**" << 1));

    runQuerySortProj(fromjson("{a: {$gte: 3}, b: 1}"), BSON("a" << 1), BSONObj());
    assertNumSolutions(2U);
    // The non-blocking sort solution.
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {'$_path': 1, a: 1}, "
        "bounds: {'$_path': [['a','a',true,true]], a: [[3,Infinity,true,true]]}}}}}");

    // A blocking sort solution (by doing a scan with a filter on 'b').
    assertSolutionExists(
        "{sort: {pattern: {a: 1}, limit: 0, type: 'simple', node:"
        "{fetch: {filter: {a: {$gte: 3}}, node: "
        "{ixscan: {pattern: {'$_path': 1, b: 1},"
        "bounds: {'$_path': [['b','b',true,true]], b: [[1, 1, true, true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexMustUseBlockingSortWithElemMatch) {
    addWildcardIndex(BSON("$**" << 1), {"a"});

    runQuerySortProj(fromjson("{a: {$elemMatch: {$eq: 1}}}"), BSON("a" << 1), BSONObj());
    assertNumSolutions(1U);
    assertSolutionExists(
        "{sort: {pattern: {a: 1}, limit: 0, type: 'simple', node: "
        "{fetch: {filter: {a: {$elemMatch: {$eq: 1}}}, node: "
        "{ixscan: {pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[1, 1, true, true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexMustUseBlockingSortWithCompoundSort) {
    addWildcardIndex(BSON("$**" << 1));

    runQuerySortProj(fromjson("{a: {$lte: 3}}"), BSON("a" << 1 << "b" << 1), BSONObj());
    assertNumSolutions(1U);
    assertSolutionExists(
        "{sort: {pattern: {a: 1, b: 1}, limit: 0, type: 'simple', node: "
        "{fetch: {filter: null, node: "
        "{ixscan: {pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], a: [[-Infinity, 3, true, true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexMustUseBlockingSortWithExistsQueries) {
    addWildcardIndex(BSON("$**" << 1));

    runQuerySortProj(fromjson("{a: {$exists: true}}"), BSON("a" << 1), BSONObj());
    assertNumSolutions(1U);
    assertSolutionExists(
        "{sort: {pattern: {a: 1}, limit: 0, type: 'simple', node: "
        "{fetch: {filter: null, node: "
        "{ixscan: {pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true], ['a.', 'a/', true, false]],"
        "a: [['MinKey', 'MaxKey', true, true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexMustUseBlockingSortWhenFilterNotPresent) {
    // Since there's no filter on the field that we're sorting by, we cannot use an index scan to
    // answer the query as $** indexes are sparse.
    runQuerySortProj(BSONObj(), fromjson("{a: 1}"), BSONObj());
    assertSolutionExists(
        "{sort: {pattern: {a: 1}, limit: 0, type: 'simple', node: "
        "{cscan: {dir: 1}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexMustUseBlockingSortWhenFilterDoesNotIncludeSortKey) {
    addWildcardIndex(BSON("$**" << 1));

    runQuerySortProj(fromjson("{b: 1, c: 1}"), fromjson("{a: 1}"), BSONObj());
    assertNumSolutions(2U);
    assertSolutionExists(
        "{sort: {pattern: {a: 1}, limit: 0, type: 'simple', node: "
        "{fetch: {filter: {c: 1}, node: "
        "{ixscan: {pattern: {'$_path': 1, b: 1},"
        "bounds: {'$_path': [['b','b',true,true]], b: [[1, 1, true, true]]}}}}}}}");
    assertSolutionExists(
        "{sort: {pattern: {a: 1}, limit: 0, type: 'simple', node: "
        "{fetch: {filter: {b: 1}, node: "
        "{ixscan: {pattern: {'$_path': 1, c: 1},"
        "bounds: {'$_path': [['c','c',true,true]], c: [[1, 1, true, true]]}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexMustUseBlockingSortWhenFieldIsNotIncluded) {
    addWildcardIndex(BSON("$**" << 1), {}, BSON("b" << 0));

    runQuerySortProj(fromjson("{b: 1}"), fromjson("{b: 1}"), BSONObj());
    assertNumSolutions(1U);
    assertSolutionExists(
        "{sort: {pattern: {b: 1}, limit: 0, type: 'simple', node: "
        "{cscan: {dir: 1, filter: {b: 1}}}}}");
}

//
// Field name or array index tests.
//

TEST_F(QueryPlannerWildcardTest, CannotAnswerQueryThroughArrayIndexProjection) {
    addWildcardIndex(BSON("$**" << 1), {"a"}, fromjson("{'a.0': 1, 'a.b': 1}"));

    // Queries whose path lies along a projected array index cannot be answered.
    runQuery(fromjson("{'a.0': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");

    runQuery(fromjson("{'a.0.b': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerQueryOnNonProjectedArrayIndex) {
    addWildcardIndex(BSON("$**" << 1), {"a", "c"}, fromjson("{'a.0': 1, b: 1, c: 1}"));

    // Queries on fields that are not projected array indices can be answered, even when such a
    // projection is present in the 'starPathsTempName' spec.
    runQuery(fromjson("{b: 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, b: 1}, "
        "bounds: {'$_path': [['b','b',true,true]], b: [[10,10,true,true]]}}}}}");

    // Queries on indices of arrays that have been projected in their entirety can be answered.
    runQuery(fromjson("{'c.0': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'c.0': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, 'c.0': "
        "1}, bounds: {'$_path': [['c','c',true,true], ['c.0','c.0',true,true]], 'c.0': "
        "[[10,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldGenerateAllPotentialPathBoundsForArrayIndexQueries) {
    addWildcardIndex(BSON("a.$**" << 1), {"a", "a.b"});

    // A numeric path component immediately following a multikey path may represent an array index,
    // a fieldname, or both. The $** index does not record array indices explicitly, so for all such
    // potential array indices in the query path, we generate bounds that both include and exclude
    // the numeric path components.
    runQuery(fromjson("{'a.0': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.0': "
        "1}, bounds: {'$_path': [['a','a',true,true], ['a.0','a.0',true,true]], 'a.0': "
        "[[10,10,true,true]]}}}}}");

    runQuery(fromjson("{'a.0.b': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, "
        "'a.0.b': 1}, bounds: {'$_path': [['a.0.b','a.0.b',true,true], ['a.b','a.b',true,true]], "
        "'a.0.b': [[10,10,true,true]]}}}}}");

    runQuery(fromjson("{'a.0.b.1.c': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b.1.c': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, "
        "'a.0.b.1.c': 1}, bounds: {'$_path': [['a.0.b.1.c','a.0.b.1.c',true,true], "
        "['a.0.b.c','a.0.b.c',true,true], ['a.b.1.c','a.b.1.c',true,true], "
        "['a.b.c','a.b.c',true,true]], 'a.0.b.1.c': [[10,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldAddAllPredicatesToFetchFilterForArrayIndexQueries) {
    addWildcardIndex(BSON("$**" << 1), {"a", "a.b"});

    // Ordinarily, a query such as {'a': {$gt: 0, $lt: 5}} on a multikey field 'a' produces an EXACT
    // IXSCAN on one of the predicates and a FETCH filter on the other. For fieldname-or-array-index
    // queries on 'a.0' or similar, however, *all* predicates must be added to the FETCH filter to
    // ensure correctness.
    runQuery(fromjson("{'a.0': {$gt: 10, $lt: 20}}"));
    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0': {$gt: 10, $lt: 20}}, node: {ixscan: {filter: null, pattern: "
        "{'$_path': 1, 'a.0': 1}, bounds: {'$_path': [['a','a',true,true], "
        "['a.0','a.0',true,true]], 'a.0': [[-inf,20,true,false]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.0': {$gt: 10, $lt: 20}}, node: {ixscan: {filter: null, pattern: "
        "{'$_path': 1, 'a.0': 1}, bounds: {'$_path': [['a','a',true,true], "
        "['a.0','a.0',true,true]], 'a.0': [[10,inf,false,true]]}}}}}");

    runQuery(fromjson("{'a.0.b': {$gte: 10, $lte: 20, $ne: 15}}"));
    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b': {$gte: 10, $lte: 20, $ne: 15}}, node: {ixscan: {filter: null, "
        "pattern: {'$_path': 1, 'a.0.b': 1}, bounds: {'$_path': [['a.0.b','a.0.b',true,true], "
        "['a.b','a.b',true,true]], 'a.0.b': [[-inf,20.0,true,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b': {$gte: 10, $lte: 20, $ne: 15}}, node: {ixscan: {filter: null, "
        "pattern: {'$_path': 1, 'a.0.b': 1}, bounds: {'$_path': [['a.0.b','a.0.b',true,true], "
        "['a.b','a.b',true,true]], 'a.0.b': [[10,inf,true,true]]}}}}}");

    runQuery(fromjson("{'a.0.b.1.c': {$in: [10, 20, 30]}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b.1.c': {$in: [10, 20, 30]}}, node: {ixscan: {filter: null, "
        "pattern: {'$_path': 1, 'a.0.b.1.c': 1}, bounds: {'$_path': "
        "[['a.0.b.1.c','a.0.b.1.c',true,true], ['a.0.b.c','a.0.b.c',true,true], "
        "['a.b.1.c','a.b.1.c',true,true], ['a.b.c','a.b.c',true,true]], 'a.0.b.1.c': "
        "[[10,10,true,true], [20,20,true,true], [30,30,true,true]]}}}}}");

    runQuery(fromjson("{'a.0.b.1.c': {$gte: 10, $lte: 30}, 'a.1.b.0.c': {$in: [10, 20, 30]}}"));
    assertNumSolutions(3U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b.1.c': {$gte: 10, $lte: 30}, 'a.1.b.0.c': {$in: [10, 20, 30]}}, "
        "node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.0.b.1.c': 1}, bounds: {'$_path': "
        "[['a.0.b.1.c','a.0.b.1.c',true,true], ['a.0.b.c','a.0.b.c',true,true], "
        "['a.b.1.c','a.b.1.c',true,true], ['a.b.c','a.b.c',true,true]], 'a.0.b.1.c': "
        "[[-inf,30,true,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b.1.c': {$gte: 10, $lte: 30}, 'a.1.b.0.c': {$in: [10, 20, 30]}}, "
        "node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.0.b.1.c': 1}, bounds: {'$_path': "
        "[['a.0.b.1.c','a.0.b.1.c',true,true], ['a.0.b.c','a.0.b.c',true,true], "
        "['a.b.1.c','a.b.1.c',true,true], ['a.b.c','a.b.c',true,true]], 'a.0.b.1.c': "
        "[[10,inf,true,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b.1.c': {$gte: 10, $lte: 30}, 'a.1.b.0.c': {$in: [10, 20, 30]}}, "
        "node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.1.b.0.c': 1}, bounds: {'$_path': "
        "[['a.1.b.0.c','a.1.b.0.c',true,true], ['a.1.b.c','a.1.b.c',true,true], "
        "['a.b.0.c','a.b.0.c',true,true], ['a.b.c','a.b.c',true,true]], 'a.1.b.0.c': "
        "[[10,10,true,true], [20,20,true,true], [30,30,true,true]]}}}}}");

    // Finally, confirm that queries which include a numeric path component on a field that is *not*
    // multikey produce EXACT bounds and do *not* add their predicates to the filter.
    runQuery(fromjson("{'d.0': {$gt: 10, $lt: 20}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, 'd.0': 1}, "
        "bounds: {'$_path': [['d.0','d.0',true,true]], 'd.0': [[10,20,false,false]]}}}}}");

    // Here, in contrast to the multikey case, we generate one indexed solution for each predicate
    // and only filter the *other* predicate in the FETCH stage.
    runQuery(fromjson("{'d.0.e': {$gt: 10, $lt: 20}, 'd.1.f': {$in: [10, 20, 30]}}"));
    assertNumSolutions(2U);
    assertSolutionExists(
        "{fetch: {filter: {'d.0.e': {$gt: 10, $lt: 20}}, node: {ixscan: {filter: null, pattern: "
        "{'$_path': 1, 'd.1.f': 1}, bounds: {'$_path': [['d.1.f','d.1.f',true,true]], 'd.1.f': "
        "[[10,10,true,true], [20,20,true,true], [30,30,true,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {'d.1.f': {$in: [10, 20, 30]}}, node: {ixscan: {filter: null, pattern: "
        "{'$_path': 1, 'd.0.e': 1}, bounds: {'$_path': [['d.0.e','d.0.e',true,true]], 'd.0.e': "
        "[[10,20,false,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldOnlyBuildSpecialBoundsForMultikeyPaths) {
    addWildcardIndex(BSON("a.$**" << 1), {"a.b", "a.b.c.d"});

    // 'a' is not multikey, so 'a.0' refers specifically to a fieldname rather than an array index,
    // and special bounds will not be generated.
    runQuery(fromjson("{'a.0': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.0': 1}, "
        "bounds: {'$_path': [['a.0','a.0',true,true]], 'a.0': [[10,10,true,true]]}}}}}");

    // 'a' is not multikey, so 'a.0.b' does not refer to multikey path 'a.b' and 'b.1' is therefore
    // also strictly a fieldname. Special bounds will not be generated for either.
    runQuery(fromjson("{'a.0.b.1': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.0.b.1': "
        "1}, bounds: {'$_path': [['a.0.b.1','a.0.b.1',true,true]], 'a.0.b.1': "
        "[[10,10,true,true]]}}}}}");

    // 'a.b' is multikey but 'a.b.c' is not, so special bounds will only be generated for 'b.1'.
    runQuery(fromjson("{'a.b.1.c.2.d': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.1.c.2.d': 10}, node: {ixscan: {filter: null, pattern: {'$_path': "
        "1, 'a.b.1.c.2.d': 1}, bounds: {'$_path': [['a.b.1.c.2.d','a.b.1.c.2.d',true,true], "
        "['a.b.c.2.d','a.b.c.2.d',true,true]], 'a.b.1.c.2.d': [[10,10,true,true]]}}}}}");

    // 'a.b' is multikey but 'a.b.c' is not. 'a.b.c.2.d' therefore refers to a different path than
    // multikey path 'a.[b].c.[d]', so special bounds will be generated for 'b.1' but not for 'd.3'.
    runQuery(fromjson("{'a.b.1.c.2.d.3.e': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.1.c.2.d.3.e': 10}, node: {ixscan: {filter: null, pattern: "
        "{'$_path': 1, 'a.b.1.c.2.d.3.e': 1}, bounds: "
        "{'$_path':[['a.b.1.c.2.d.3.e','a.b.1.c.2.d.3.e',true,true], "
        "['a.b.c.2.d.3.e','a.b.c.2.d.3.e',true,true]], 'a.b.1.c.2.d.3.e':[[10,10,true,true]]}}}}}");

    // 'a.b' and 'a.b.c.d' are both multikey. Special bounds will be generated for 'b.1' and 'd.3'.
    runQuery(fromjson("{'a.b.1.c.d.3.e': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.1.c.d.3.e': 10}, node: {ixscan: {filter: null, pattern: {'$_path': "
        "1, 'a.b.1.c.d.3.e': 1}, bounds: {'$_path':[['a.b.1.c.d.3.e','a.b.1.c.d.3.e',true,true], "
        "['a.b.1.c.d.e','a.b.1.c.d.e',true,true], ['a.b.c.d.3.e','a.b.c.d.3.e',true,true], "
        "['a.b.c.d.e','a.b.c.d.e',true,true]], 'a.b.1.c.d.3.e':[[10,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldGenerateSpecialBoundsForNumericMultikeyPaths) {
    addWildcardIndex(BSON("a.$**" << 1), {"a.b", "a.b.0", "a.b.0.c"});

    // 'a.b.0' is itself a multikey path, but since 'a.b' is multikey 'b.0' may refer to an array
    // element of 'b'. We generate special bounds for 'b.0'.
    runQuery(fromjson("{'a.b.0': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.0': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, "
        "'a.b.0': 1}, bounds: {'$_path': [['a.b','a.b',true,true], ['a.b.0','a.b.0',true,true]], "
        "'a.b.0': [[10,10,true,true]]}}}}}");

    // 'a.b' and 'a.b.0.c' are both multikey paths; we generate special bounds for 'b.0' and 'c.1'.
    runQuery(fromjson("{'a.b.0.c.1.d': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.0.c.1.d': 10}, node: {ixscan: {filter: null, pattern: {'$_path': "
        "1, 'a.b.0.c.1.d': 1}, bounds: {'$_path': [['a.b.0.c.1.d','a.b.0.c.1.d',true,true], "
        "['a.b.0.c.d','a.b.0.c.d',true,true], ['a.b.c.1.d','a.b.c.1.d',true,true], "
        "['a.b.c.d','a.b.c.d',true,true]], 'a.b.0.c.1.d': [[10,10,true,true]]}}}}}");

    // 'a.b' is multikey but 'a.b.c' is not, so we only generate special bounds for 'a.b.1'.
    runQuery(fromjson("{'a.b.1.c.1': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.b.1.c.1': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, "
        "'a.b.1.c.1': 1}, bounds: {'$_path': [['a.b.1.c.1','a.b.1.c.1',true,true], "
        "['a.b.c.1','a.b.c.1',true,true]], 'a.b.1.c.1': [[10,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldNotGenerateSpecialBoundsForFieldNamesWithLeadingZeroes) {
    addWildcardIndex(BSON("a.$**" << 1), {"a.b"});

    // 'a.b' is multikey, but '01' is not eligible for consideration as a numeric array index; it is
    // always strictly a field name. We therefore do not generate special bounds.
    runQuery(fromjson("{'a.b.01': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, 'a.b.01': 1}, "
        "bounds: {'$_path': [['a.b.01','a.b.01',true,true]], 'a.b.01': [[10,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, InitialNumericPathComponentIsAlwaysFieldName) {
    addWildcardIndex(BSON("$**" << 1), {"0"}, fromjson("{'0': 1}"));

    // '0' is multikey, but the first component in a path can never be an array index since it must
    // always be a field name. We therefore do not generate special bounds for '0.b'.
    runQuery(fromjson("{'0.b': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {'$_path': 1, '0.b': 1}, "
        "bounds: {'$_path': [['0.b','0.b',true,true]], '0.b': [[10,10,true,true]]}}}}}");

    // '0' is multikey, so a query on '0.1.b' will generate special bounds for '1' but not for '0'.
    runQuery(fromjson("{'0.1.b': 10}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'0.1.b': 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, "
        "'0.1.b': 1}, bounds: {'$_path': [['0.1.b','0.1.b',true,true], ['0.b','0.b',true,true]], "
        "'0.1.b': [[10,10,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldGenerateSpecialBoundsForNullAndExistenceQueries) {
    addWildcardIndex(BSON("a.$**" << 1),
                     {"a", "a.b", "a.b.2", "a.b.2.3", "a.b.2.3.4", "a.c.b", "a.c.b.1"});

    runQuery(fromjson("{'a.0.b': {$exists: true}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b': {$exists: true}}, node: {ixscan: {filter: null, pattern: "
        "{'$_path': 1, 'a.0.b': 1}, bounds: {'$_path': [['a.0.b','a.0.b',true,true], "
        "['a.0.b.','a.0.b/',true,false], ['a.b','a.b',true,true], ['a.b.','a.b/',true,false]], "
        "'a.0.b': [[{$minKey: 1},{$maxKey: 1},true,true]]}}}}}");

    runQuery(fromjson("{'a.0.b.1.c': {$exists: true, $eq: null}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.b.1.c': {$exists: true, $eq: null}}, node: {ixscan: {filter:null, "
        "pattern:{'$_path': 1, 'a.0.b.1.c': 1}, bounds: {'$_path': "
        "[['a.0.b.1.c','a.0.b.1.c',true,true], ['a.0.b.1.c.','a.0.b.1.c/',true,false], "
        "['a.0.b.c','a.0.b.c',true,true], ['a.0.b.c.','a.0.b.c/',true,false], "
        "['a.b.1.c','a.b.1.c',true,true], ['a.b.1.c.','a.b.1.c/',true,false], "
        "['a.b.c','a.b.c',true,true], ['a.b.c.','a.b.c/',true,false]], 'a.0.b.1.c': [[{$minKey: "
        "1},{$maxKey: 1},true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest,
       CanAnswerMultipleNumericPathComponentsWhenPrefixPathIsNotMultikey) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{'a.0.1': 'foo'}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, 'a.0.1': 1}, bounds:"
        "{$_path: [['a.0.1','a.0.1',true,true]], 'a.0.1': [['foo','foo',true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest,
       CannotAnswerMultipleNumericPathComponentsWhenPrefixPathIsMultikey) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{'a.0.1': 'foo'}"));

    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest,
       CannotAnswerMultipleNumericPathComponentsWhenPrefixMultikeyPathIsNumeric) {
    addWildcardIndex(BSON("$**" << 1), {"a.0"});
    runQuery(fromjson("{'a.0.1.2': 'foo'}"));

    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, CannotAnswerExistencePredicateOnMultipleNumericPathComponents) {
    addWildcardIndex(BSON("a.$**" << 1), {"a"});

    // This query cannot be serviced by the wildcard index, because there are multiple successive
    // positional path components.
    runQuery(fromjson("{'a.0.0.0': {$exists: true}}"));
    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, CannotAnswerNullEqualityPredicateOnMultipleNumericPathComponents) {
    addWildcardIndex(BSON("a.$**" << 1),
                     {"a", "a.b", "a.b.2", "a.b.2.3", "a.b.2.3.4", "a.c.b", "a.c.b.1"});

    runQuery(fromjson("{'a.0.b.2.3.4': {$exists: true, $eq: null}}"));
    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest,
       CannotAnswerEqualityPredicateWithMultipleNumericPathComponentsInMiddleOfPath) {
    addWildcardIndex(BSON("a.$**" << 1), {"a"});

    runQuery(fromjson("{'a.10.11.b.2': {$eq: 'foo'}}"));
    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, DoNotProduceOverlappingBoundsWithMultipleNumericPathComponents) {
    addWildcardIndex(BSON("a.$**" << 1), {"a.0"});
    // When an array index field exists in the query pattern and one of the resulting
    // fieldname-or-array-index paths is a prefix of another, then the subpath bounds generated by
    // the prefix path will contain all the bounds generated by its subpaths. Test that we avoid
    // overlap by removing the redundant paths.
    //
    // In the below example, 'a.0' is multikey and the query is on 'a.0.0'. We generate paths
    // 'a.0' and 'a.0.0' because the first '0' is an array index. But the subpaths bound
    // generated by 'a.0' -> ['a.0.','a.0/'] would contain all the bounds generated by
    // 'a.0.0'. Therefore we must remove path 'a.0.0' before generating the subpath bounds.
    runQuery(fromjson("{'a.0.0': {$exists: true}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.0': {$exists: true}}, node: {ixscan: {filter:null, "
        "pattern:{'$_path': 1, 'a.0.0': 1}, bounds: {'$_path': [['a.0','a.0',true,true], "
        "['a.0.','a.0/',true,false]], 'a.0.0': [[{$minKey: 1},{$maxKey: 1},true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest,
       DoNotProduceOverlappingBoundsWithMultipleNumericPathComponentsInMiddleOfPath) {
    addWildcardIndex(BSON("a.$**" << 1), {"a.0", "a.0.b"});
    runQuery(fromjson("{'a.0.0.b.1': {$exists: true}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {'a.0.0.b.1': {$exists: true}}, node: {ixscan: {filter:null, "
        "pattern:{'$_path': 1, 'a.0.0.b.1': 1}, bounds: {'$_path': "
        "[['a.0.0.b','a.0.0.b',true,true], "
        "['a.0.0.b.','a.0.0.b/',true,false], ['a.0.b','a.0.b',true,true], "
        "['a.0.b.','a.0.b/',true,false]],"
        "'a.0.0.b.1': [[{$minKey: 1},{$maxKey: 1},true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ShouldDeclineToAnswerQueriesThatTraverseTooManyArrays) {
    addWildcardIndex(BSON("$**" << 1),
                     {"a",
                      "a.b",
                      "a.b.c",
                      "a.b.c.d",
                      "a.b.c.d.e",
                      "a.b.c.d.e.f",
                      "a.b.c.d.e.f.g",
                      "a.b.c.d.e.f.g.h",
                      "a.b.c.d.e.f.g.h.i",
                      "a.b.c.d.e.f.g.h.i.j",
                      "a.b.c.d.e.f.g.h.i.j.k"});

    // We can query through 8 levels of arrays via explicit array indices...
    runQuery(fromjson("{'a.1.b.2.c.3.d.4.e.5.f.6.g.7.h.8': 1}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {node: {ixscan: {pattern: {$_path: 1, 'a.1.b.2.c.3.d.4.e.5.f.6.g.7.h.8': 1}}}}}");

    // ... but after that, we decline to answer the query using the $** index.
    runQuery(fromjson("{'a.1.b.2.c.3.d.4.e.5.f.6.g.7.h.8.i.9': 1}"));
    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");

    // However, we continue to allow implicit (non-indices) traversal of arrays to any depth.
    runQuery(fromjson("{'a.1.b.2.c.3.d.4.e.5.f.6.g.7.h.8.i.j.k': 1}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch:{node:{ixscan:{pattern:{$_path:1, 'a.1.b.2.c.3.d.4.e.5.f.6.g.7.h.8.i.j.k': 1}}}}}");
}

//
// Min/max with wildcard index.
//

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMin) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(BSONObj(), BSON("$**" << 1), fromjson("{x: 1}"), BSONObj());
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMax) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(BSONObj(), BSON("$**" << 1), BSONObj(), fromjson("{x: 10}"));
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinMax) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(BSONObj(), BSON("$**" << 1), fromjson("{x: 1}"), fromjson("{x: 10}"));
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportCompoundMinMax) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(
        BSONObj(), BSON("$**" << 1), fromjson("{x: 1, y: 1}"), fromjson("{x: 10, y:10}"));
}

// Test with a query argument to check that the expanded indices are not used.

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinWithFilter) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(
        fromjson("{x: {$eq: 5}}"), BSON("$**" << 1), fromjson("{x: 1}"), BSONObj());
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMaxWithFilter) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(
        fromjson("{x: {$eq: 5}}"), BSON("$**" << 1), BSONObj(), fromjson("{x: 10}"));
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinMaxWithFilter) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(
        fromjson("{x: {$eq: 5}}"), BSON("$**" << 1), fromjson("{x: 1}"), fromjson("{x: 10}"));
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportCompoundMinMaxWithFilter) {
    addWildcardIndex(BSON("$**" << 1));

    runInvalidQueryHintMinMax(fromjson("{x: 5, y: 5}"),
                              BSON("$**" << 1),
                              fromjson("{x: 1, y: 1}"),
                              fromjson("{x: 10, y:10}"));
}

TEST_F(QueryPlannerWildcardTest, PathSpecifiedWildcardIndexDoesNotSupportMinMax) {
    addWildcardIndex(BSON("x.$**" << 1));

    runInvalidQueryHintMinMax(
        fromjson("{x: {$eq: 5}}"), BSON("x.$**" << 1), fromjson("{x: 1}"), fromjson("{x: 10}"));
}

TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotAffectValidMinMax) {
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("x" << 1));

    runQueryHintMinMax(fromjson("{x: {$eq: 5}}"),
                       BSONObj(fromjson("{x: 1}")),
                       fromjson("{x: 1}"),
                       fromjson("{x: 10}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {x: {$eq: 5}}, "
        "node: {ixscan: {filter: null, pattern: {x: 1}}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerEqualityToEmptyObject) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, a: 1}},"
        "bounds: {$_path: [['a', 'a', true, true]], a: [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerLTEToEmptyObject) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$lte: {}}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, a: 1}},"
        "bounds: {$_path: [['a', 'a', true, true]], a: [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CannotAnswerInequalityToEmptyObject) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$gte: {}}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, CannotAnswerNotEqualToEmptyObject) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$ne: {}}}"));
    assertHasOnlyCollscan();
}

TEST_F(QueryPlannerWildcardTest, CanAnswerEqualityToEmptyObjectWhenPathIsMultikey) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$eq: {}}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, a: 1}},"
        "bounds: {$_path: [['a', 'a', true, true]], a: [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerEqualityToEmptyObjectWithProjectionInKeyPattern) {
    addWildcardIndex(BSON("a.b.$**" << 1));
    runQuery(fromjson("{'a.b.c': {$eq: {}}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, 'a.b.c': 1}},"
        "bounds: {$_path: [['a.b.c', 'a.b.c', true, true]], 'a.b.c': [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerEqualityToEmptyObjectWithWildcardProjection) {
    addWildcardIndex(BSON("$**" << 1), {}, BSON("a.b" << 1 << "c.d" << 1));
    runQuery(fromjson("{'a.b.c': {$eq: {}}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, 'a.b.c': 1}},"
        "bounds: {$_path: [['a.b.c', 'a.b.c', true, true]], 'a.b.c': [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerEqualityToEmptyObjectWithinElemMatchObject) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$elemMatch: {b: {}}}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {b: {$eq: {}}}}},"
        "node: {ixscan: {filter: null, pattern: {$_path: 1, 'a.b': 1}},"
        "bounds: {$_path: [['a.b', 'a.b', true, true]], 'a.b': [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerEqualityToEmptyObjectWithinElemMatchValue) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$elemMatch: {$eq: {}}}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$elemMatch: {$eq: {}}}},"
        "node: {ixscan: {filter: null, pattern: {$_path: 1, a: 1}},"
        "bounds: {$_path: [['a', 'a', true, true]], a: [[{}, {}, true, true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerInContainingEmptyObject) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$in: [2, {}]}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, a: 1}},"
        "bounds: {$_path: [['a','a',true,true]], a: [[2,2,true,true],[{},{},true,true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanAnswerInContainingEmptyObjectWhenPathIsMultikey) {
    addWildcardIndex(BSON("$**" << 1), {"a"});
    runQuery(fromjson("{a: {$in: [2, {}]}}"));
    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {ixscan: {filter: null, pattern: {$_path: 1, a: 1}},"
        "bounds: {$_path: [['a','a',true,true]], a: [[2,2,true,true],[{},{},true,true]]}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanProduceSortMergePlanWithWildcardIndex) {
    auto defaultMaxOr = internalQueryEnumerationMaxOrSolutions.load();
    ON_BLOCK_EXIT([&] { internalQueryEnumerationMaxOrSolutions.store(defaultMaxOr); });
    internalQueryEnumerationMaxOrSolutions.store(5);
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("a" << 1 << "b" << 1));
    runQueryAsCommand(
        fromjson("{find: 'testns', filter: {$or: [{a: 1, b: 1}, {b: 2}]}, sort: {b: -1}}"));
    assertNumSolutions(3U);
    assertSolutionExists(
        "{fetch: {filter: null, node: {mergeSort: {nodes: ["
        "{fetch: {filter: {a: 1}, node: {ixscan: {pattern: {$_path: 1, b: 1}, bounds: "
        "{$_path: [['b','b',true,true]], b: [[1,1,true,true]]}}}}},"
        "{ixscan: {pattern: {$_path: 1, b: 1}, bounds:"
        "{$_path: [['b','b',true,true]], b: [[2,2,true,true]]}}}]}}}}");
    assertSolutionExists(
        "{fetch: {filter: null, node: {mergeSort: {nodes: ["
        "{ixscan: {pattern: {a: 1, b: 1}, bounds: "
        "{a: [[1,1,true,true]], b: [[1,1,true,true]]}}},"
        "{ixscan: {pattern: {$_path: 1, b: 1}, bounds:"
        "{$_path: [['b','b',true,true]], b: [[2,2,true,true]]}}}]}}}}");
    assertSolutionExists(
        "{fetch: {filter: null, node: {sort: {pattern: {b: -1}, type: 'default', limit: 0, "
        "node: {or: {nodes: ["
        "{fetch: {filter: {b: 1}, node: {ixscan: {pattern: {$_path: 1, a: 1}, bounds:"
        "{$_path: [['a','a',true,true]], a: [[1,1,true,true]]}}}}},"
        "{ixscan: {filter: null, pattern: {$_path: 1, b: 1}, bounds:"
        "{$_path: [['b','b',true,true]], b: [[2,2,true,true]]}}}]}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, ContainedOrPushdownWorksWithWildcardIndex) {
    auto defaultMaxOr = internalQueryEnumerationMaxOrSolutions.load();
    ON_BLOCK_EXIT([&] { internalQueryEnumerationMaxOrSolutions.store(defaultMaxOr); });
    internalQueryEnumerationMaxOrSolutions.store(5);
    addWildcardIndex(BSON("$**" << 1));
    addIndex(BSON("a" << 1 << "b" << 1));
    runQuery(fromjson("{a: 1, $or: [{c: 2}, {b: 3}]}"));
    assertNumSolutions(4U);
    assertSolutionExists(
        "{fetch: {filter: {a: 1}, node: {or: {nodes: ["
        "{ixscan: {pattern: {a: 1, b: 1}, filter: null, bounds:"
        "{a: [[1,1,true,true]], b: [[3,3,true,true]]}}},"
        "{ixscan: {pattern: {$_path: 1, c: 1}, filter: null, bounds:"
        "{$_path: [['c','c',true,true]], c: [[2,2,true,true]]}}}]}}}}");
    assertSolutionExists(
        "{fetch: {filter: {a: 1}, node: {or: {nodes: ["
        "{ixscan: {pattern: {$_path: 1, c: 1}, filter: null, bounds:"
        "{$_path: [['c','c',true,true]], c: [[2,2,true,true]]}}},"
        "{ixscan: {pattern: {$_path: 1, b: 1}, filter: null, bounds:"
        "{$_path: [['b','b',true,true]], b: [[3,3,true,true]]}}}]}}}}");
    assertSolutionExists(
        "{fetch: {filter: {$or: [{c: 2}, {b: 3}]}, node:"
        "{ixscan: {filter: null, pattern: {a: 1, b: 1}, bounds:"
        "{a: [[1,1,true,true]], b:[['MinKey','MaxKey',true,true]]}}}}}");
    assertSolutionExists(
        "{fetch: {filter: {$or: [{c: 2}, {b: 3}]}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, a: 1}, bounds:"
        "{$_path: [['a','a',true,true]], a:[[1,1,true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, TypeOfObjectWithWildcardIndex) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$type: 'object'}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$type: [3]}}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, a: 1}, bounds:"
        "{$_path: [['a','a',true,true], ['a.','a/', true, false]], "
        "a:[['MinKey','MaxKey',true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, TypeOfArrayWithWildcardIndex) {
    addWildcardIndex(BSON("$**" << 1));
    runQuery(fromjson("{a: {$type: 'array'}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: {a: {$type: [4]}}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, a: 1}, bounds:"
        "{$_path: [['a','a',true,true], ['a.','a/', true, false]], "
        "a:[['MinKey','MaxKey',true,true]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest,
       StringComparisonWithUnequalCollatorsAndWildcardIndexResultsInCollscan) {
    CollatorInterfaceMock alwaysEqualCollator(CollatorInterfaceMock::MockType::kAlwaysEqual);
    addWildcardIndex(fromjson("{'$**': 1}"), {}, {}, {}, &alwaysEqualCollator);

    runQueryAsCommand(
        fromjson("{find: 'testns', filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}}"));

    assertNumSolutions(1U);
    assertSolutionExists("{cscan: {dir: 1}}");
}

TEST_F(QueryPlannerWildcardTest, StringComparisonWithEqualCollatorsAndWildcardIndexUsesIndex) {
    params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN;

    CollatorInterfaceMock reverseStringCollator(CollatorInterfaceMock::MockType::kReverseString);
    addWildcardIndex(fromjson("{'$**': 1}"), {}, {}, {}, &reverseStringCollator);

    runQueryAsCommand(
        fromjson("{find: 'testns', filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}}"));

    assertNumSolutions(1U);
    assertSolutionExists(
        "{fetch: {filter: null, collation: {locale: 'reverse'}, node: "
        "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1},"
        "bounds: {'$_path': [['a','a',true,true]], 'a': [['','oof',true,false]]}}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanUseWildcardIndexAndPushProjectionBeneathSort) {
    addWildcardIndex(BSON("$**" << 1));
    runQueryAsCommand(fromjson(
        "{find: 'testns', filter: {a: 1, b: {$gt: 0}}, projection: {_id: 0, b: 1}, sort: {b: 1}}"));

    assertNumSolutions(2U);
    assertSolutionExists(
        "{proj: {spec: {_id: 0, b: 1}, node: {fetch: {filter: {a: 1}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, b: 1}, bounds: "
        "{$_path: [['b','b',true,true]], b: [[0,Infinity,false,true]]}}}}}}}");
    assertSolutionExists(
        "{sort: {pattern: {b: 1}, limit: 0, type: 'simple', node:"
        "{proj: {spec: {_id: 0, b: 1}, node: {fetch: {filter: {b: {$gt: 0}}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, a: 1}, bounds:"
        "{$_path: [['a','a',true,true]], a: [[1,1,true,true]]}}}}}}}}}");
}

TEST_F(QueryPlannerWildcardTest, CanPushProjectionBeneathSortWithExistsPredicate) {
    addWildcardIndex(BSON("$**" << 1));
    runQueryAsCommand(
        fromjson("{find: 'testns', filter: {a: 1, b: {$exists: true}}, projection: {_id: 0, b: 1}, "
                 "sort: {b: 1}}"));

    assertNumSolutions(2U);
    assertSolutionExists(
        "{sort: {pattern: {b: 1}, limit: 0,  type: 'simple', node:"
        "{proj: {spec: {_id: 0, b: 1}, node: {fetch: {filter: {a: {$eq: 1}}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, b: 1}, bounds:"
        "{$_path: [['b','b',true,true], ['b.', 'b/', true, false]],"
        "b: [['MinKey','MaxKey',true,true]]}}}}}}}}}}}");
    assertSolutionExists(
        "{sort: {pattern: {b: 1}, limit: 0, type: 'simple', node:"
        "{proj: {spec: {_id: 0, b: 1}, node: {fetch: {filter: {b: {$exists: true}}, node:"
        "{ixscan: {filter: null, pattern: {$_path: 1, a: 1}, bounds:"
        "{$_path: [['a','a',true,true]], a: [[1,1,true,true]]}}}}}}}}}");
}

}  // namespace mongo
