mongo/jstests/core/computed_projections.js

1122 lines
39 KiB
JavaScript

(function() {
"use strict";
load("jstests/aggregation/extras/utils.js"); // For arrayEq and orderedArrayEq.
load("jstests/libs/sbe_assert_error_override.js");
const coll = db.computed_projection;
coll.drop();
const documents = [
{_id: 0, a: 1, b: "x", c: 10, zero: 0},
{_id: 1, a: 2, b: "y", c: 11},
{_id: 2, a: 3, b: "z", c: 12},
{_id: 3, x: {y: 1}},
{_id: 4, x: {y: 2}},
{_id: 5, x: {y: [1, 2, 3]}, v: {w: [4, 5, 6]}},
{_id: 6, x: {y: 4}, v: {w: 4}},
{_id: 7, x: [{y: 1}], v: [{w: 1}]},
{_id: 8, x: [{y: 1}, {y: 2}], v: [{w: 5}, {w: 6}]},
{_id: 9, x: [{y: 1}, {y: [1, 2, 3]}], v: [{w: 4}, {w: [4, 5, 6]}]},
{_id: 10, z: 1},
{_id: 11, z: 2},
{_id: 12, z: [1, 2, 3]},
{_id: 13, z: 3},
{_id: 14, z: 4},
{_id: 15, a: 10, x: 1},
{_id: 16, a: 10, x: 10},
{_id: 17, x: {y: [{z: 1}, {z: 2}]}},
{_id: 18, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
{_id: 19, i: {j: 5}, k: {l: 10}},
{_id: 20, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
{_id: 21, x: [[{y: {z: 1}}, {y: 2}], {y: 3}, {y: {z: 2}}, [[[{y: 5}, {y: {z: 3}}]]], {y: 6}]},
{
_id: 22,
tf: [true, false],
ff: [false, false],
t: true,
f: false,
n: null,
a: 1,
b: 0,
zero: 0
},
{_id: 23, i1: NumberInt(1), i2: NumberInt(-1), i3: NumberInt(-2147483648)},
{_id: 24, l1: NumberLong("12345678900"), l2: NumberLong("-12345678900")},
{_id: 25, s: "string", l: NumberLong("-9223372036854775808"), n: null},
{_id: 26, d1: 4.6, d2: -4.6, dec1: NumberDecimal("4.6"), dec2: NumberDecimal("-4.6")},
{_id: 27, dec1: NumberDecimal("-1.0"), dec2: NumberDecimal("2.0"), dec3: NumberDecimal("3.0")}
];
assert.commandWorked(coll.insert(documents));
// A concise way to express an "expected" result that takes the form
// [{_id: 0, foo: <BOOL>}, {_id, 1, foo: <BOOL>}, ..., {_id: 26, foo: <BOOL>}] by passing an object
// of the form {foo: [INTEGER_LIST]}, where INTEGER_LIST is the list of '_id' values for documents
// where "foo" should be true.
function computedProjectionWithBoolValues(boolProj) {
return documents.map(doc => {
const projectedDoc = {_id: doc._id};
Object.keys(boolProj).forEach(key => {
projectedDoc[key] = boolProj[key].includes(doc._id);
});
return projectedDoc;
});
}
const testCases = [
{
desc: "Single-level path 1",
expected: [
{_id: 0, foo: 1}, {_id: 1, foo: 2}, {_id: 2, foo: 3}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15, foo: 10}, {_id: 16, foo: 10}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 1}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {_id: 1, foo: "$a"}
},
{
desc: "Single-level path 2",
expected: [
{_id: 0, foo: 1}, {_id: 1, foo: 2}, {_id: 2, foo: 3}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15, foo: 10}, {_id: 16, foo: 10}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 1}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {foo: "$a"}
},
{
desc: "Single-level path 3",
expected: [
{foo: 1}, {foo: 2}, {foo: 3}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
{}, {foo: 10}, {foo: 10}, {}, {}, {}, {}, {}, {foo: 1}, {}, {}, {}, {}, {}
],
query: {},
proj: {_id: 0, foo: "$a"}
},
{
desc: "Two single-level paths",
expected: [
{_id: 0, foo: 1, bar: "x"},
{_id: 1, foo: 2, bar: "y"},
{_id: 2, foo: 3, bar: "z"},
{_id: 3},
{_id: 4},
{_id: 5},
{_id: 6},
{_id: 7},
{_id: 8},
{_id: 9},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15, foo: 10},
{_id: 16, foo: 10},
{_id: 17},
{_id: 18},
{_id: 19},
{_id: 20},
{_id: 21},
{_id: 22, foo: 1, bar: 0},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {_id: 1, foo: "$a", bar: "$b"}
},
{
desc: "Simple addition",
expected: [{_id: 0, foo: 11}, {_id: 1, foo: 13}, {_id: 2, foo: 15}],
query: {c: {$gt: 0}},
proj: {foo: {$add: ["$a", "$c"]}}
},
{
desc: "$add with arity 1 and 3",
expected: [{_id: 27, add1: NumberDecimal("-1.0"), add3: NumberDecimal("4.0")}],
query: {dec1: NumberDecimal("-1.0")},
proj: {add1: {$add: "$dec1"}, add3: {$add: ["$dec1", "$dec2", "$dec3"]}}
},
{
desc: "Single-level path 4",
expected: [
{_id: 0, a: 1, foo: "x"},
{_id: 1, a: 2, foo: "y"},
{_id: 2, a: 3, foo: "z"},
{_id: 3},
{_id: 4},
{_id: 5},
{_id: 6},
{_id: 7},
{_id: 8},
{_id: 9},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15, a: 10},
{_id: 16, a: 10},
{_id: 17},
{_id: 18},
{_id: 19},
{_id: 20},
{_id: 21},
{_id: 22, a: 1, foo: 0},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {a: 1, foo: "$b"}
},
{
desc: "Two-level path 1",
expected: [
{_id: 0},
{_id: 1},
{_id: 2},
{_id: 3, foo: 1},
{_id: 4, foo: 2},
{_id: 5, foo: [1, 2, 3]},
{_id: 6, foo: 4},
{_id: 7, foo: [1]},
{_id: 8, foo: [1, 2]},
{_id: 9, foo: [1, [1, 2, 3]]},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15},
{_id: 16},
{_id: 17, foo: [{z: 1}, {z: 2}]},
{_id: 18, foo: [3, 4, 6]},
{_id: 19},
{_id: 20, foo: [3, 4, 6]},
{_id: 21, foo: [3, {z: 2}, 6]},
{_id: 22},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {_id: 1, foo: "$x.y"}
},
{
desc: "Two-level path 2",
expected: [
{_id: 0},
{_id: 1},
{_id: 2},
{_id: 3, foo: 1},
{_id: 4, foo: 2},
{_id: 5, foo: [1, 2, 3]},
{_id: 6, foo: 4},
{_id: 7, foo: [1]},
{_id: 8, foo: [1, 2]},
{_id: 9, foo: [1, [1, 2, 3]]},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15},
{_id: 16},
{_id: 17, foo: [{z: 1}, {z: 2}]},
{_id: 18, foo: [3, 4, 6]},
{_id: 19},
{_id: 20, foo: [3, 4, 6]},
{_id: 21, foo: [3, {z: 2}, 6]},
{_id: 22},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {foo: "$x.y"}
},
{
desc: "Two-level path 3",
expected: [
{},
{},
{},
{foo: 1},
{foo: 2},
{foo: [1, 2, 3]},
{foo: 4},
{foo: [1]},
{foo: [1, 2]},
{foo: [1, [1, 2, 3]]},
{},
{},
{},
{},
{},
{},
{},
{foo: [{z: 1}, {z: 2}]},
{foo: [3, 4, 6]},
{},
{foo: [3, 4, 6]},
{foo: [3, {z: 2}, 6]},
{},
{},
{},
{},
{},
{}
],
query: {},
proj: {_id: 0, foo: "$x.y"}
},
{
desc: "Two two-level paths",
expected: [
{_id: 0},
{_id: 1},
{_id: 2},
{_id: 3, foo: 1},
{_id: 4, foo: 2},
{_id: 5, foo: [1, 2, 3], bar: [4, 5, 6]},
{_id: 6, foo: 4, bar: 4},
{_id: 7, foo: [1], bar: [1]},
{_id: 8, foo: [1, 2], bar: [5, 6]},
{_id: 9, foo: [1, [1, 2, 3]], bar: [4, [4, 5, 6]]},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15},
{_id: 16},
{_id: 17, foo: [{z: 1}, {z: 2}]},
{_id: 18, foo: [3, 4, 6]},
{_id: 19},
{_id: 20, foo: [3, 4, 6]},
{_id: 21, foo: [3, {z: 2}, 6]},
{_id: 22},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {foo: "$x.y", bar: "$v.w"}
},
{
desc: "Addition of two-level paths",
expected: [{_id: 19, foo: 15}],
query: {"i.j": {$gt: 0}},
proj: {foo: {$add: ["$i.j", "$k.l"]}}
},
{
desc: "Dotted-path projection and two-level path",
expected: [
{_id: 0},
{_id: 1},
{_id: 2},
{_id: 3, x: {y: 1}},
{_id: 4, x: {y: 2}},
{_id: 5, x: {y: [1, 2, 3]}, foo: [4, 5, 6]},
{_id: 6, x: {y: 4}, foo: 4},
{_id: 7, x: [{y: 1}], foo: [1]},
{_id: 8, x: [{y: 1}, {y: 2}], foo: [5, 6]},
{_id: 9, x: [{y: 1}, {y: [1, 2, 3]}], foo: [4, [4, 5, 6]]},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15},
{_id: 16},
{_id: 17, x: {y: [{z: 1}, {z: 2}]}},
{_id: 18, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
{_id: 19},
{_id: 20, x: [[{y: 1}, {y: 2}], {y: 3}, {y: 4}, [[[{y: 5}]]], {y: 6}]},
{
_id: 21,
x: [[{y: {z: 1}}, {y: 2}], {y: 3}, {y: {z: 2}}, [[[{y: 5}, {y: {z: 3}}]]], {y: 6}]
},
{_id: 22},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {"x.y": 1, foo: "$v.w"}
},
//
// Test simple expressions with the $abs operator.
//
{
desc: "$abs operator",
expected: [{_id: 23, abs_i1: 1, abs_i2: 1, abs_i3: NumberLong("2147483648")}],
query: {i1: 1},
proj: {abs_i1: {$abs: "$i1"}, abs_i2: {$abs: "$i2"}, abs_i3: {$abs: "$i3"}}
},
{
desc: "$abs with NumberLong input",
expected: [{_id: 24, abs_l1: NumberLong("12345678900"), abs_l2: NumberLong("12345678900")}],
query: {l1: NumberLong("12345678900")},
proj: {abs_l1: {$abs: "$l1"}, abs_l2: {$abs: "$l2"}}
},
{
desc: "$abs with NumberDecimal input",
expected: [{
_id: 26,
abs_d1: 4.6,
abs_d2: 4.6,
abs_dec1: NumberDecimal("4.6"),
abs_dec2: NumberDecimal("4.6")
}],
query: {d1: 4.6},
proj: {
abs_d1: {$abs: "$d1"},
abs_d2: {$abs: "$d2"},
abs_dec1: {$abs: "$dec1"},
abs_dec2: {$abs: "$dec2"}
}
},
{
desc: "$abs with string input",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {abs_s: {$abs: "$s"}}
},
{
desc: "$abs with MIN_LONG_LONG input",
expectedErrorCode: 28680,
query: {s: "string"},
proj: {abs_l: {$abs: "$l"}}
},
{
desc: "$abs with missing input",
expected: [{_id: 25, abs_n: null, abs_ne: null}],
query: {s: "string"},
proj: {abs_n: {$abs: "$n"}, abs_ne: {$abs: "$non_existent"}}
},
//
// Test $and/$or.
//
{
desc: "Single-branch $and",
expected: [
{_id: 0, foo: true}, {_id: 1, foo: true}, {_id: 2, foo: true},
{_id: 3, foo: false}, {_id: 4, foo: false}, {_id: 5, foo: false},
{_id: 6, foo: false}, {_id: 7, foo: false}, {_id: 8, foo: false},
{_id: 9, foo: false}, {_id: 10, foo: false}, {_id: 11, foo: false},
{_id: 12, foo: false}, {_id: 13, foo: false}, {_id: 14, foo: false},
{_id: 15, foo: true}, {_id: 16, foo: true}, {_id: 17, foo: false},
{_id: 18, foo: false}, {_id: 19, foo: false}, {_id: 20, foo: false},
{_id: 21, foo: false}, {_id: 22, foo: true}, {_id: 23, foo: false},
{_id: 24, foo: false}, {_id: 25, foo: false}, {_id: 26, foo: false},
{_id: 27, foo: false}
],
query: {},
proj: {foo: {$and: ["$a"]}}
},
{
desc: "Single-branch $or",
expected: [
{_id: 0, foo: true}, {_id: 1, foo: true}, {_id: 2, foo: true},
{_id: 3, foo: false}, {_id: 4, foo: false}, {_id: 5, foo: false},
{_id: 6, foo: false}, {_id: 7, foo: false}, {_id: 8, foo: false},
{_id: 9, foo: false}, {_id: 10, foo: false}, {_id: 11, foo: false},
{_id: 12, foo: false}, {_id: 13, foo: false}, {_id: 14, foo: false},
{_id: 15, foo: true}, {_id: 16, foo: true}, {_id: 17, foo: false},
{_id: 18, foo: false}, {_id: 19, foo: false}, {_id: 20, foo: false},
{_id: 21, foo: false}, {_id: 22, foo: true}, {_id: 23, foo: false},
{_id: 24, foo: false}, {_id: 25, foo: false}, {_id: 26, foo: false},
{_id: 27, foo: false}
],
query: {},
proj: {foo: {$or: ["$a"]}}
},
{
desc: "$and with BSONNull branch",
expected: [{_id: 22, foo: false}],
query: {_id: 22, a: 1},
proj: {foo: {$and: ["$n"]}}
},
{
desc: "$or with BSONNull branch",
expected: [{_id: 22, foo: false}],
query: {_id: 22, a: 1},
proj: {foo: {$or: ["$n"]}}
},
{
desc: "$and with missing path in branch",
expected: [{_id: 22, foo: false}],
query: {_id: 22, a: 1},
proj: {foo: {$and: ["$nonexistent"]}}
},
{
desc: "$or with missing path in branch",
expected: [{_id: 22, foo: false}],
query: {_id: 22, a: 1},
proj: {foo: {$or: ["$nonexistent"]}}
},
{
desc: "$and with array branch",
expected: [{_id: 22, foo: true, bar: true}],
query: {_id: 22, a: 1},
proj: {foo: {$and: []}, bar: {$and: ["$tf", "$t", "$a"]}}
},
{
desc: "Three-branch $or",
expected: [{_id: 22, foo: false, bar: false}],
query: {_id: 22, a: 1},
proj: {foo: {$or: []}, bar: {$or: ["$f", "$n", "$nonexistent"]}}
},
{
desc: "Multiple computed $and projections 1",
expected: [{_id: 22, foo: false, bar: false, baz: false}],
query: {_id: 22, a: 1},
proj: {foo: {$and: ["$a", "$b"]}, bar: {$and: ["$a", "$f"]}, baz: {$and: ["$a", "$n"]}}
},
{
desc: "Multiple computed $or projections",
expected: [{_id: 22, foo: true, bar: true, baz: true}],
query: {_id: 22, a: 1},
proj: {foo: {$or: ["$a", "$b"]}, bar: {$or: ["$a", "$f"]}, baz: {$or: ["$a", "$n"]}}
},
{
desc: "Multiple computed $and projections 2",
expected: [{_id: 22, foo: true, bar: false}],
query: {_id: 22, a: 1},
proj: {foo: {$and: ["$ff", "$t"]}, bar: {$and: ["$nonexistent", "$t"]}}
},
//
// $switch and $cond tests.
//
{
desc: "Single-case $switch with default",
expected: [
{_id: 0, foo: "x"}, {_id: 1, foo: 11}, {_id: 2, foo: 12}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15}, {_id: 16}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 0}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {foo: {$switch: {branches: [{case: {$eq: ["$a", 1]}, then: "$b"}], default: "$c"}}}
},
{
desc: "$cond",
expected: [
{_id: 0, foo: "x"}, {_id: 1, foo: 11}, {_id: 2, foo: 12}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15}, {_id: 16}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 0}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {foo: {$cond: {if: {$eq: ["$a", 1]}, then: "$b", else: "$c"}}}
},
{
desc: "Two-case $switch with default",
expected: [
{_id: 0, foo: "x"}, {_id: 1, foo: 2}, {_id: 2, foo: 12}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15}, {_id: 16}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 0}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {
foo: {
$switch: {
branches: [
{case: {$eq: ["$a", 1]}, then: "$b"},
{case: {$eq: ["$b", "y"]}, then: "$a"}
],
default: "$c"
}
}
}
},
//
// Failing expressions
//
{
desc: "$abs with string input as $and branch",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$and: [{$abs: ["$s"]}, "$n"]}}
},
{
desc: "$abs with string input as $or branch",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$or: [{$abs: ["$s"]}, "$s"]}}
},
{
desc: "Switch fall-through with no default",
expectedErrorCode: 40066,
query: {},
proj: {
foo: {
$switch: {
branches: [
{case: {$eq: ["$a", 1]}, then: "$b"},
{case: {$eq: ["$b", "y"]}, then: "$a"}
]
// No default case.
}
}
}
},
{
desc: "$abs with string input as case condition",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$switch: {branches: [{case: {$gt: [{$abs: ["$s"]}, 0]}, then: "$n"}]}}}
},
{
desc: "$abs with string input as case result",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$switch: {branches: [{case: {$eq: ["$s", "string"]}, then: {$abs: ["$s"]}}]}}}
},
{
desc: "$abs with string input as $switch default",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {
foo:
{$switch: {branches: [{case: {$eq: ["$s", 0]}, then: "$n"}], default: {$abs: "$s"}}}
}
},
{
desc: "$abs with string input as $cond condition",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$cond: {if: {$eq: [{$abs: "$s"}, "$n"]}, then: "$b", else: "$c"}}}
},
{
desc: "$abs with string input as $cond result (then)",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$cond: {if: {$eq: ["$s", "string"]}, then: {$abs: ["$s"]}, else: "$c"}}}
},
{
desc: "$abs with string input as $cond result (else)",
expectedErrorCode: 28765,
query: {s: "string"},
proj: {foo: {$cond: {if: {$eq: ["$s", "gnirts"]}, then: "$b", else: {$abs: ["$s"]}}}}
},
//
// Test short circuiting: these queries have expressions that would fail (because |$x| is
// invalid) but won't because they do not execute.
//
{
desc: "$abs with string input as short-circuited $and branch",
expected: [{_id: 25, foo: false}],
query: {s: "string"},
proj: {foo: {$and: ["$n", {$abs: ["$s"]}]}}
},
{
desc: "$abs with string input as short-circuited $or branch",
expected: [{_id: 25, foo: true}],
query: {s: "string"},
proj: {foo: {$or: ["$s", {$abs: ["$s"]}]}}
},
//
// Test that short-circuited branches do not do any work, such as traversing an array. The
// short-circuited branches cointain divide by zero operation which if it were to execute
// would result in an error from the query that would cause the test to fail.
//
{
desc: "$divide by zero in short-circuited $and/$or branches",
expected: [{_id: 22, foo: false, bar: true}],
query: {_id: 22, a: 1},
proj: {
foo: {$and: ["$f", {$divide: [1, "$zero"]}]},
bar: {$or: ["$t", {$divide: [1, "$zero"]}]}
}
},
{
desc: "$divide by zero in nested short-circuited $or branches",
expected: [{_id: 22, foo: false, bar: true}],
query: {_id: 22, a: 1},
proj: {
foo: {$and: ["$f", {$or: ["$f", {$divide: [1, "$zero"]}]}, {$eq: ["$a", 1]}]},
bar: {$and: ["$t", {$or: ["$t", {$divide: [1, "$zero"]}]}, {$eq: ["$a", 1]}]}
}
},
{
desc: "$divide by zero in untaken $switch cases",
expected: [{_id: 0, foo: "x"}, {_id: 22, foo: 0}],
query: {a: 1},
proj: {
foo: {
$switch: {
branches: [
{case: {$eq: ["$a", 2]}, then: {$divide: [1, "$zero"]}},
{case: {$eq: ["$a", 3]}, then: {$divide: [1, "$zero"]}}
],
default: "$b"
}
}
}
},
{
desc: "$divide by zero in unevaluated case condition and untaken $switch default branch",
expected: [{_id: 0, foo: "x"}, {_id: 22, foo: 0}],
query: {a: 1},
proj: {
foo: {
$switch: {
branches: [
{case: {$eq: ["$a", 1]}, then: "$b"},
{case: {$divide: [1, "$zero"]}, then: {$divide: [1, "$zero"]}}
],
default: {$divide: [1, "$zero"]}
}
}
}
},
{
desc: "$divide by zero in untaken $cond branch (else)",
expected: [{_id: 0, foo: "x"}, {_id: 22, foo: 0}],
query: {a: 1},
proj: {foo: {$cond: {if: {$eq: ["$a", 1]}, then: "$b", else: {$divide: [1, "$zero"]}}}}
},
{
desc: "$divide by zero in untaken $cond branch (then)",
expected: [{_id: 0, foo: "x"}, {_id: 22, foo: 0}],
query: {a: 1},
proj: {foo: {$cond: {if: {$eq: ["$a", 2]}, then: {$divide: [1, "$zero"]}, else: "$b"}}}
},
//
// $let tests.
//
{
desc: "$let with single-path variable definitions 1",
expected: computedProjectionWithBoolValues({foo: [0, 1, 2, 15, 16, 22]}),
query: {},
proj: {foo: {$let: {vars: {va: "$a", vb: "$b"}, "in": {$and: "$$va"}}}}
},
{
desc: "$let with single-path variable definitions 2",
expected: computedProjectionWithBoolValues({foo: [0, 1, 2]}),
query: {},
proj: {foo: {$let: {vars: {va: "$a", vb: "$b"}, "in": {$and: "$$vb"}}}}
},
{
desc: "$let with single-path variable definitions 3",
expected: computedProjectionWithBoolValues({foo: [0, 1, 2]}),
query: {},
proj: {foo: {$let: {vars: {va: "$a", vb: "$b"}, "in": {$and: ["$$va", "$$vb"]}}}}
},
{
desc: "$let with $and variable definition",
expected: computedProjectionWithBoolValues({foo: [0, 1, 2]}),
query: {},
proj: {foo: {$let: {vars: {va: {$and: ["$a", "$b"]}}, "in": "$$va"}}}
},
{
desc: "Two-level path including $let variable",
expected: [
{_id: 0},
{_id: 1},
{_id: 2},
{_id: 3, foo: 1},
{_id: 4, foo: 2},
{_id: 5, foo: [1, 2, 3]},
{_id: 6, foo: 4},
{_id: 7, foo: [1]},
{_id: 8, foo: [1, 2]},
{_id: 9, foo: [1, [1, 2, 3]]},
{_id: 10},
{_id: 11},
{_id: 12},
{_id: 13},
{_id: 14},
{_id: 15},
{_id: 16},
{_id: 17, foo: [{z: 1}, {z: 2}]},
{_id: 18, foo: [3, 4, 6]},
{_id: 19},
{_id: 20, foo: [3, 4, 6]},
{_id: 21, foo: [3, {z: 2}, 6]},
{_id: 22},
{_id: 23},
{_id: 24},
{_id: 25},
{_id: 26},
{_id: 27}
],
query: {},
proj: {foo: {$let: {vars: {va: "$x"}, "in": "$$va.y"}}}
},
{
desc: "Nested $let expressions",
expected: computedProjectionWithBoolValues({foo: [22]}),
query: {},
proj: {
foo: {
$let: {
vars: {vt: "$t", vf: "$f"},
"in": {
$let: {vars: {vf: "$$vt", va: "$a"},
"in": {$and: ["$$vt", "$$vf", "$$va"]}}
}
}
}
}
},
{
desc: "$let with variable definition including nested $let",
expected: [{_id: 22, foo: false}],
query: {_id: 22, a: 1},
proj: {
foo: {
$let: {
vars:
{va: {$let: {vars: {vt: "$t", va: "$va"}, "in": {$and: ["$$vt", "$$va"]}}}},
"in": "$$va"
}
}
}
},
{
desc: "$let renaming $$CURRENT",
expected: [
{_id: 0, foo: 1}, {_id: 1, foo: 2}, {_id: 2, foo: 3}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15, foo: 10}, {_id: 16, foo: 10}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 1}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {foo: {$let: {vars: {doc: "$$CURRENT"}, "in": "$$doc.a"}}}
},
{
desc: "$let shadowing $$CURRENT",
expected: [
{_id: 0, foo: 1}, {_id: 1, foo: 2}, {_id: 2, foo: 3}, {_id: 3}, {_id: 4},
{_id: 5}, {_id: 6}, {_id: 7}, {_id: 8}, {_id: 9},
{_id: 10}, {_id: 11}, {_id: 12}, {_id: 13}, {_id: 14},
{_id: 15, foo: 10}, {_id: 16, foo: 10}, {_id: 17}, {_id: 18}, {_id: 19},
{_id: 20}, {_id: 21}, {_id: 22, foo: 1}, {_id: 23}, {_id: 24},
{_id: 25}, {_id: 26}, {_id: 27}
],
query: {},
proj: {foo: {$let: {vars: {CURRENT: "$$CURRENT.a"}, "in": "$$CURRENT"}}}
},
{
desc: "$$REMOVE",
expected: documents.map(doc => ({_id: doc._id})),
query: {},
proj: {a: "$$REMOVE"}
},
{
desc: "$$REMOVE with additional path components",
expected: documents.map(doc => ({_id: doc._id})),
query: {},
proj: {a: "$$REMOVE.x.y"}
},
{
desc: "$lt",
expected: computedProjectionWithBoolValues(
{foo: [3, 4, 5, 6, 7, 8, 9, 17, 18, 20, 21], bar: [0, 1, 2], baz: [5, 8, 9], qux: []}),
query: {},
proj: {
foo: {$lt: ["$a", "$x"]},
bar: {$lt: ["$a", "$b"]},
baz: {$lt: ["$x.y", "$v.w"]},
qux: {$lt: ["$a", "$nonexistent"]}
}
},
{
desc: "$lte",
expected: computedProjectionWithBoolValues({
foo: [
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27
],
bar: [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27
],
baz: [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27],
qux:
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27]
}),
query: {},
proj: {
foo: {$lte: ["$a", "$x"]},
bar: {$lte: ["$a", "$b"]},
baz: {$lte: ["$x.y", "$v.w"]},
qux: {$lte: ["$a", "$nonexistent"]}
}
},
{
desc: "$gt",
expected: computedProjectionWithBoolValues({
foo: [0, 1, 2, 15, 22],
bar: [15, 16, 22],
baz: [3, 4, 17, 18, 20, 21],
qux: [0, 1, 2, 15, 16, 22]
}),
query: {},
proj: {
foo: {$gt: ["$a", "$x"]},
bar: {$gt: ["$a", "$b"]},
baz: {$gt: ["$x.y", "$v.w"]},
qux: {$gt: ["$a", "$nonexistent"]}
}
},
{
desc: "$gte",
expected: computedProjectionWithBoolValues({
foo: [0, 1, 2, 10, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27],
bar: [
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
],
baz: [
0, 1, 2, 3, 4, 6, 7, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
],
qux: [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
]
}),
query: {},
proj: {
foo: {$gte: ["$a", "$x"]},
bar: {$gte: ["$a", "$b"]},
baz: {$gte: ["$x.y", "$v.w"]},
qux: {$gte: ["$a", "$nonexistent"]}
}
},
{
desc: "$eq",
expected: computedProjectionWithBoolValues({
foo: [10, 11, 12, 13, 14, 16, 19, 23, 24, 25, 26, 27],
bar: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27],
baz: [0, 1, 2, 6, 7, 10, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27],
qux:
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27]
}),
query: {},
proj: {
foo: {$eq: ["$a", "$x"]},
bar: {$eq: ["$a", "$b"]},
baz: {$eq: ["$x.y", "$v.w"]},
qux: {$eq: ["$a", "$nonexistent"]}
}
},
{
desc: "$ne",
expected: computedProjectionWithBoolValues({
foo: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 17, 18, 20, 21, 22],
bar: [0, 1, 2, 15, 16, 22],
baz: [3, 4, 5, 8, 9, 17, 18, 20, 21],
qux: [0, 1, 2, 15, 16, 22]
}),
query: {},
proj: {
foo: {$ne: ["$a", "$x"]},
bar: {$ne: ["$a", "$b"]},
baz: {$ne: ["$x.y", "$v.w"]},
qux: {$ne: ["$a", "$nonexistent"]}
}
},
{
desc: "$cmp",
expected: [
{_id: 0, foo: 1, bar: -1, baz: 0, qux: 1}, {_id: 1, foo: 1, bar: -1, baz: 0, qux: 1},
{_id: 2, foo: 1, bar: -1, baz: 0, qux: 1}, {_id: 3, foo: -1, bar: 0, baz: 1, qux: 0},
{_id: 4, foo: -1, bar: 0, baz: 1, qux: 0}, {_id: 5, foo: -1, bar: 0, baz: -1, qux: 0},
{_id: 6, foo: -1, bar: 0, baz: 0, qux: 0}, {_id: 7, foo: -1, bar: 0, baz: 0, qux: 0},
{_id: 8, foo: -1, bar: 0, baz: -1, qux: 0}, {_id: 9, foo: -1, bar: 0, baz: -1, qux: 0},
{_id: 10, foo: 0, bar: 0, baz: 0, qux: 0}, {_id: 11, foo: 0, bar: 0, baz: 0, qux: 0},
{_id: 12, foo: 0, bar: 0, baz: 0, qux: 0}, {_id: 13, foo: 0, bar: 0, baz: 0, qux: 0},
{_id: 14, foo: 0, bar: 0, baz: 0, qux: 0}, {_id: 15, foo: 1, bar: 1, baz: 0, qux: 1},
{_id: 16, foo: 0, bar: 1, baz: 0, qux: 1}, {_id: 17, foo: -1, bar: 0, baz: 1, qux: 0},
{_id: 18, foo: -1, bar: 0, baz: 1, qux: 0}, {_id: 19, foo: 0, bar: 0, baz: 0, qux: 0},
{_id: 20, foo: -1, bar: 0, baz: 1, qux: 0}, {_id: 21, foo: -1, bar: 0, baz: 1, qux: 0},
{_id: 22, foo: 1, bar: 1, baz: 0, qux: 1}, {_id: 23, foo: 0, bar: 0, baz: 0, qux: 0},
{_id: 24, foo: 0, bar: 0, baz: 0, qux: 0}, {_id: 25, foo: 0, bar: 0, baz: 0, qux: 0},
{_id: 26, foo: 0, bar: 0, baz: 0, qux: 0}, {_id: 27, foo: 0, bar: 0, baz: 0, qux: 0}
],
query: {},
proj: {
foo: {$cmp: ["$a", "$x"]},
bar: {$cmp: ["$a", "$b"]},
baz: {$cmp: ["$x.y", "$v.w"]},
qux: {$cmp: ["$a", "$nonexistent"]}
}
},
//
// Nesting torture tests.
//
{
desc: "Nesting torture test 1",
expected: computedProjectionWithBoolValues({foo: [5, 6, 7, 8, 9]}),
query: {},
proj: {
foo: {
$let: {
vars: {v1: {$or: ["$x.y", "$v.w"]}, vx: "$x"},
"in": {$and: ["$$vx.y", "$v.w"]}
}
}
}
},
{
desc: "Nesting torture test 2",
expected: computedProjectionWithBoolValues({foo: [5, 6, 7, 8, 9]}),
query: {},
proj: {
foo: {
$let: {
vars: {v1: "$x.y"},
"in": {
$let: {
vars:
{v2: {$let: {vars: {v3: "$v.w"}, "in": {$and: ["$$v1", "$$v3"]}}}},
"in": "$$v2"
}
}
}
}
}
},
{
desc: "Nesting torture test 3",
expected: computedProjectionWithBoolValues(
{foo: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 18, 20, 21, 22]}),
query: {},
proj: {foo: {$or: ["$a", {$lt: [{$and: ["$a", "$c"]}, {$or: ["$c", "$x"]}]}, "$x"]}}
},
{
desc: "Ludicrous nesting torture test with multiple computed fields",
expected: [
{_id: 0, foo: false, baz: false},
{_id: 1, foo: false, baz: false},
{_id: 2, foo: false, baz: false},
{_id: 3, foo: 2, baz: false},
{_id: 4, bar: 2, baz: false},
{_id: 5, foo: true, bar: [1, 2, 3], baz: true},
{_id: 6, foo: true, bar: 4, baz: false},
{_id: 7, foo: true, bar: [1], baz: false},
{_id: 8, foo: true, bar: [1, 2], baz: true},
{_id: 9, foo: true, bar: [1, [1, 2, 3]], baz: true},
{_id: 10, foo: false, baz: false},
{_id: 11, foo: false, baz: false},
{_id: 12, foo: false, baz: false},
{_id: 13, foo: false, baz: false},
{_id: 14, foo: false, baz: false},
{_id: 15, foo: false, baz: false},
{_id: 16, foo: false, baz: false},
{_id: 17, foo: true, bar: [{z: 1}, {z: 2}], baz: false},
{_id: 18, foo: true, bar: [3, 4, 6], baz: false},
{_id: 19, foo: false, baz: false},
{_id: 20, foo: true, bar: [3, 4, 6], baz: false},
{_id: 21, foo: true, bar: [3, {z: 2}, 6], baz: false},
{_id: 22, foo: true, baz: false},
{_id: 23, foo: false, baz: false},
{_id: 24, foo: false, baz: false},
{_id: 25, foo: false, baz: false},
{_id: 26, foo: false, baz: false},
{_id: 27, foo: false, baz: false}
],
query: {},
proj: {
foo: {
$let: {
vars: {
v1: {$or: ["$x.y", "$v.w"]},
v2: {$switch: {branches: [{case: "$v.w", then: 1}], default: 2}},
v3: "$b"
},
"in": {
$switch: {
branches: [
{case: {$eq: ["$x.y", 1]}, then: "$$v2"},
{case: {$eq: ["$x.y", 2]}, then: "$v.w"},
{case: {$eq: ["$$v3", 2]}, then: "$c"}
],
default: {$or: ["$$v1", "$tf"]}
}
}
}
},
bar: {
$switch: {
branches: [
{case: {$gt: ["$x.y", 1]}, then: "$x.y"},
{case: {$let: {vars: {v4: "$v.w1"}, "in": "$$v4"}}, then: "$v.w2"}
],
default: "$x.y.z"
}
},
baz: {
$let: {
vars: {v5: "$x.y", v6: "$v.w"},
"in": {
$cond: {
if: {$lt: ["$$v5", "$$v6"]},
then: {
$switch: {
branches: [
{case: {$eq: ["$v.w", 5]}, then: "$v.w"},
{case: {$eq: ["$v.w", 4]}, then: "$x.y"}
],
default: {$or: ["$$v5", "$$v6"]}
}
},
else: false
}
}
}
}
}
}
];
testCases.forEach(function(test) {
if (test.expected) {
let actual;
assert.doesNotThrow(() => {
actual = coll.find(test.query, test.proj).toArray();
}, [], test);
assert(arrayEq(actual, test.expected), Object.assign({actual: actual}, test));
} else {
assert(test.expectedErrorCode, test);
const result =
db.runCommand({find: coll.getName(), filter: test.query, projection: test.proj});
assert.commandFailedWithCode(
result, test.expectedErrorCode, Object.assign({result: result}, test));
}
});
}());