mirror of https://github.com/mongodb/mongo
446 lines
15 KiB
JavaScript
446 lines
15 KiB
JavaScript
/**
|
|
* Tests basic functionality of the $getField expression.
|
|
* @tags: [
|
|
* ]
|
|
*/
|
|
import {assertArrayEq} from "jstests/aggregation/extras/utils.js";
|
|
|
|
const coll = db.expression_get_field;
|
|
coll.drop();
|
|
|
|
// Test that $getField fails with the provided 'code' for invalid arguments 'getFieldArgs'.
|
|
function assertGetFieldFailedWithCode(getFieldArgs, code) {
|
|
const error = assert.throws(() => coll.aggregate([{$project: {test: {$getField: getFieldArgs}}}]));
|
|
assert.commandFailedWithCode(error, code);
|
|
}
|
|
|
|
// Test that $getField returns the 'expected' results for the given arguments 'getFieldArgs'.
|
|
function assertGetFieldResultsEq(getFieldArgs, expected) {
|
|
assertPipelineResultsEq([{$project: {_id: 1, test: {$getField: getFieldArgs}}}], expected);
|
|
}
|
|
|
|
// Test the given 'pipeline' returns the 'expected' results.
|
|
function assertPipelineResultsEq(pipeline, expected) {
|
|
const actual = coll.aggregate(pipeline).toArray();
|
|
assertArrayEq({actual, expected});
|
|
}
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
assert.commandWorked(
|
|
coll.insert({
|
|
_id: i,
|
|
x: i,
|
|
y: "c",
|
|
"a$b": "foo",
|
|
"a.b": "bar",
|
|
"a.$b": 5,
|
|
".xy": i,
|
|
".$xz": i,
|
|
"..zz": i,
|
|
"$a": 10,
|
|
"$x.$y": 20,
|
|
"$x..$y": {"$a": 1, "$b..$c": 2},
|
|
c: {d: "x"},
|
|
e: {"$f": 30},
|
|
f: [{"$a": 41}, {"$b..": 42}],
|
|
"$v..": null,
|
|
foo: "bar",
|
|
bar: "baz",
|
|
"letterB": "b",
|
|
"$b": "b",
|
|
arr: {0: {0: {0: {b: "bar"}}}},
|
|
maybeStr: i === 0 ? "2" : 2,
|
|
"$.1": "$.2",
|
|
"$.2": "bar",
|
|
items: [0, 1, 2, 3, 4],
|
|
lookupField: "field" + i,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Test that $getField fails with a document missing named arguments.
|
|
assertGetFieldFailedWithCode({input: {a: "b"}}, 3041702);
|
|
assertGetFieldFailedWithCode({field: "a"}, 3041703);
|
|
|
|
// Test that $getField fails with a document with one or more arguments of incorrect type.
|
|
assertGetFieldFailedWithCode({field: true, input: {a: "b"}}, [5654602, 3041704]);
|
|
assertGetFieldFailedWithCode({field: {"a": 1}, input: {"a": 1}}, 3041704);
|
|
assertGetFieldFailedWithCode(5, [5654602, 3041704]);
|
|
assertGetFieldFailedWithCode(true, [5654602, 3041704]);
|
|
assertGetFieldFailedWithCode({field: null, input: {"a": 1}}, [5654602, 3041704]);
|
|
|
|
// Test that $getField fails with a document with invalid arguments.
|
|
assertGetFieldFailedWithCode({field: "a", input: {a: "b"}, unknown: true}, 3041701);
|
|
|
|
// Test that $getField fails when 'field' argument is an arbitrary expression that doesn't evaluate
|
|
// to a string.
|
|
assertGetFieldFailedWithCode({$add: [2, 3]}, 3041704);
|
|
assertGetFieldFailedWithCode({$const: true}, [5654602, 3041704]);
|
|
assertGetFieldFailedWithCode({$const: {"a": 1}}, [5654602, 3041704]);
|
|
assertGetFieldFailedWithCode({field: {$const: []}, input: {"a": 1}}, [5654602, 3041704]);
|
|
|
|
// Test that $getField returns the correct value from the provided object.
|
|
assertGetFieldResultsEq({field: "a", input: {a: "b"}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$concat: ["a", "b"]}, input: {ab: "b"}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$cond: [false, null, "x"]}, input: {x: "b"}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$cond: [{$eq: ["$y", 9]}, null, "x"]}, input: {x: "b"}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
|
|
// Test that $getField returns the correct value from the $$CURRENT object.
|
|
assertGetFieldResultsEq("a", [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
|
|
assertGetFieldResultsEq("a$b", [
|
|
{_id: 0, test: "foo"},
|
|
{_id: 1, test: "foo"},
|
|
]);
|
|
assertGetFieldResultsEq("a.b", [
|
|
{_id: 0, test: "bar"},
|
|
{_id: 1, test: "bar"},
|
|
]);
|
|
assertGetFieldResultsEq("x", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq("a.$b", [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq(".xy", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq(".$xz", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq("..zz", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq({$const: "$a"}, [
|
|
{_id: 0, test: 10},
|
|
{_id: 1, test: 10},
|
|
]);
|
|
assertGetFieldResultsEq({$const: "$x.$y"}, [
|
|
{_id: 0, test: 20},
|
|
{_id: 1, test: 20},
|
|
]);
|
|
assertGetFieldResultsEq({$const: "$x..$y"}, [
|
|
{_id: 0, test: {"$a": 1, "$b..$c": 2}},
|
|
{_id: 1, test: {"$a": 1, "$b..$c": 2}},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$f"}, input: "$e"}, [
|
|
{_id: 0, test: 30},
|
|
{_id: 1, test: 30},
|
|
]);
|
|
|
|
// Test that $getField returns the correct value when 'field' argument contains a field reference
|
|
// evaluating to a string value.
|
|
assertGetFieldResultsEq("$foo", [
|
|
{_id: 0, test: "baz"},
|
|
{_id: 1, test: "baz"},
|
|
]);
|
|
assertGetFieldResultsEq({$concat: ["$letterB", "a", "r"]}, [
|
|
{_id: 0, test: "baz"},
|
|
{_id: 1, test: "baz"},
|
|
]);
|
|
|
|
// Test that $getField fails when 'field' argument is a field reference that evaluates to string for
|
|
// some but not all documents.
|
|
assertGetFieldFailedWithCode("$maybeStr", 3041704);
|
|
|
|
// Test that $getField fails when 'field' argument is a field reference evaluating to a non-string
|
|
// value.
|
|
assertGetFieldFailedWithCode("$a", 3041704);
|
|
assertGetFieldFailedWithCode("$a.b", 3041704);
|
|
assertGetFieldFailedWithCode("$$CURRENT.a", 3041704);
|
|
assertGetFieldFailedWithCode("$x", 3041704);
|
|
assertGetFieldFailedWithCode("$missing", 3041704);
|
|
assertGetFieldFailedWithCode("$arr.0.1.2.x", 3041704);
|
|
|
|
// Test that $getField returns the correct value when 'field' argument is a dynamic expression
|
|
// evaluation to a string value.
|
|
assertGetFieldResultsEq({$concat: ["b", "a", "r"]}, [
|
|
{_id: 0, test: "baz"},
|
|
{_id: 1, test: "baz"},
|
|
]);
|
|
assertGetFieldResultsEq({$concat: [{$getField: {$const: "$b"}}, "a", "r"]}, [
|
|
{_id: 0, test: "baz"},
|
|
{_id: 1, test: "baz"},
|
|
]);
|
|
assertGetFieldResultsEq({$getField: {$getField: {$const: "$.1"}}}, [
|
|
{_id: 0, test: "baz"},
|
|
{_id: 1, test: "baz"},
|
|
]);
|
|
assertGetFieldResultsEq("$c.d", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq("$arr.0.0.0.b", [
|
|
{_id: 0, test: "baz"},
|
|
{_id: 1, test: "baz"},
|
|
]);
|
|
|
|
// Test that $getField fails when 'field' argument is a dynamic expression evaluating to a
|
|
// non-string value.
|
|
assertGetFieldFailedWithCode({$add: [1, 2]}, 3041704);
|
|
assertGetFieldFailedWithCode({$mod: [5, 10]}, 3041704);
|
|
assertGetFieldFailedWithCode({$month: "$$NOW"}, 3041704);
|
|
assertGetFieldFailedWithCode({$ne: ["$x", 1]}, 3041704);
|
|
assertGetFieldFailedWithCode({$toDouble: "2.5"}, 3041704);
|
|
assertGetFieldFailedWithCode({$reverseArray: "$items"}, 3041704);
|
|
assertGetFieldFailedWithCode({$mergeObjects: ["$c", "$e"]}, 3041704);
|
|
assertGetFieldFailedWithCode({$mergeObjects: [null, null]}, 3041704);
|
|
|
|
// Test that $getField returns the correct value when 'field' argument contains reference to a
|
|
// system variable.
|
|
assertGetFieldResultsEq("$$CURRENT.c.d", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq("$$ROOT.c.d", [
|
|
{_id: 0, test: 0},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq({$toString: "$$NOW"}, [{_id: 0}, {_id: 1}]);
|
|
|
|
// Test that $getField fails when 'field' argument is a reference to a system variable that is of
|
|
// resolves to a non-string type or is not available.
|
|
assertGetFieldFailedWithCode("$$NOW", 3041704);
|
|
assertGetFieldFailedWithCode("$$REMOVE", 3041704);
|
|
assertGetFieldFailedWithCode("$$DESCEND", 17276);
|
|
assertGetFieldFailedWithCode("$$PRUNE", 17276);
|
|
assertGetFieldFailedWithCode("$$KEEP", 17276);
|
|
assertGetFieldFailedWithCode("$$USER_ROLES", 3041704);
|
|
// Error code depends on presence of the enterprise module.
|
|
assertGetFieldFailedWithCode("$$SEARCH_META", [6347902, 6347903]);
|
|
// $$CLUSTER_TIME is only available on replica sets and sharded clusters.
|
|
assertGetFieldFailedWithCode("$$CLUSTER_TIME", [3041704, 51144, 10071200]);
|
|
|
|
// Test that $getField treats dotted fields as key literals instead of field paths. Note that it is
|
|
// necessary to use $const in places, otherwise object field validation would reject some of these
|
|
// field names.
|
|
assertGetFieldResultsEq({field: "a.b", input: {$const: {"a.b": "b"}}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: ".ab", input: {$const: {".ab": "b"}}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: "ab.", input: {$const: {"ab.": "b"}}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: "a.b.c", input: {$const: {"a.b.c": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: "a.b.c", input: {a: {b: {c: 5}}}}, [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
|
|
assertGetFieldResultsEq({field: "d", input: {$getField: "c"}}, [
|
|
{_id: 0, "test": "x"},
|
|
{_id: 1, "test": "x"},
|
|
]);
|
|
|
|
// Test that $getField works with fields that contain '$'.
|
|
assertGetFieldResultsEq({field: "a$b", input: {"a$b": "b"}}, [
|
|
{_id: 0, test: "b"},
|
|
{_id: 1, test: "b"},
|
|
]);
|
|
assertGetFieldResultsEq({field: "a$b.b", input: {$const: {"a$b.b": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "a$b.b"}, input: {$const: {"a$b.b": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$b.b"}, input: {$const: {"$b.b": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$b"}, input: {$const: {"$b": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$.ab"}, input: {$const: {"$.ab": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$$xz"}, input: {$const: {"$$xz": 5}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
|
|
// Test null and missing cases.
|
|
assertGetFieldResultsEq({field: "a", input: null}, [
|
|
{_id: 0, test: null},
|
|
{_id: 1, test: null},
|
|
]);
|
|
assertGetFieldResultsEq({field: "a", input: {b: 2, c: 3}}, [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
|
|
assertGetFieldResultsEq({field: "a", input: {a: null, b: 2, c: 3}}, [
|
|
{_id: 0, test: null},
|
|
{_id: 1, test: null},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$a"}, input: {$const: {"$a": null, b: 2, c: 3}}}, [
|
|
{_id: 0, test: null},
|
|
{_id: 1, test: null},
|
|
]);
|
|
assertGetFieldResultsEq({field: "a", input: {}}, [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
|
|
assertGetFieldResultsEq({$const: "$v.."}, [
|
|
{_id: 0, test: null},
|
|
{_id: 1, test: null},
|
|
]);
|
|
assertGetFieldResultsEq({$const: "$u.."}, [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
|
|
assertGetFieldResultsEq({field: "doesNotExist2", input: {$getField: "doesNotExist1"}}, [{_id: 0}, {_id: 1}]);
|
|
assertGetFieldResultsEq({field: "x", input: {$getField: "doesNotExist"}}, [{_id: 0}, {_id: 1}]);
|
|
assertGetFieldResultsEq({field: "a", input: true}, [{_id: 0}, {_id: 1}]);
|
|
|
|
// Test case where $getField stages are nested.
|
|
assertGetFieldResultsEq({field: "a", input: {$getField: {field: "b.c", input: {$const: {"b.c": {a: 5}}}}}}, [
|
|
{_id: 0, test: 5},
|
|
{_id: 1, test: 5},
|
|
]);
|
|
assertGetFieldResultsEq({field: "x", input: {$getField: {field: "b.c", input: {$const: {"b.c": {a: 5}}}}}}, [
|
|
{_id: 0},
|
|
{_id: 1},
|
|
]);
|
|
assertGetFieldResultsEq({field: "a", input: {$getField: {field: "b.d", input: {$const: {"b.c": {a: 5}}}}}}, [
|
|
{_id: 0},
|
|
{_id: 1},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$a"}, input: {$getField: {$const: "$x..$y"}}}, [
|
|
{_id: 0, test: 1},
|
|
{_id: 1, test: 1},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$b..$c"}, input: {$getField: {$const: "$x..$y"}}}, [
|
|
{_id: 0, test: 2},
|
|
{_id: 1, test: 2},
|
|
]);
|
|
|
|
// Test case when a dotted/dollar path is within an array.
|
|
assertGetFieldResultsEq(
|
|
{
|
|
field: {$const: "a$b"},
|
|
input: {$arrayElemAt: [[{$const: {"a$b": 1}}, {$const: {"a$b": 2}}], 0]},
|
|
},
|
|
[
|
|
{_id: 0, test: 1},
|
|
{_id: 1, test: 1},
|
|
],
|
|
);
|
|
assertGetFieldResultsEq(
|
|
{
|
|
field: {$const: "a.."},
|
|
input: {$arrayElemAt: [[{$const: {"a..": 1}}, {$const: {"a..": 2}}], 1]},
|
|
},
|
|
[
|
|
{_id: 0, test: 2},
|
|
{_id: 1, test: 2},
|
|
],
|
|
);
|
|
assertGetFieldResultsEq({field: {$const: "$a"}, input: {$arrayElemAt: ["$f", 0]}}, [
|
|
{_id: 0, test: 41},
|
|
{_id: 1, test: 41},
|
|
]);
|
|
assertGetFieldResultsEq({field: {$const: "$b.."}, input: {$arrayElemAt: ["$f", 1]}}, [
|
|
{_id: 0, test: 42},
|
|
{_id: 1, test: 42},
|
|
]);
|
|
|
|
// Test $getField expression with other pipeline stages.
|
|
|
|
assertPipelineResultsEq(
|
|
[
|
|
{$match: {$expr: {$eq: [{$getField: "_id"}, {$getField: ".$xz"}]}}},
|
|
{$project: {aa: {$getField: ".$xz"}, "_id": 1}},
|
|
],
|
|
[
|
|
{_id: 0, aa: 0},
|
|
{_id: 1, aa: 1},
|
|
],
|
|
);
|
|
|
|
assertPipelineResultsEq([{$match: {$expr: {$ne: [{$getField: "_id"}, {$getField: ".$xz"}]}}}], []);
|
|
assertPipelineResultsEq(
|
|
[
|
|
{$match: {$expr: {$ne: [{$getField: "_id"}, {$getField: "a.b"}]}}},
|
|
{$project: {"a": {$getField: "x"}, "b": {$getField: {$const: "a.b"}}}},
|
|
],
|
|
[
|
|
{_id: 0, a: 0, b: "bar"},
|
|
{_id: 1, a: 1, b: "bar"},
|
|
],
|
|
);
|
|
|
|
assertPipelineResultsEq(
|
|
[{$addFields: {aa: {$getField: {$const: "a.b"}}}}, {$project: {aa: 1, _id: 1}}],
|
|
[
|
|
{_id: 0, aa: "bar"},
|
|
{_id: 1, aa: "bar"},
|
|
],
|
|
);
|
|
|
|
assertPipelineResultsEq(
|
|
[{$bucket: {groupBy: {$getField: {$const: "a.b"}}, boundaries: ["aaa", "bar", "zzz"]}}], // We should get one bucket here ("bar") with two documents.
|
|
[{_id: "bar", count: 2}],
|
|
);
|
|
assertPipelineResultsEq(
|
|
[
|
|
{
|
|
$bucket: {groupBy: {$getField: "x"}, boundaries: [0, 1, 2, 3, 4]},
|
|
},
|
|
], // We should get two buckets here for the two possible values of x.
|
|
[
|
|
{_id: 0, count: 1},
|
|
{_id: 1, count: 1},
|
|
],
|
|
);
|
|
|
|
// Test $getField expression with uncorrelated $lookup and $project with dynamic field expression.
|
|
{
|
|
const coll2 = db.expression_get_field_lookup_test;
|
|
coll2.drop();
|
|
|
|
assert.commandWorked(coll2.insert({_id: 0, field0: "0", field1: "1"}));
|
|
|
|
assertPipelineResultsEq(
|
|
[
|
|
{
|
|
$lookup: {
|
|
let: {
|
|
// Either "field0" or "field1"
|
|
field: "$lookupField",
|
|
},
|
|
from: "expression_get_field_lookup_test",
|
|
pipeline: [
|
|
{
|
|
$project: {
|
|
field: {$getField: {field: "$$field", input: "$$CURRENT"}},
|
|
},
|
|
},
|
|
],
|
|
as: "result",
|
|
},
|
|
},
|
|
{$project: {result: 1}},
|
|
],
|
|
[
|
|
{_id: 0, result: [{_id: 0, field: "0"}]},
|
|
{_id: 1, result: [{_id: 0, field: "1"}]},
|
|
],
|
|
);
|
|
|
|
coll2.drop();
|
|
}
|