// Test $or with predicates that generate inexact bounds. The access planner
// has special logic for such queries.
var t = db.jstests_or_inexact;
var cursor;

// A predicate which uses an index falls into one of three categories:
//
// 1) EXACT
//    The predicate can be fully evaluated by the index bounds.
// 2) INEXACT_COVERED
//    The predicate cannot be fully evaluated by the index bounds. However,
//    there is enough information in the index key to evaluate. Such predicates
//    are answered by an index scan with an additional filter on the index key.
// 3) INEXACT_FETCH
//    We must fetch the full documents in order to evaluate the predicate.

// Case 1: An EXACT predicate and an INEXACT_COVERED
t.drop();
t.createIndex({name: 1});
t.insert({_id: 0, name: "thomas"});
t.insert({_id: 1, name: "alexandra"});
cursor = t.find({$or: [{name: "thomas"}, {name: /^alexand(er|ra)/}]});
assert.eq(2, cursor.itcount(), "case 1");

// Case 2: Two INEXACT_COVERED predicates.
t.drop();
t.createIndex({name: 1});
t.insert({_id: 0, name: "thomas"});
t.insert({_id: 1, name: "alexandra"});
cursor = t.find({$or: [{name: /omas/}, {name: /^alexand(er|ra)/}]});
assert.eq(2, cursor.itcount(), "case 2");

// Case 3: An EXACT, and INEXACT_COVERED, and an INEXACT_FETCH.
t.drop();
t.createIndex({names: 1});
t.insert({_id: 0, names: ["thomas", "alexandra"]});
t.insert({_id: 1, names: "frank"});
t.insert({_id: 2, names: "alice"});
t.insert({_id: 3, names: ["dave"]});
cursor = t.find(
    {$or: [{names: "frank"}, {names: /^al(ice|ex)/}, {names: {$elemMatch: {$eq: "thomas"}}}]});
assert.eq(3, cursor.itcount(), "case 3");

// Case 4: Two INEXACT_FETCH.
t.drop();
t.createIndex({names: 1});
t.insert({_id: 0, names: ["thomas", "alexandra"]});
t.insert({_id: 1, names: ["frank", "alice"]});
t.insert({_id: 2, names: "frank"});
cursor = t.find(
    {$or: [{names: {$elemMatch: {$eq: "alexandra"}}}, {names: {$elemMatch: {$eq: "frank"}}}]});
assert.eq(2, cursor.itcount(), "case 4");

// Case 5: Two indices. One has EXACT and INEXACT_COVERED. The other
// has EXACT and INEXACT_FETCH.
t.drop();
t.createIndex({first: 1});
t.createIndex({last: 1});
t.insert({_id: 0, first: "frank", last: "smith"});
t.insert({_id: 1, first: "john", last: "doe"});
t.insert({_id: 2, first: "dave", last: "st"});
t.insert({_id: 3, first: ["dave", "david"], last: "pasette"});
t.insert({_id: 4, first: "joanna", last: ["smith", "doe"]});
cursor = t.find(
    {$or: [{first: "frank"}, {last: {$elemMatch: {$eq: "doe"}}}, {first: /david/}, {last: "st"}]});
assert.eq(4, cursor.itcount(), "case 5");

// Case 6: Multikey with only EXACT predicates.
t.drop();
t.createIndex({names: 1});
t.insert({_id: 0, names: ["david", "dave"]});
t.insert({_id: 1, names: ["joseph", "joe", "joey"]});
cursor = t.find({$or: [{names: "dave"}, {names: "joe"}]});
assert.eq(2, cursor.itcount(), "case 6");

// Case 7: Multikey with EXACT and INEXACT_COVERED.
t.drop();
t.createIndex({names: 1});
t.insert({_id: 0, names: ["david", "dave"]});
t.insert({_id: 1, names: ["joseph", "joe", "joey"]});
cursor = t.find({$or: [{names: "dave"}, {names: /joe/}]});
assert.eq(2, cursor.itcount(), "case 7");

// Case 8: Text with EXACT.
t.drop();
t.createIndex({pre: 1, names: "text"});
t.createIndex({other: 1});
t.insert({_id: 0, pre: 3, names: "david dave", other: 1});
t.insert({_id: 1, pre: 4, names: "joseph joe joey", other: 2});
cursor = t.find({$or: [{$text: {$search: "dave"}, pre: 3}, {other: 2}]});
assert.eq(2, cursor.itcount(), "case 8");

// Case 9: Text with INEXACT_COVERED.
t.drop();
t.createIndex({pre: 1, names: "text"});
t.createIndex({other: 1});
t.insert({_id: 0, pre: 3, names: "david dave", other: "foo"});
t.insert({_id: 1, pre: 5, names: "david dave", other: "foo"});
t.insert({_id: 2, pre: 4, names: "joseph joe joey", other: "bar"});
cursor = t.find({$or: [{$text: {$search: "dave"}, pre: 3}, {other: /bar/}]});
assert.eq(2, cursor.itcount(), "case 9");

// Case 10: Text requiring filter with INEXACT_COVERED.
t.drop();
t.createIndex({pre: 1, names: "text"});
t.createIndex({other: 1});
t.insert({_id: 0, pre: 3, names: "david dave", other: "foo"});
t.insert({_id: 1, pre: 3, names: "david dave", other: "foo"});
t.insert({_id: 2, pre: 4, names: "joseph joe joey", other: "bar"});
cursor = t.find({$or: [{$text: {$search: "dave"}, pre: 3, other: "foo"}, {other: /bar/}]});
assert.eq(3, cursor.itcount(), "case 10");

// Case 11: GEO with non-geo, same index, 2dsphere.
t.drop();
t.createIndex({pre: 1, loc: "2dsphere"});
t.insert({_id: 0, pre: 3, loc: {type: "Point", coordinates: [40, 5]}});
t.insert({_id: 1, pre: 4, loc: {type: "Point", coordinates: [0, 0]}});
cursor = t.find({
    $or: [
        {
            pre: 3,
            loc: {
                $geoWithin: {
                    $geometry: {
                        type: "Polygon",
                        coordinates: [[[39, 4], [41, 4], [41, 6], [39, 6], [39, 4]]]
                    }
                }
            }
        },
        {
            pre: 4,
            loc: {
                $geoWithin: {
                    $geometry: {
                        type: "Polygon",
                        coordinates: [[[-1, -1], [1, -1], [1, 1], [-1, 1], [-1, -1]]]
                    }
                }
            }
        }
    ]
});
assert.eq(2, cursor.itcount(), "case 11");

// Case 12: GEO with non-geo, same index, 2d.
t.drop();
t.createIndex({pre: 1, loc: "2d"});
t.insert({_id: 0, pre: 3, loc: {type: "Point", coordinates: [40, 5]}});
t.insert({_id: 1, pre: 4, loc: {type: "Point", coordinates: [0, 0]}});
cursor = t.find({
    $or: [
        {
            pre: 3,
            loc: {
                $geoWithin: {
                    $geometry: {
                        type: "Polygon",
                        coordinates: [[[39, 4], [41, 4], [41, 6], [39, 6], [39, 4]]]
                    }
                }
            }
        },
        {
            pre: 4,
            loc: {
                $geoWithin: {
                    $geometry: {
                        type: "Polygon",
                        coordinates: [[[-1, -1], [1, -1], [1, 1], [-1, 1], [-1, -1]]]
                    }
                }
            }
        }
    ]
});
assert.eq(2, cursor.itcount(), "case 12");

// Case 13: $elemMatch object.
t.drop();
t.createIndex({"a.b": 1});
t.insert({_id: 0, a: [{b: 1}, {b: 2}]});
t.insert({_id: 1, a: [{b: 3}, {b: 4}]});
cursor = t.find({$or: [{a: {$elemMatch: {b: {$lte: 1}}}}, {a: {$elemMatch: {b: {$gte: 4}}}}]});
assert.eq(2, cursor.itcount(), "case 13");

// Case 14: $elemMatch object, below an AND.
t.drop();
t.createIndex({"a.b": 1});
t.insert({_id: 0, a: [{b: 1}, {b: 2}]});
t.insert({_id: 1, a: [{b: 2}, {b: 4}]});
cursor =
    t.find({"a.b": 2, $or: [{a: {$elemMatch: {b: {$lte: 1}}}}, {a: {$elemMatch: {b: {$gte: 4}}}}]});
assert.eq(2, cursor.itcount(), "case 14");

// Case 15: $or below $elemMatch.
t.drop();
t.createIndex({"a.b": 1});
t.insert({_id: 0, a: [{b: 1}, {b: 2}]});
t.insert({_id: 1, a: [{b: 2}, {b: 4}]});
t.insert({_id: 2, a: {b: 4}});
cursor = t.find({a: {$elemMatch: {$or: [{b: 1}, {b: 4}]}}});
assert.eq(2, cursor.itcount(), "case 15");

// Case 16: $or below $elemMatch with INEXACT_COVERED.
t.drop();
t.createIndex({"a.b": 1});
t.insert({_id: 0, a: [{b: "x"}, {b: "y"}]});
t.insert({_id: 1, a: [{b: "y"}, {b: ["y", "z"]}]});
t.insert({_id: 2, a: {b: ["y", "z"]}});
cursor = t.find({a: {$elemMatch: {$or: [{b: "x"}, {b: /z/}]}}});
assert.eq(2, cursor.itcount(), "case 16");

// Case 17: case from SERVER-14030.
t.drop();
t.createIndex({number: 1});
t.insert({number: null, user_id: 1});
t.insert({number: 2, user_id: 1});
t.insert({number: 1, user_id: 1});
cursor = t.find({$or: [{number: null}, {number: {$lte: 2}}]});
assert.eq(3, cursor.itcount(), "case 17");

// Case 18: $in with EXACT and INEXACT_COVERED.
t.drop();
t.createIndex({name: 1});
t.insert({_id: 0, name: "thomas"});
t.insert({_id: 1, name: "alexandra"});
cursor = t.find({name: {$in: ["thomas", /^alexand(er|ra)/]}});
assert.eq(2, cursor.itcount(), "case 18");

// Case 19: $in with EXACT, INEXACT_COVERED, and INEXACT_FETCH.
t.drop();
t.createIndex({name: 1});
t.insert({_id: 0, name: "thomas"});
t.insert({_id: 1, name: "alexandra"});
t.insert({_id: 2});
cursor = t.find({$or: [{name: {$in: ["thomas", /^alexand(er|ra)/]}}, {name: {$exists: false}}]});
assert.eq(3, cursor.itcount(), "case 19");

// Case 20: $in with EXACT, INEXACT_COVERED, and INEXACT_FETCH, two indices.
t.drop();
t.createIndex({a: 1});
t.createIndex({b: 1});
t.insert({_id: 0, a: "x", b: "y"});
t.insert({_id: 1, a: "z", b: "z"});
t.insert({_id: 2});
t.insert({_id: 3, a: "w", b: "x"});
t.insert({_id: 4, a: "l", b: "p"});
cursor =
    t.find({$or: [{a: {$in: [/z/, /x/]}}, {a: "w"}, {b: {$exists: false}}, {b: {$in: ["p"]}}]});
assert.eq(5, cursor.itcount(), "case 19");

// Case 21: two $geoWithin that collapse to a single GEO index scan.
t.drop();
t.createIndex({loc: "2dsphere"});
t.insert({_id: 0, loc: {type: "Point", coordinates: [40, 5]}});
t.insert({_id: 1, loc: {type: "Point", coordinates: [0, 0]}});
cursor = t.find({
    $or: [
        {
            loc: {
                $geoWithin: {
                    $geometry: {
                        type: "Polygon",
                        coordinates: [[[39, 4], [41, 4], [41, 6], [39, 6], [39, 4]]]
                    }
                }
            }
        },
        {
            loc: {
                $geoWithin: {
                    $geometry: {
                        type: "Polygon",
                        coordinates: [[[-1, -1], [1, -1], [1, 1], [-1, 1], [-1, -1]]]
                    }
                }
            }
        }
    ]
});
assert.eq(2, cursor.itcount(), "case 21");
