SERVER-108478 JS formatted by prettier and remove clang-format (#39656)

GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
This commit is contained in:
Zac 2025-08-21 10:17:44 -07:00 committed by MongoDB Bot
parent 601555914b
commit 591928c619
6489 changed files with 268119 additions and 223051 deletions

View File

@ -1,3 +1,6 @@
# SERVER-108478 Prettier JS format
1adc32cdd5d6f7b8a9283d328bf7aa7d32caba6b
# SERVER-72197 Clang format v12.0.1
f63255ee677ecae5896d6f35dd712ed60ae8c39a
ff917ea96cf91f76bbd73b0d88d626a8523c5ec0

View File

@ -12,6 +12,7 @@
!*.yml
!*.yaml
!*.idl
!*.js
# Opt in specific JS file paths
!*.mjs
@ -45,4 +46,4 @@ build
bazel-*
# Streams specific
src/mongo/db/modules/enterprise/src/streams/third_party/mongocxx/dist
src/mongo/db/modules/enterprise/src/streams/third_party/mongocxx/dist

View File

@ -4,7 +4,9 @@
{
"files": ["*.js", "*.mjs"],
"options": {
"tabWidth": 4
"tabWidth": 4,
"printWidth": 120,
"quoteProps": "preserve"
}
},
{

View File

@ -8,6 +8,7 @@
"clangd.checkUpdates": true,
"clangd.path": "${workspaceFolder}/buildscripts/clangd_vscode.sh",
"clang-format.executable": "${workspaceRoot}/bazel-out/../../../external/mongo_toolchain_v5/v5/bin/clang-format",
"prettier.prettierPath": "bazel-bin/node_modules/.aspect_rules_js/prettier@3.4.2/node_modules/prettier",
"clang-tidy.executable": "buildscripts/clang_tidy_vscode.py",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
@ -43,7 +44,7 @@
"editor.formatOnSave": true,
},
"[javascript]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
},
"[python]": {

View File

@ -29,11 +29,12 @@ py_binary(
format_multirun(
name = "rules_lint_format",
c = "//:clang_format",
cc = "//:clang_format",
#c = "//:clang_format",
#cc = "//:clang_format",
css = "//:prettier",
graphql = "//:prettier",
html = "//:prettier",
javascript = "//:prettier",
markdown = "//:prettier",
shell = "@shfmt//:shfmt",
sql = "//:prettier",

View File

@ -1,11 +1,6 @@
const stopList = [
"print",
"printjson",
"printjsononeline",
];
const stopList = ["print", "printjson", "printjsononeline"];
export default {
meta: {
type: "problem",
docs: {
@ -16,20 +11,18 @@ export default {
create(context) {
return {
CallExpression: function(node) {
if (node.callee.type == "Identifier" &&
stopList.some(fn => fn == node.callee.name)) {
context.report(
{
node,
message: `Direct use of '${
node.callee
.name}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn'
CallExpression: function (node) {
if (node.callee.type == "Identifier" && stopList.some((fn) => fn == node.callee.name)) {
context.report({
node,
message: `Direct use of '${
node.callee.name
}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn'
More about rules configuration: https://eslint.org/docs/latest/use/configure/rules`,
});
});
}
}
},
};
}
},
};

View File

@ -1,11 +1,11 @@
const print_fns = [
'jsTestLog',
'jsTest.log',
'jsTest.log.info',
'jsTest.log.debug',
'jsTest.log.warning',
'jsTest.log.error',
'print',
"jsTestLog",
"jsTest.log",
"jsTest.log.info",
"jsTest.log.debug",
"jsTest.log.warning",
"jsTest.log.error",
"print",
];
function flattenMemberExpressionName(expr) {
@ -19,7 +19,6 @@ function flattenMemberExpressionName(expr) {
}
export default {
meta: {
type: "problem",
docs: {
@ -30,43 +29,31 @@ export default {
create(context) {
return {
CallExpression: function(node) {
CallExpression: function (node) {
if (node.callee.type == "MemberExpression") {
node.callee.name = flattenMemberExpressionName(node.callee);
} else if (node.callee.type != "Identifier") return;
} else if (node.callee.type != "Identifier")
return;
if (print_fns.every((name) => name != node.callee.name)) return;
if (print_fns.every((name) => name != node.callee.name))
return;
node.arguments.forEach((arg) => {
if (arg.type != "CallExpression") return;
node.arguments.forEach(arg => {
if (arg.type != "CallExpression")
return;
if (arg.callee.type != "Identifier") return;
if (arg.callee.type != "Identifier")
return;
if (arg.callee.name != "tojson" && arg.callee.name != "tojsononeline") return;
if (arg.callee.name != "tojson" && arg.callee.name != "tojsononeline")
return;
context.report(
{
node,
message: `Calling ${arg.callee.name}() as a parameter of ${
node.callee
.name}(). Consider using toJsonForLog() instead or disable this rule by adding '// eslint-disable-next-line mongodb/no-printing-tojson'`,
fix(fixer) {
return fixer.replaceTextRange(
[
arg.callee.start,
arg.callee.end,
],
"toJsonForLog");
}
});
context.report({
node,
message: `Calling ${arg.callee.name}() as a parameter of ${
node.callee.name
}(). Consider using toJsonForLog() instead or disable this rule by adding '// eslint-disable-next-line mongodb/no-printing-tojson'`,
fix(fixer) {
return fixer.replaceTextRange([arg.callee.start, arg.callee.end], "toJsonForLog");
},
});
});
}
},
};
}
},
};

View File

@ -1 +1 @@
assert.eq(true, false)
assert.eq(true, false);

View File

@ -1,6 +1,5 @@
(function() {
"use strict";
(function () {
"use strict";
writeFile(TestData.outputLocation,
tojson(db.adminCommand("getCmdLineOpts")["parsed"]["setParameter"]));
}());
writeFile(TestData.outputLocation, tojson(db.adminCommand("getCmdLineOpts")["parsed"]["setParameter"]));
})();

View File

@ -1,9 +1,8 @@
(function() {
"use strict";
(function () {
"use strict";
print("DEBUG BUILDINFO");
printjson(db.adminCommand("buildInfo"));
print("DEBUG BUILDINFO");
printjson(db.adminCommand("buildInfo"));
writeFile(TestData.outputLocation,
tojson(db.adminCommand("getCmdLineOpts")["parsed"]["setParameter"]));
}());
writeFile(TestData.outputLocation, tojson(db.adminCommand("getCmdLineOpts")["parsed"]["setParameter"]));
})();

View File

@ -1,12 +1,4 @@
import {
after,
afterEach,
before,
beforeEach,
describe,
it,
} from "jstests/libs/mochalite.js";
import {after, afterEach, before, beforeEach, describe, it} from "jstests/libs/mochalite.js";
// validate test execution and ordering among sync and async content
@ -27,7 +19,7 @@ after(async () => log("after2"));
it("test1", () => log("----test1"));
it("test2", async () => log("----test2"));
describe("describe", function() {
describe("describe", function () {
before(() => log("----describe before1"));
before(async () => log("----describe before2"));

View File

@ -1,4 +1,3 @@
import {describe, it} from "jstests/libs/mochalite.js";
describe("fruits", () => {

View File

@ -1,13 +1,12 @@
import {describe, it} from "jstests/libs/mochalite.js";
const pass = () => assert(true, 'pass');
const fail = () => assert(false, 'fail');
const pass = () => assert(true, "pass");
const fail = () => assert(false, "fail");
it("test1", pass);
it("test2", pass);
describe("describe", function() {
describe("describe", function () {
it("test3", pass);
it("test4", fail); // This test will fail
it("test4", fail); // This test will fail
it("test5", pass);
});

View File

@ -1 +1 @@
(function() {})();
(function () {})();

View File

@ -2,4 +2,4 @@
* @tags: [featureFlagToaster]
*/
(function() {})();
(function () {})();

View File

@ -2,4 +2,4 @@
* @tags: [featureFlagFryer]
*/
(function() {})();
(function () {})();

View File

@ -1 +1 @@
(function() {})();
(function () {})();

View File

@ -4,11 +4,13 @@ const adminDB = db.getSiblingDB("admin");
assert.commandWorked(t.insert({_id: 1, x: 1}));
assert.commandWorked(adminDB.runCommand({
configureFailPoint: "failCommand",
mode: "alwaysOn",
data: {
errorCode: ErrorCodes.InternalError,
failCommands: ["validate"],
}
}));
assert.commandWorked(
adminDB.runCommand({
configureFailPoint: "failCommand",
mode: "alwaysOn",
data: {
errorCode: ErrorCodes.InternalError,
failCommands: ["validate"],
},
}),
);

View File

@ -17,11 +17,7 @@ const compat = new FlatCompat({
export default [
...compat.extends("eslint:recommended"),
{
ignores: [
"src/mongo/gotools/*",
"**/*.tpl.js",
"jstests/third_party/**/*.js",
],
ignores: ["src/mongo/gotools/*", "**/*.tpl.js", "jstests/third_party/**/*.js"],
},
{
languageOptions: {
@ -336,13 +332,13 @@ export default [
"no-redeclare": 0,
"no-constant-condition": 0,
"no-loss-of-precision": 0,
"no-unexpected-multiline": 0,
semi: 2,
"no-restricted-syntax": [
"error",
{
message:
"Invalid load call. Please convert your library to a module and import it instead.",
message: "Invalid load call. Please convert your library to a module and import it instead.",
selector: 'CallExpression > Identifier[name="load"]',
},
],
@ -381,11 +377,7 @@ export default [
},
{
// Shell-specific: extra strict!
files: [
"jstests/core/js/**",
"jstests/noPassthrough/shell/**",
"src/mongo/shell/**",
],
files: ["jstests/core/js/**", "jstests/noPassthrough/shell/**", "src/mongo/shell/**"],
rules: {
"no-var": 2,
"no-unused-vars": [2, {args: "none", caughtErrors: "none"}],

View File

@ -676,37 +676,6 @@ tasks:
target: >-
//buildscripts:resmoke -- --help
- name: lint_clang_format
tags:
[
"assigned_to_jira_team_devprod_build",
"development_critical_single_variant",
"lint",
]
commands:
- command: timeout.update
params:
# 20 mins
exec_timeout_secs: 1200
- func: "f_expansions_write"
- command: manifest.load
- func: "git get project and add git tag"
- func: "f_expansions_write"
- func: "kill processes"
- func: "cleanup environment"
- func: "set up venv"
- func: "upload pip requirements"
- func: "get engflow creds"
- command: subprocess.exec
type: test
params:
binary: bash
args:
- "./src/evergreen/run_python_script_with_report.sh"
- "lint-clang-format"
- "buildscripts/clang_format.py"
- "lint-all"
- name: bazel_run_lint
tags:
[

View File

@ -12,30 +12,32 @@ for (const word of ["hello", "world", "world", "hello", "hi"]) {
}
const command = {
aggregate: 'accumulator_js',
aggregate: "accumulator_js",
cursor: {},
pipeline: [{
$group: {
_id: "$word",
wordCount: {
$accumulator: {
init: function() {
return 0;
pipeline: [
{
$group: {
_id: "$word",
wordCount: {
$accumulator: {
init: function () {
return 0;
},
accumulateArgs: ["$val"],
accumulate: function (state, val) {
return state + val;
},
merge: function (state1, state2) {
return state1 + state2;
},
finalize: function (state) {
return state;
},
},
accumulateArgs: ["$val"],
accumulate: function(state, val) {
return state + val;
},
merge: function(state1, state2) {
return state1 + state2;
},
finalize: function(state) {
return state;
}
}
}
}
}],
},
},
},
],
};
let expectedResults = [
@ -65,31 +67,37 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
// Test a finalizer other than the identity function. Finalizers are useful when the intermediate
// state needs to be a different format from the final result.
res = assert.commandWorked(db.runCommand(Object.merge(command, {
pipeline: [{
$group: {
_id: 1,
avgWordLen: {
$accumulator: {
init: function() {
return {count: 0, sum: 0};
res = assert.commandWorked(
db.runCommand(
Object.merge(command, {
pipeline: [
{
$group: {
_id: 1,
avgWordLen: {
$accumulator: {
init: function () {
return {count: 0, sum: 0};
},
accumulateArgs: [{$strLenCP: "$word"}],
accumulate: function ({count, sum}, wordLen) {
return {count: count + 1, sum: sum + wordLen};
},
merge: function (s1, s2) {
return {count: s1.count + s2.count, sum: s1.sum + s2.sum};
},
finalize: function ({count, sum}) {
return sum / count;
},
lang: "js",
},
},
},
accumulateArgs: [{$strLenCP: "$word"}],
accumulate: function({count, sum}, wordLen) {
return {count: count + 1, sum: sum + wordLen};
},
merge: function(s1, s2) {
return {count: s1.count + s2.count, sum: s1.sum + s2.sum};
},
finalize: function({count, sum}) {
return sum / count;
},
lang: 'js',
}
},
}
}],
})));
},
],
}),
),
);
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, avgWordLen: 22 / 5}]), res.cursor);
// Test that a null word is considered a valid value.
@ -102,204 +110,230 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
// This is similar to how most other agg operators work.
assert(db.accumulator_js.drop());
assert.commandWorked(db.accumulator_js.insert({sentinel: 1}));
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
return [];
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {
return [];
},
accumulateArgs: ["$no_such_field"],
accumulate: function (state, value) {
return state.concat([value]);
},
merge: function (s1, s2) {
return s1.concat(s2);
},
lang: "js",
},
accumulateArgs: ["$no_such_field"],
accumulate: function(state, value) {
return state.concat([value]);
},
merge: function(s1, s2) {
return s1.concat(s2);
},
lang: 'js',
}
}
}
}];
},
},
},
];
res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: [null]}]), res.cursor);
// Test that initArgs must evaluate to an array.
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {},
initArgs: {$const: 5},
accumulateArgs: [],
accumulate: function() {},
merge: function() {},
lang: 'js',
}
}
}
}];
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {},
initArgs: {$const: 5},
accumulateArgs: [],
accumulate: function () {},
merge: function () {},
lang: "js",
},
},
},
},
];
assert.commandFailedWithCode(db.runCommand(command), 4544711);
// Test that initArgs is passed to init.
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function(str1, str2) {
return "initial_state_set_from_" + str1 + "_and_" + str2;
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function (str1, str2) {
return "initial_state_set_from_" + str1 + "_and_" + str2;
},
initArgs: ["ABC", "DEF"],
accumulateArgs: [],
accumulate: function (state) {
return state;
},
merge: function (s1, s2) {
return s1;
},
lang: "js",
},
initArgs: ["ABC", "DEF"],
accumulateArgs: [],
accumulate: function(state) {
return state;
},
merge: function(s1, s2) {
return s1;
},
lang: 'js',
}
}
}
}];
},
},
},
];
res = assert.commandWorked(db.runCommand(command));
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: "initial_state_set_from_ABC_and_DEF"}]),
res.cursor);
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: "initial_state_set_from_ABC_and_DEF"}]), res.cursor);
// Test that when initArgs errors, we fail gracefully, and don't call init.
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
throw 'init should not be called';
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {
throw "init should not be called";
},
// Use $cond to thwart constant folding, to ensure we are testing evaluate rather
// than optimize.
initArgs: {$add: {$cond: ["$foo", "", ""]}},
accumulateArgs: [],
accumulate: function () {
throw "accumulate should not be called";
},
merge: function () {
throw "merge should not be called";
},
lang: "js",
},
// Use $cond to thwart constant folding, to ensure we are testing evaluate rather
// than optimize.
initArgs: {$add: {$cond: ["$foo", "", ""]}},
accumulateArgs: [],
accumulate: function() {
throw 'accumulate should not be called';
},
merge: function() {
throw 'merge should not be called';
},
lang: 'js',
}
}
}
}];
},
},
},
];
// ErrorCodes.TypeMismatch means "$add only supports numeric or date types". Code 16554 represented
// a type mismatch before 6.1 for this specific check.
assert.commandFailedWithCode(db.runCommand(command), [16554, ErrorCodes.TypeMismatch]);
// Test that initArgs can have a different length per group.
assert(db.accumulator_js.drop());
assert.commandWorked(db.accumulator_js.insert([
{_id: 1, a: ['A', 'B', 'C']},
{_id: 2, a: ['A', 'B', 'C']},
{_id: 3, a: ['X', 'Y']},
{_id: 4, a: ['X', 'Y']},
]));
command.pipeline = [{
$group: {
_id: {a: "$a"},
value: {
$accumulator: {
init: function(...args) {
return args.toString();
assert.commandWorked(
db.accumulator_js.insert([
{_id: 1, a: ["A", "B", "C"]},
{_id: 2, a: ["A", "B", "C"]},
{_id: 3, a: ["X", "Y"]},
{_id: 4, a: ["X", "Y"]},
]),
);
command.pipeline = [
{
$group: {
_id: {a: "$a"},
value: {
$accumulator: {
init: function (...args) {
return args.toString();
},
initArgs: "$a",
accumulateArgs: [],
accumulate: function (state) {
return state;
},
merge: function (s1, s2) {
return s1;
},
lang: "js",
},
initArgs: "$a",
accumulateArgs: [],
accumulate: function(state) {
return state;
},
merge: function(s1, s2) {
return s1;
},
lang: 'js',
}
}
}
}];
},
},
},
];
res = assert.commandWorked(db.runCommand(command));
assert(
resultsEq(res.cursor.firstBatch,
[{_id: {a: ['A', 'B', 'C']}, value: "A,B,C"}, {_id: {a: ['X', 'Y']}, value: "X,Y"}]),
res.cursor);
resultsEq(res.cursor.firstBatch, [
{_id: {a: ["A", "B", "C"]}, value: "A,B,C"},
{_id: {a: ["X", "Y"]}, value: "X,Y"},
]),
res.cursor,
);
// Test that accumulateArgs must evaluate to an array.
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {},
accumulateArgs: {$const: 5},
accumulate: function(state, value) {},
merge: function(s1, s2) {},
lang: 'js',
}
}
}
}];
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {},
accumulateArgs: {$const: 5},
accumulate: function (state, value) {},
merge: function (s1, s2) {},
lang: "js",
},
},
},
},
];
assert.commandFailedWithCode(db.runCommand(command), 4544712);
// Test that accumulateArgs can have more than one element.
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {},
accumulateArgs: ["ABC", "DEF"],
accumulate: function(state, str1, str2) {
return str1 + str2;
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {},
accumulateArgs: ["ABC", "DEF"],
accumulate: function (state, str1, str2) {
return str1 + str2;
},
merge: function (s1, s2) {
return s1 || s2;
},
lang: "js",
},
merge: function(s1, s2) {
return s1 || s2;
},
lang: 'js',
}
}
}
}];
res = assert.commandWorked(db.runCommand(command));
expectedResults = [
{_id: 1, value: "ABCDEF"},
},
},
},
];
res = assert.commandWorked(db.runCommand(command));
expectedResults = [{_id: 1, value: "ABCDEF"}];
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
// Test that accumulateArgs can have a different length per document.
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
return [];
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {
return [];
},
accumulateArgs: "$a",
accumulate: function (state, ...values) {
state.push(values);
state.sort();
return state;
},
merge: function (s1, s2) {
return s1.concat(s2);
},
lang: "js",
},
accumulateArgs: "$a",
accumulate: function(state, ...values) {
state.push(values);
state.sort();
return state;
},
merge: function(s1, s2) {
return s1.concat(s2);
},
lang: 'js',
}
}
}
}];
},
},
},
];
res = assert.commandWorked(db.runCommand(command));
expectedResults = [
{_id: 1, value: [['A', 'B', 'C'], ['A', 'B', 'C'], ['X', 'Y'], ['X', 'Y']]},
{
_id: 1,
value: [
["A", "B", "C"],
["A", "B", "C"],
["X", "Y"],
["X", "Y"],
],
},
];
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
@ -309,36 +343,36 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
// to JS as usual.
assert(db.accumulator_js.drop());
assert.commandWorked(db.accumulator_js.insert({}));
command.pipeline = [{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
return null;
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function () {
return null;
},
accumulateArgs: [
null,
"$no_such_field",
{$let: {vars: {not_an_object: 5}, in: "$not_an_object.field"}},
],
accumulate: function (state, ...values) {
return {
len: values.length,
types: values.map((v) => typeof v),
values: values,
};
},
merge: function (s1, s2) {
return s1 || s2;
},
lang: "js",
},
accumulateArgs: [
null,
"$no_such_field",
{$let: {vars: {not_an_object: 5}, in : "$not_an_object.field"}}
],
accumulate: function(state, ...values) {
return {
len: values.length,
types: values.map(v => typeof v),
values: values,
};
},
merge: function(s1, s2) {
return s1 || s2;
},
lang: 'js',
}
}
}
}];
res = assert.commandWorked(db.runCommand(command));
expectedResults = [
{_id: 1, value: {len: 3, types: ['object', 'object', 'object'], values: [null, null, null]}},
},
},
},
];
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
res = assert.commandWorked(db.runCommand(command));
expectedResults = [{_id: 1, value: {len: 3, types: ["object", "object", "object"], values: [null, null, null]}}];
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);

View File

@ -9,12 +9,14 @@ function runExample(groupKey, accumulatorSpec, aggregateOptions = {}) {
const aggregateCmd = {
aggregate: coll.getName(),
cursor: {},
pipeline: [{
$group: {
_id: groupKey,
accumulatedField: {$accumulator: accumulatorSpec},
}
}]
pipeline: [
{
$group: {
_id: groupKey,
accumulatedField: {$accumulator: accumulatorSpec},
},
},
],
};
return coll.runCommand(Object.assign(aggregateCmd, aggregateOptions));
}
@ -23,20 +25,20 @@ function runExample(groupKey, accumulatorSpec, aggregateOptions = {}) {
coll.drop();
assert.commandWorked(coll.insert({}));
let res = runExample(1, {
init: function() {
init: function () {
return "a".repeat(20 * 1024 * 1024);
},
accumulate: function() {
throw 'accumulate should not be called';
accumulate: function () {
throw "accumulate should not be called";
},
accumulateArgs: [],
merge: function() {
throw 'merge should not be called';
merge: function () {
throw "merge should not be called";
},
finalize: function() {
throw 'finalize should not be called';
finalize: function () {
throw "finalize should not be called";
},
lang: 'js',
lang: "js",
});
assert.commandFailedWithCode(res, [16493, 10334]);
@ -44,21 +46,21 @@ assert.commandFailedWithCode(res, [16493, 10334]);
assert(coll.drop());
assert.commandWorked(coll.insert({}));
res = runExample(1, {
init: function() {
init: function () {
const str = "a".repeat(1 * 1024 * 1024);
return Array.from({length: 20}, () => str);
},
accumulate: function() {
throw 'accumulate should not be called';
accumulate: function () {
throw "accumulate should not be called";
},
accumulateArgs: [],
merge: function() {
throw 'merge should not be called';
merge: function () {
throw "merge should not be called";
},
finalize: function() {
throw 'finalize should not be called';
finalize: function () {
throw "finalize should not be called";
},
lang: 'js',
lang: "js",
});
assert.commandFailedWithCode(res, [17260, 10334]);
@ -68,21 +70,21 @@ const oneMBString = "a".repeat(1 * 1024 * 1024);
const tenMBArray = Array.from({length: 10}, () => oneMBString);
assert.commandWorked(coll.insert([{arr: tenMBArray}, {arr: tenMBArray}]));
res = runExample(1, {
init: function() {
init: function () {
return [];
},
accumulate: function(state, input) {
accumulate: function (state, input) {
state.push(input);
return state;
},
accumulateArgs: ["$arr"],
merge: function(state1, state2) {
merge: function (state1, state2) {
return state1.concat(state2);
},
finalize: function() {
throw 'finalize should not be called';
finalize: function () {
throw "finalize should not be called";
},
lang: 'js',
lang: "js",
});
assert.commandFailedWithCode(res, [4545000]);
@ -91,30 +93,34 @@ assert(coll.drop());
assert.commandWorked(coll.insert(Array.from({length: 200}, (_, i) => ({_id: i}))));
// By grouping on _id, each group contains only 1 document. This means it creates many
// AccumulatorState instances.
res = runExample("$_id",
{
init: function() {
// Each accumulator state is big enough to be expensive, but not big enough
// to hit the BSON size limit.
return "a".repeat(1 * 1024 * 1024);
},
accumulate: function(state) {
return state;
},
accumulateArgs: [1],
merge: function(state1, state2) {
return state1;
},
finalize: function(state) {
return state.length;
},
lang: 'js',
},
{allowDiskUse: false});
res = runExample(
"$_id",
{
init: function () {
// Each accumulator state is big enough to be expensive, but not big enough
// to hit the BSON size limit.
return "a".repeat(1 * 1024 * 1024);
},
accumulate: function (state) {
return state;
},
accumulateArgs: [1],
merge: function (state1, state2) {
return state1;
},
finalize: function (state) {
return state.length;
},
lang: "js",
},
{allowDiskUse: false},
);
// If featureFlagShardFilteringDistinctScan is on, we will push this $group down to shards on
// sharded collection passthrough suites, and may run out of space during JS execution of init().
assert.commandFailedWithCode(
res, [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.JSInterpreterFailure]);
assert.commandFailedWithCode(res, [
ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
ErrorCodes.JSInterpreterFailure,
]);
// Verify that having large number of documents doesn't cause the $accumulator to run out of memory.
coll.drop();
@ -122,38 +128,44 @@ assert.commandWorked(coll.insert({groupBy: 1, largeField: "a".repeat(1000)}));
assert.commandWorked(coll.insert({groupBy: 2, largeField: "a".repeat(1000)}));
const largeAccumulator = {
$accumulator: {
init: function() {
init: function () {
return "";
},
accumulateArgs: [{fieldName: "$a"}],
accumulate: function(state, args) {
accumulate: function (state, args) {
return state + "a";
},
merge: function(state1, state2) {
merge: function (state1, state2) {
return state1 + state2;
},
finalize: function(state) {
finalize: function (state) {
return state.length;
}
}
},
},
};
res = coll.aggregate([
{$addFields: {a: {$range: [0, 1000000]}}},
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
{$group: {_id: "$groupBy", count: largeAccumulator}}
])
.toArray();
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
res = coll
.aggregate([
{$addFields: {a: {$range: [0, 1000000]}}},
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
{$group: {_id: "$groupBy", count: largeAccumulator}},
])
.toArray();
assert.sameMembers(res, [
{_id: 1, count: 1000000},
{_id: 2, count: 1000000},
]);
// With $bucket.
res =
coll.aggregate([
{$addFields: {a: {$range: [0, 1000000]}}},
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
{
$bucket:
{groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}}
}
])
.toArray();
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
res = coll
.aggregate([
{$addFields: {a: {$range: [0, 1000000]}}},
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
{
$bucket: {groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}},
},
])
.toArray();
assert.sameMembers(res, [
{_id: 1, count: 1000000},
{_id: 2, count: 1000000},
]);

View File

@ -5,30 +5,30 @@ var coll = db.accumulate_avg_sum_null;
coll.drop();
// Null cases.
assert.commandWorked(coll.insert({a: 1, b: 2, c: 'string', d: null}));
assert.commandWorked(coll.insert({a: 1, b: 2, c: "string", d: null}));
// Missing field.
var pipeline = [{$group: {_id: '$a', avg: {$avg: '$missing'}}}];
var pipeline = [{$group: {_id: "$a", avg: {$avg: "$missing"}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
// Non-numeric field.
pipeline = [{$group: {_id: '$a', avg: {$avg: '$c'}}}];
pipeline = [{$group: {_id: "$a", avg: {$avg: "$c"}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
// Field with value of null.
pipeline = [{$group: {_id: '$a', avg: {$avg: '$d'}}}];
pipeline = [{$group: {_id: "$a", avg: {$avg: "$d"}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
// All three.
coll.insert({a: 1, d: 'string'});
coll.insert({a: 1, d: "string"});
coll.insert({a: 1});
pipeline = [{$group: {_id: '$a', avg: {$avg: '$d'}}}];
pipeline = [{$group: {_id: "$a", avg: {$avg: "$d"}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
// Non-null cases.
coll.drop();
assert.commandWorked(coll.insert({a: 1, b: 2}));
pipeline = [{$group: {_id: '$a', avg: {$avg: '$b'}}}];
pipeline = [{$group: {_id: "$a", avg: {$avg: "$b"}}}];
// One field.
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 2}]);
@ -43,6 +43,6 @@ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
// Missing, null, or non-numeric fields should not error or affect the average.
assert.commandWorked(coll.insert({a: 1}));
assert.commandWorked(coll.insert({a: 1, b: 'string'}));
assert.commandWorked(coll.insert({a: 1, b: "string"}));
assert.commandWorked(coll.insert({a: 1, b: null}));
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);

View File

@ -15,55 +15,50 @@ coll.insertMany(docs);
{
// One bucket, all the documents coming out of the $bucketAuto stage should be in the same
// order as they came in (i.e. sorted by _id).
const res =
coll.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}}
])
.toArray();
const expected = [{
"_id": {"min": 0, "max": 9},
"nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42, 5, 42, 6, 42, 7, 42, 8, 42, 9, 42]
}];
assert.eq(
res,
expected,
"$bucketAuto with 1 bucket & $concatArrays does not obey sort order of incoming docs.");
const res = coll
.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}},
])
.toArray();
const expected = [
{
"_id": {"min": 0, "max": 9},
"nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42, 5, 42, 6, 42, 7, 42, 8, 42, 9, 42],
},
];
assert.eq(res, expected, "$bucketAuto with 1 bucket & $concatArrays does not obey sort order of incoming docs.");
}
{
// Two buckets, the documents in each bucket should be sorted by _id as well.
const res =
coll.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}}
])
.toArray();
const res = coll
.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}},
])
.toArray();
const expected = [
{"_id": {"min": 0, "max": 5}, "nums": [5, 42, 6, 42, 7, 42, 8, 42, 9, 42]},
{"_id": {"min": 5, "max": 9}, "nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42]}
{"_id": {"min": 5, "max": 9}, "nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42]},
];
assert.eq(
res,
expected,
"$bucketAuto with 2 buckets & $concatArrays does not obey sort order of incoming docs.");
assert.eq(res, expected, "$bucketAuto with 2 buckets & $concatArrays does not obey sort order of incoming docs.");
}
{
// Ensure that if we try to $concatArrays something that evaluates to 'missing', no value gets
// appended to the resulting array.
const res =
coll.aggregate([{
const res = coll
.aggregate([
{
"$bucketAuto": {
"groupBy": "$a",
"buckets": 1,
"output":
{"array": {"$concatArrays": {$getField: {"field": "b", "input": {a: 0}}}}}
}
}])
.toArray();
"output": {"array": {"$concatArrays": {$getField: {"field": "b", "input": {a: 0}}}}},
},
},
])
.toArray();
const expected = [{"_id": {"min": null, "max": null}, "array": []}];
assert.eq(res,
expected,
"$bucketAuto with $concatArrays does not append any missing values as null.");
assert.eq(res, expected, "$bucketAuto with $concatArrays does not append any missing values as null.");
}

View File

@ -14,40 +14,34 @@ coll.insertMany(docs);
{
// One bucket, all the documents coming out of the $bucketAuto stage should be in the same
// order as they came in (i.e. sorted by _id).
const res =
coll.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}
])
.toArray();
const expected = [{
"_id": {"min": 0, "max": 9},
"docs": [
{"_id": 0, "n": 9},
{"_id": 1, "n": 8},
{"_id": 2, "n": 7},
{"_id": 3, "n": 6},
{"_id": 4, "n": 5},
{"_id": 5, "n": 4},
{"_id": 6, "n": 3},
{"_id": 7, "n": 2},
{"_id": 8, "n": 1},
{"_id": 9, "n": 0}
]
}];
assert.eq(res,
expected,
"$bucketAuto with 1 bucket & $push does not obey sort order of incoming docs.");
const res = coll
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}])
.toArray();
const expected = [
{
"_id": {"min": 0, "max": 9},
"docs": [
{"_id": 0, "n": 9},
{"_id": 1, "n": 8},
{"_id": 2, "n": 7},
{"_id": 3, "n": 6},
{"_id": 4, "n": 5},
{"_id": 5, "n": 4},
{"_id": 6, "n": 3},
{"_id": 7, "n": 2},
{"_id": 8, "n": 1},
{"_id": 9, "n": 0},
],
},
];
assert.eq(res, expected, "$bucketAuto with 1 bucket & $push does not obey sort order of incoming docs.");
}
{
// Two buckets, the documents in each bucket should be sorted by _id as well.
const res =
coll.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}
])
.toArray();
const res = coll
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}])
.toArray();
const expected = [
{
"_id": {"min": 0, "max": 5},
@ -56,8 +50,8 @@ coll.insertMany(docs);
{"_id": 6, "n": 3},
{"_id": 7, "n": 2},
{"_id": 8, "n": 1},
{"_id": 9, "n": 0}
]
{"_id": 9, "n": 0},
],
},
{
"_id": {"min": 5, "max": 9},
@ -66,27 +60,27 @@ coll.insertMany(docs);
{"_id": 1, "n": 8},
{"_id": 2, "n": 7},
{"_id": 3, "n": 6},
{"_id": 4, "n": 5}
]
}
{"_id": 4, "n": 5},
],
},
];
assert.eq(res,
expected,
"$bucketAuto with 2 buckets & $push does not obey sort order of incoming docs.");
assert.eq(res, expected, "$bucketAuto with 2 buckets & $push does not obey sort order of incoming docs.");
}
{
// Ensure that if we try to $push something that evaluates to 'missing', no value gets pushed to
// the resulting array.
const res =
coll.aggregate([{
const res = coll
.aggregate([
{
"$bucketAuto": {
"groupBy": "$a",
"buckets": 1,
"output": {"array": {"$push": {$getField: {"field": "b", "input": {a: 0}}}}}
}
}])
.toArray();
"output": {"array": {"$push": {$getField: {"field": "b", "input": {a: 0}}}}},
},
},
])
.toArray();
const expected = [{"_id": {"min": null, "max": null}, "array": []}];
assert.eq(res, expected, "$bucketAuto with $push does not push any missing values as null.");
}

View File

@ -9,141 +9,164 @@ assert(coll.drop());
// Test that $concatArrays correctly concatenates arrays, preserves sort order and preserves order
// of the elements inside the concatenated arrays.
assert.commandWorked(coll.insert([
{_id: 0, author: "Adrian", publisher: "Pub1", books: ["Adrian Book 0", "Adrian Book 1"]},
{_id: 1, author: "Militsa", publisher: "Pub1", books: ["Militsa Book 0"]},
{_id: 2, author: "Hana", publisher: "Pub1", books: ["Hana Book 0", "Hana Book 1"]},
]));
assert.commandWorked(
coll.insert([
{_id: 0, author: "Adrian", publisher: "Pub1", books: ["Adrian Book 0", "Adrian Book 1"]},
{_id: 1, author: "Militsa", publisher: "Pub1", books: ["Militsa Book 0"]},
{_id: 2, author: "Hana", publisher: "Pub1", books: ["Hana Book 0", "Hana Book 1"]},
]),
);
let result = coll.aggregate([
{$match: {_id: {$in: [0, 1, 2]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
])
.toArray();
let result = coll
.aggregate([
{$match: {_id: {$in: [0, 1, 2]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
])
.toArray();
assert.eq(
result, [{
assert.eq(result, [
{
_id: null,
allBooks:
["Adrian Book 0", "Adrian Book 1", "Militsa Book 0", "Hana Book 0", "Hana Book 1"]
}]);
allBooks: ["Adrian Book 0", "Adrian Book 1", "Militsa Book 0", "Hana Book 0", "Hana Book 1"],
},
]);
// Redo the aggregation with the reverse sort to ensure sort order is really preserverd and we're
// not just looking at the docs in the order they are found in the collection.
result = coll.aggregate([
{$match: {_id: {$in: [0, 1, 2]}}},
{$sort: {_id: -1}},
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
])
.toArray();
result = coll
.aggregate([
{$match: {_id: {$in: [0, 1, 2]}}},
{$sort: {_id: -1}},
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
])
.toArray();
assert.eq(
result, [{
assert.eq(result, [
{
_id: null,
allBooks:
["Hana Book 0", "Hana Book 1", "Militsa Book 0", "Adrian Book 0", "Adrian Book 1"]
}]);
allBooks: ["Hana Book 0", "Hana Book 1", "Militsa Book 0", "Adrian Book 0", "Adrian Book 1"],
},
]);
// $concatArrays should concatenate arrays, and any nested arrays should remain array values.
assert.commandWorked(coll.insert({
_id: 3,
author: "Ben",
publisher: "Pub2",
books:
[["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"]
}));
result = coll.aggregate([
{$match: {_id: {$in: [1, 3]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
])
.toArray();
assert.eq(result, [{
_id: null,
allBooks: [
"Militsa Book 0",
["Ben Series 0 Book 0", "Ben Series 0 Book 1"],
["Ben Series 1 Book 0"],
"Ben Book 4"
]
}]);
assert.commandWorked(
coll.insert({
_id: 3,
author: "Ben",
publisher: "Pub2",
books: [["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"],
}),
);
result = coll
.aggregate([
{$match: {_id: {$in: [1, 3]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
])
.toArray();
assert.eq(result, [
{
_id: null,
allBooks: [
"Militsa Book 0",
["Ben Series 0 Book 0", "Ben Series 0 Book 1"],
["Ben Series 1 Book 0"],
"Ben Book 4",
],
},
]);
// $concatArrays should 'skip over' documents that do not have field.
// Importantly, do not create a null element for documents that do not have the referenced field.
assert.commandWorked(coll.insert([
{_id: 4, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
{_id: 5, author: "Santiago", publisher: "Pub3"},
{_id: 6, author: "Matt", publisher: "Pub3", books: ["Happy!"]}
]));
result = coll.aggregate([
{$match: {_id: {$in: [4, 5, 6]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
])
.toArray();
assert.commandWorked(
coll.insert([
{_id: 4, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
{_id: 5, author: "Santiago", publisher: "Pub3"},
{_id: 6, author: "Matt", publisher: "Pub3", books: ["Happy!"]},
]),
);
result = coll
.aggregate([
{$match: {_id: {$in: [4, 5, 6]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
])
.toArray();
assert.eq(result, [{_id: null, allBooks: ["Smile :)", "Happy!"]}]);
// Test for errors on non-array types ($concatArrays only supports arrays).
const notArrays = [1, "string", {object: "object"}, null];
for (const notAnArray of notArrays) {
assert.commandWorked(coll.insert([{
_id: "doesNotMatter",
author: "doesNotMatter",
publisher: "doesNotMatter",
books: notAnArray
}]));
assert.commandWorked(
coll.insert([
{
_id: "doesNotMatter",
author: "doesNotMatter",
publisher: "doesNotMatter",
books: notAnArray,
},
]),
);
assertErrorCode(coll,
[{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}],
ErrorCodes.TypeMismatch);
assertErrorCode(coll, [{$group: {_id: null, allBooks: {$concatArrays: "$books"}}}], ErrorCodes.TypeMismatch);
assert.commandWorked(coll.deleteOne({_id: "doesNotMatter"}));
}
// Basic test of $concatArrays with grouping.
result = coll.aggregate([
{$match: {_id: {$in: [0, 1, 3]}}},
{$sort: {_id: 1}},
{$group: {_id: '$publisher', booksByPublisher: {$concatArrays: '$books'}}},
{$sort: {_id: 1}}
])
.toArray();
result = coll
.aggregate([
{$match: {_id: {$in: [0, 1, 3]}}},
{$sort: {_id: 1}},
{$group: {_id: "$publisher", booksByPublisher: {$concatArrays: "$books"}}},
{$sort: {_id: 1}},
])
.toArray();
assert.eq(result, [
{_id: "Pub1", booksByPublisher: ["Adrian Book 0", "Adrian Book 1", "Militsa Book 0"]},
{
_id: "Pub2",
booksByPublisher:
[["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"]
booksByPublisher: [["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"],
},
]);
// $concatArrays dotted field.
assert(coll.drop());
assert.commandWorked(coll.insert(
[{_id: 1, a: {b: [1, 2, 3]}}, {_id: 2, a: {b: [4, 5, 6]}}, {_id: 3, a: {b: [7, 8, 9]}}]));
result = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, nums: {$concatArrays: '$a.b'}}},
])
.toArray();
assert.commandWorked(
coll.insert([
{_id: 1, a: {b: [1, 2, 3]}},
{_id: 2, a: {b: [4, 5, 6]}},
{_id: 3, a: {b: [7, 8, 9]}},
]),
);
result = coll.aggregate([{$sort: {_id: 1}}, {$group: {_id: null, nums: {$concatArrays: "$a.b"}}}]).toArray();
assert.eq(result, [{_id: null, nums: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
assert(coll.drop());
// $concatArrays dotted field, array halfway on path.
assert(coll.drop());
assert.commandWorked(coll.insert([
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
{_id: 3, a: [{b: [7, 8, 9]}]}
]));
result = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, nums: {$concatArrays: '$a.b'}}},
])
.toArray();
assert.eq(result, [{_id: null, nums: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [7, 8, 9]]}]);
assert.commandWorked(
coll.insert([
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
{_id: 3, a: [{b: [7, 8, 9]}]},
]),
);
result = coll.aggregate([{$sort: {_id: 1}}, {$group: {_id: null, nums: {$concatArrays: "$a.b"}}}]).toArray();
assert.eq(result, [
{
_id: null,
nums: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
[7, 8, 9],
],
},
]);
assert(coll.drop());
// Basic correctness test for $concatArrays used in $bucket (see bucketAuto_concatArrays.js for
@ -157,16 +180,20 @@ for (let i = 0; i < 10; i++) {
}
coll.insertMany(docs);
result = coll.aggregate([
{$sort: {_id: 1}},
{
$bucket: {
groupBy: '$_id',
boundaries: [0, 5, 10],
output: {nums: {$concatArrays: "$arr"}}
}
}
])
.toArray();
assert.eq(result, [{"_id": 0, "nums": [0, 1, 2, 3, 4]}, {"_id": 5, "nums": [5, 6, 7, 8, 9]}]);
result = coll
.aggregate([
{$sort: {_id: 1}},
{
$bucket: {
groupBy: "$_id",
boundaries: [0, 5, 10],
output: {nums: {$concatArrays: "$arr"}},
},
},
])
.toArray();
assert.eq(result, [
{"_id": 0, "nums": [0, 1, 2, 3, 4]},
{"_id": 5, "nums": [5, 6, 7, 8, 9]},
]);
assert(coll.drop());

View File

@ -9,7 +9,7 @@ coll.drop();
const sharded = FixtureHelpers.isSharded(coll);
const memoryLimitMB = sharded ? 200 : 100;
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
const bulk = coll.initializeUnorderedBulkOp();
for (let i = 0; i < memoryLimitMB + 10; i++) {
@ -21,18 +21,18 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
// so may hit the group error code before we hit ExceededMemoryLimit.
const pipeline =
[{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$concatArrays: '$bigArr'}}}];
const expectedCodes =
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
const pipeline = [{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$concatArrays: "$bigArr"}}}];
const expectedCodes = [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
assert.commandFailedWithCode(
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
expectedCodes);
expectedCodes,
);
// The $concatArrays accumulator does not support spilling to disk, so ensure that it will fail even
// when disk use is allowed.
assert.commandFailedWithCode(
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}),
expectedCodes);
expectedCodes,
);

View File

@ -14,12 +14,15 @@ let docId = 0;
const kMaxSales = 20;
let expectedFirstSales = [];
let expectedLastSales = [];
for (const states
of [{state: 'AZ', sales: 3}, {state: 'CA', sales: 2}, {state: 'NY', sales: kMaxSales}]) {
for (const states of [
{state: "AZ", sales: 3},
{state: "CA", sales: 2},
{state: "NY", sales: kMaxSales},
]) {
let firstSales;
let lastSales;
const state = states['state'];
const sales = states['sales'];
const state = states["state"];
const sales = states["sales"];
for (let i = 0; i < kMaxSales && i < sales; ++i) {
const salesAmt = i * 10;
docs.push({_id: docId++, state: state, sales: salesAmt, stateObj: {"st": state}, n: 3});
@ -36,51 +39,53 @@ for (const states
assert.commandWorked(coll.insert(docs));
{ // Test $first when grouping by a string.
const actualResults = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$first: "$sales"}}},
])
.toArray();
{
// Test $first when grouping by a string.
const actualResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$first: "$sales"}}}])
.toArray();
assert(arrayEq(expectedFirstSales, actualResults),
() => "expected " + tojson(expectedFirstSales) + " actual " + tojson(actualResults));
assert(
arrayEq(expectedFirstSales, actualResults),
() => "expected " + tojson(expectedFirstSales) + " actual " + tojson(actualResults),
);
}
{ // Test $first when grouping by an object.
const actualResults = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: "$stateObj", sales: {$first: "$sales"}}},
])
.toArray();
{
// Test $first when grouping by an object.
const actualResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$stateObj", sales: {$first: "$sales"}}}])
.toArray();
const expectedResult =
expectedFirstSales.map(doc => ({'_id': {'st': doc['_id']}, sales: doc['sales']}));
assert(arrayEq(expectedResult, actualResults),
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults));
const expectedResult = expectedFirstSales.map((doc) => ({"_id": {"st": doc["_id"]}, sales: doc["sales"]}));
assert(
arrayEq(expectedResult, actualResults),
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults),
);
}
{ // Test $last when grouping by a string.
const actualResults = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$last: "$sales"}}},
])
.toArray();
assert(arrayEq(expectedLastSales, actualResults),
() => "expected " + tojson(expectedLastSales) + " actual " + tojson(actualResults));
{
// Test $last when grouping by a string.
const actualResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$last: "$sales"}}}])
.toArray();
assert(
arrayEq(expectedLastSales, actualResults),
() => "expected " + tojson(expectedLastSales) + " actual " + tojson(actualResults),
);
}
{ // Test $last when grouping by an object.
const actualResults = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: "$stateObj", sales: {$last: "$sales"}}},
])
.toArray();
{
// Test $last when grouping by an object.
const actualResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$stateObj", sales: {$last: "$sales"}}}])
.toArray();
const expectedResult =
expectedLastSales.map(doc => ({'_id': {'st': doc['_id']}, sales: doc['sales']}));
assert(arrayEq(expectedResult, actualResults),
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults));
const expectedResult = expectedLastSales.map((doc) => ({"_id": {"st": doc["_id"]}, sales: doc["sales"]}));
assert(
arrayEq(expectedResult, actualResults),
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults),
);
}
// Basic correctness test for $first/$last used in $bucketAuto. Though $bucketAuto uses
@ -90,59 +95,61 @@ assert.commandWorked(coll.insert(docs));
// buckets than groups, it will always be the case that the min value of each bucket
// corresponds to the group key).
{ // Test $first in $bucketAuto.
let actualResults =
coll.aggregate([
{$sort: {sales: 1}},
{$bucketAuto: {groupBy: '$state', buckets: 1, output: {sales: {$first: "$sales"}}}},
{$project: {_id: "$_id.min", sales: 1}}
])
.toArray();
const expectedResults = [{_id: 'AZ', sales: 0}];
assert(orderedArrayEq(expectedResults, actualResults),
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults));
{
// Test $first in $bucketAuto.
let actualResults = coll
.aggregate([
{$sort: {sales: 1}},
{$bucketAuto: {groupBy: "$state", buckets: 1, output: {sales: {$first: "$sales"}}}},
{$project: {_id: "$_id.min", sales: 1}},
])
.toArray();
const expectedResults = [{_id: "AZ", sales: 0}];
assert(
orderedArrayEq(expectedResults, actualResults),
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
);
}
{ // Test $last in $bucketAuto.
let actualResults =
coll.aggregate([
{$sort: {sales: 1}},
{$bucketAuto: {groupBy: '$state', buckets: 1, output: {sales: {$last: "$sales"}}}},
{$project: {_id: "$_id.min", sales: 1}}
])
.toArray();
const expectedResults = [{_id: 'AZ', sales: 190}];
assert(orderedArrayEq(expectedResults, actualResults),
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults));
{
// Test $last in $bucketAuto.
let actualResults = coll
.aggregate([
{$sort: {sales: 1}},
{$bucketAuto: {groupBy: "$state", buckets: 1, output: {sales: {$last: "$sales"}}}},
{$project: {_id: "$_id.min", sales: 1}},
])
.toArray();
const expectedResults = [{_id: "AZ", sales: 190}];
assert(
orderedArrayEq(expectedResults, actualResults),
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
);
}
// Verify that an index on {_id: 1, sales: -1} will produce the expected results.
const idxSpec = {
_id: 1,
sales: -1
sales: -1,
};
assert.commandWorked(coll.createIndex(idxSpec));
{ // Test $first with index.
const actualResults = coll.aggregate(
[
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$first: "$sales"}}},
{$sort: {_id: 1}},
],
{hint: idxSpec})
.toArray();
{
// Test $first with index.
const actualResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$first: "$sales"}}}, {$sort: {_id: 1}}], {
hint: idxSpec,
})
.toArray();
assert.eq(expectedFirstSales, actualResults);
}
{ // Test $last with index.
const actualResults = coll.aggregate(
[
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$last: "$sales"}}},
{$sort: {_id: 1}},
],
{hint: idxSpec})
.toArray();
{
// Test $last with index.
const actualResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$last: "$sales"}}}, {$sort: {_id: 1}}], {
hint: idxSpec,
})
.toArray();
assert.eq(expectedLastSales, actualResults);
}

View File

@ -8,9 +8,8 @@ import {arrayEq} from "jstests/aggregation/extras/utils.js";
const coll = db[jsTestName()];
coll.drop();
const largestInt =
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
// Basic correctness tests.
let docs = [];
@ -22,15 +21,18 @@ let expectedLastThree = [];
let expectedAllResults = [];
let expectedFirstNWithInitExpr = [];
let expectedLastNWithInitExpr = [];
for (const states
of [{state: 'AZ', sales: 3}, {state: 'CA', sales: 2}, {state: 'NY', sales: kMaxSales}]) {
for (const states of [
{state: "AZ", sales: 3},
{state: "CA", sales: 2},
{state: "NY", sales: kMaxSales},
]) {
let allResults = [];
let firstThree = [];
let lastThree = [];
let firstWithInitExpr = [];
let lastWithInitExpr = [];
const state = states['state'];
const sales = states['sales'];
const state = states["state"];
const sales = states["sales"];
for (let i = 0; i < kMaxSales; ++i) {
const salesAmt = i * 10;
if (i < sales) {
@ -45,10 +47,10 @@ for (const states
lastThree.push(salesAmt);
}
if (i == 0 || (state == 'AZ' && i < defaultN)) {
if (i == 0 || (state == "AZ" && i < defaultN)) {
firstWithInitExpr.push(salesAmt);
}
if (i + 1 == sales || (state == 'AZ' && i + defaultN >= sales)) {
if (i + 1 == sales || (state == "AZ" && i + defaultN >= sales)) {
lastWithInitExpr.push(salesAmt);
}
allResults.push(salesAmt);
@ -64,125 +66,121 @@ for (const states
assert.commandWorked(coll.insert(docs));
function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
const actualFirstNResults =
coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
])
.toArray();
const actualFirstNResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}}])
.toArray();
// As these are unordered operators, we need to ensure we can deterministically test the values
// returned by firstN/lastN. As the output is not guaranteed to be in order, arrayEq is used
// instead.
assert(arrayEq(expectedFirstNResults, actualFirstNResults),
() => "expected " + tojson(expectedFirstNResults) + " actual " +
tojson(actualFirstNResults));
assert(
arrayEq(expectedFirstNResults, actualFirstNResults),
() => "expected " + tojson(expectedFirstNResults) + " actual " + tojson(actualFirstNResults),
);
const firstNResultsWithInitExpr =
coll.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: {"st": "$state"},
sales: {
$firstN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
const firstNResultsWithInitExpr = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: {"st": "$state"},
sales: {
$firstN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
])
.toArray();
},
])
.toArray();
let expectedResult = [];
expectedFirstNWithInitExpr.forEach(
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
assert(arrayEq(expectedResult, firstNResultsWithInitExpr),
() => "expected " + tojson(expectedResult) + " actual " +
tojson(firstNResultsWithInitExpr));
expectedFirstNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
assert(
arrayEq(expectedResult, firstNResultsWithInitExpr),
() => "expected " + tojson(expectedResult) + " actual " + tojson(firstNResultsWithInitExpr),
);
const firstNResultsWithInitExprAndVariableGroupId =
coll.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: "$stateObj",
sales: {
$firstN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
const firstNResultsWithInitExprAndVariableGroupId = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: "$stateObj",
sales: {
$firstN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
])
.toArray();
},
])
.toArray();
expectedResult = [];
expectedFirstNWithInitExpr.forEach(
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
assert(arrayEq(expectedResult, firstNResultsWithInitExprAndVariableGroupId),
() => "expected " + tojson(expectedResult) + " actual " +
tojson(firstNResultsWithInitExprAndVariableGroupId));
expectedFirstNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
assert(
arrayEq(expectedResult, firstNResultsWithInitExprAndVariableGroupId),
() => "expected " + tojson(expectedResult) + " actual " + tojson(firstNResultsWithInitExprAndVariableGroupId),
);
const actualLastNResults =
coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$lastN: {input: "$sales", n: n}}}},
])
.toArray();
const actualLastNResults = coll
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$lastN: {input: "$sales", n: n}}}}])
.toArray();
assert(
arrayEq(expectedLastNResults, actualLastNResults),
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNResults));
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNResults),
);
const lastNResultsWithInitExpr =
coll.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: {"st": "$state"},
sales: {
$lastN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
const lastNResultsWithInitExpr = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: {"st": "$state"},
sales: {
$lastN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
])
.toArray();
},
])
.toArray();
expectedResult = [];
expectedLastNWithInitExpr.forEach(
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
expectedLastNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
assert(
arrayEq(expectedResult, lastNResultsWithInitExpr),
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExpr));
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExpr),
);
const lastNResultsWithInitExprAndVariableGroupId =
coll.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: "$stateObj",
sales: {
$lastN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
const lastNResultsWithInitExprAndVariableGroupId = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
_id: "$stateObj",
sales: {
$lastN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
])
.toArray();
},
])
.toArray();
expectedResult = [];
expectedLastNWithInitExpr.forEach(
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
assert(arrayEq(expectedResult, lastNResultsWithInitExprAndVariableGroupId),
() => "expected " + tojson(expectedResult) + " actual " +
tojson(lastNResultsWithInitExprAndVariableGroupId));
expectedLastNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
assert(
arrayEq(expectedResult, lastNResultsWithInitExprAndVariableGroupId),
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExprAndVariableGroupId),
);
function reorderBucketResults(bucketResults) {
// Using a computed projection will put the fields out of order. As such, we re-order them
@ -199,67 +197,67 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
// to compare the $bucketAuto results to the expected $group results (because there are more
// buckets than groups, it will always be the case that the min value of each bucket
// corresponds to the group key).
let actualFirstNBucketAutoResults =
coll.aggregate([
{$sort: {state: 1, sales: 1}},
{
$bucketAuto: {
groupBy: '$state',
buckets: 10 * 1000,
output: {sales: {$firstN: {input: "$sales", n: n}}}
}
let actualFirstNBucketAutoResults = coll
.aggregate([
{$sort: {state: 1, sales: 1}},
{
$bucketAuto: {
groupBy: "$state",
buckets: 10 * 1000,
output: {sales: {$firstN: {input: "$sales", n: n}}},
},
{$project: {_id: "$_id.min", sales: 1}}
])
.toArray();
},
{$project: {_id: "$_id.min", sales: 1}},
])
.toArray();
reorderBucketResults(actualFirstNBucketAutoResults);
assert(arrayEq(expectedFirstNResults, actualFirstNBucketAutoResults),
() => "expected " + tojson(expectedFirstNResults) + " actual " +
tojson(actualFirstNBucketAutoResults));
assert(
arrayEq(expectedFirstNResults, actualFirstNBucketAutoResults),
() => "expected " + tojson(expectedFirstNResults) + " actual " + tojson(actualFirstNBucketAutoResults),
);
let actualLastNBucketAutoResults =
coll.aggregate([
{$sort: {state: 1, sales: 1}},
{
$bucketAuto: {
groupBy: '$state',
buckets: 10 * 1000,
output: {sales: {$lastN: {input: "$sales", n: n}}}
}
let actualLastNBucketAutoResults = coll
.aggregate([
{$sort: {state: 1, sales: 1}},
{
$bucketAuto: {
groupBy: "$state",
buckets: 10 * 1000,
output: {sales: {$lastN: {input: "$sales", n: n}}},
},
{$project: {_id: "$_id.min", sales: 1}}
])
.toArray();
},
{$project: {_id: "$_id.min", sales: 1}},
])
.toArray();
reorderBucketResults(actualLastNBucketAutoResults);
assert(arrayEq(expectedLastNResults, actualLastNBucketAutoResults),
() => "expected " + tojson(expectedLastNResults) + " actual " +
tojson(actualLastNBucketAutoResults));
assert(
arrayEq(expectedLastNResults, actualLastNBucketAutoResults),
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNBucketAutoResults),
);
// Verify that an index on {_id: 1, sales: -1} will produce the expected results.
const idxSpec = {_id: 1, sales: -1};
assert.commandWorked(coll.createIndex(idxSpec));
const indexedFirstNResults =
coll.aggregate(
[
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
{$sort: {_id: 1}},
],
{hint: idxSpec})
.toArray();
const indexedFirstNResults = coll
.aggregate(
[
{$sort: {_id: 1}},
{$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}},
{$sort: {_id: 1}},
],
{hint: idxSpec},
)
.toArray();
assert.eq(expectedFirstNResults, indexedFirstNResults);
const indexedLastNResults =
coll.aggregate(
[
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$lastN: {input: "$sales", n: n}}}},
{$sort: {_id: 1}},
],
{hint: idxSpec})
.toArray();
const indexedLastNResults = coll
.aggregate(
[{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$lastN: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}],
{hint: idxSpec},
)
.toArray();
assert.eq(expectedLastNResults, indexedLastNResults);
}
@ -269,56 +267,67 @@ runFirstLastN(defaultN, expectedFirstThree, expectedLastThree);
runFirstLastN(largestInt, expectedAllResults, expectedAllResults);
// Reject non-integral values of n.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: 'string'}}}}],
cursor: {}
}),
5787902);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: "string"}}}}],
cursor: {},
}),
5787902,
);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: 3.2}}}}],
cursor: {}
}),
5787903);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: 3.2}}}}],
cursor: {},
}),
5787903,
);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: -1}}}}],
cursor: {}
}),
5787908);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: -1}}}}],
cursor: {},
}),
5787908,
);
// Verify that 'n' cannot be greater than the largest signed 64 bit int.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [
{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: largestIntPlus1}}}}
],
cursor: {}
}),
5787903);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: largestIntPlus1}}}}],
cursor: {},
}),
5787903,
);
// Reject invalid specifications.
// Extra fields
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{
$group: {
_id: {'st': '$state'},
sales: {$firstN: {input: '$sales', n: 2, randomField: "randomArg"}}
}
}],
cursor: {}
}),
5787901);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$group: {
_id: {"st": "$state"},
sales: {$firstN: {input: "$sales", n: 2, randomField: "randomArg"}},
},
},
],
cursor: {},
}),
5787901,
);
// Missing arguments.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales'}}}}],
cursor: {}
}),
5787906);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales"}}}}],
cursor: {},
}),
5787906,
);
assert.commandFailedWithCode(
coll.runCommand(
"aggregate",
{pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {n: 2}}}}], cursor: {}}),
5787907);
coll.runCommand("aggregate", {pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {n: 2}}}}], cursor: {}}),
5787907,
);

View File

@ -22,24 +22,28 @@ const docs = [
assert.commandWorked(coll.insertMany(docs));
function assertTypeMismatch(aggFunction, aggFunctionArgument) {
const pipeline = [{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
}
}];
const pipeline = [
{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
},
},
];
assertErrCodeAndErrMsgContains(coll, pipeline, 9961600, aggFunction);
}
function assertNoError(aggFunction, aggFunctionArgument) {
const pipeline = [{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
}
}];
const pipeline = [
{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
},
},
];
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
}

View File

@ -28,13 +28,15 @@ const docs = [
assert.commandWorked(coll.insertMany(docs));
function assertNoError(aggFunction, aggFunctionArgument) {
const pipeline = [{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
}
}];
const pipeline = [
{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
},
},
];
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
}

View File

@ -17,24 +17,26 @@ function reduce(key, values) {
return Array.sum(values);
}
let groupPipe = [{
$group: {
_id: "$word",
wordCount: {
$_internalJsReduce: {
data: {k: "$word", v: "$val"},
eval: reduce,
}
}
}
}];
let groupPipe = [
{
$group: {
_id: "$word",
wordCount: {
$_internalJsReduce: {
data: {k: "$word", v: "$val"},
eval: reduce,
},
},
},
},
];
let command = {
aggregate: 'js_reduce',
aggregate: "js_reduce",
cursor: {},
pipeline: groupPipe,
allowDiskUse: true // Set allowDiskUse to true to force the expression to run on a shard in the
// passthrough suites, where javascript execution is supported.
allowDiskUse: true, // Set allowDiskUse to true to force the expression to run on a shard in the
// passthrough suites, where javascript execution is supported.
};
const expectedResults = [
@ -100,17 +102,17 @@ assert.commandFailedWithCode(db.runCommand(command), 31242);
groupPipe[0].$group.wordCount.$_internalJsReduce = {
notEval: 1,
notData: 1
notData: 1,
};
assert.commandFailedWithCode(db.runCommand(command), 31243);
groupPipe[0].$group.wordCount.$_internalJsReduce = {
eval: reduce,
data: {v: 1}
data: {v: 1},
};
assert.commandFailedWithCode(db.runCommand(command), 31251);
groupPipe[0].$group.wordCount.$_internalJsReduce.data = {
key: 1,
value: 1
value: 1,
};
assert.commandFailedWithCode(db.runCommand(command), 31251);
assert.commandFailedWithCode(db.runCommand(command), 31251);

View File

@ -27,19 +27,20 @@ function reduce(key, values) {
const command = {
aggregate: coll.getName(),
cursor: {},
runtimeConstants:
{localNow: new Date(), clusterTime: new Timestamp(0, 0), jsScope: {modulus: modulus}},
pipeline: [{
$group: {
_id: "$word",
wordCountMod: {
$_internalJsReduce: {
data: {k: "$word", v: "$val"},
eval: reduce,
}
}
}
}],
runtimeConstants: {localNow: new Date(), clusterTime: new Timestamp(0, 0), jsScope: {modulus: modulus}},
pipeline: [
{
$group: {
_id: "$word",
wordCountMod: {
$_internalJsReduce: {
data: {k: "$word", v: "$val"},
eval: reduce,
},
},
},
},
],
fromRouter: true,
};

View File

@ -5,10 +5,7 @@
* requires_fcv_70,
* ]
*/
import {
testWithMultipleGroupsMedian,
testWithSingleGroupMedian
} from "jstests/aggregation/libs/percentiles_util.js";
import {testWithMultipleGroupsMedian, testWithSingleGroupMedian} from "jstests/aggregation/libs/percentiles_util.js";
const coll = db[jsTestName()];
@ -23,7 +20,7 @@ testWithSingleGroupMedian({
docs: [{x: 0}, {x: "non-numeric"}, {x: 1}, {no_x: 0}, {x: 2}],
medianSpec: {$median: {input: "$x", method: "approximate"}},
expectedResult: 1,
msg: "Non-numeric data should be ignored"
msg: "Non-numeric data should be ignored",
});
testWithSingleGroupMedian({
@ -31,7 +28,7 @@ testWithSingleGroupMedian({
docs: [{x: "non-numeric"}, {non_x: 1}],
medianSpec: {$median: {input: "$x", method: "approximate"}},
expectedResult: null,
msg: "Median of completely non-numeric data."
msg: "Median of completely non-numeric data.",
});
/**
@ -42,5 +39,5 @@ testWithMultipleGroupsMedian({
docs: [{k: 0, x: 2}, {k: 0, x: 1}, {k: 1, x: 2}, {k: 2}, {k: 0, x: "str"}, {k: 1, x: 0}],
medianSpec: {$median: {input: "$x", method: "approximate"}},
expectedResult: [/* k:0 */ 1, /* k:1 */ 0, /* k:2 */ null],
msg: "Median of multiple groups"
msg: "Median of multiple groups",
});

View File

@ -6,10 +6,7 @@
* featureFlagAccuratePercentiles
* ]
*/
import {
testWithMultipleGroupsMedian,
testWithSingleGroupMedian
} from "jstests/aggregation/libs/percentiles_util.js";
import {testWithMultipleGroupsMedian, testWithSingleGroupMedian} from "jstests/aggregation/libs/percentiles_util.js";
const coll = db[jsTestName()];
@ -24,7 +21,7 @@ testWithSingleGroupMedian({
docs: [{x: 0}, {x: 2}],
medianSpec: {$median: {input: "$x", method: "continuous"}},
expectedResult: 1,
msg: "Continuous interpolation should allow result not in original dataset"
msg: "Continuous interpolation should allow result not in original dataset",
});
testWithSingleGroupMedian({
@ -32,7 +29,7 @@ testWithSingleGroupMedian({
docs: [{x: 0}, {x: "non-numeric"}, {x: 1}, {no_x: 0}, {x: 2}],
medianSpec: {$median: {input: "$x", method: "continuous"}},
expectedResult: 1,
msg: "Non-numeric data should be ignored"
msg: "Non-numeric data should be ignored",
});
testWithSingleGroupMedian({
@ -40,7 +37,7 @@ testWithSingleGroupMedian({
docs: [{x: "non-numeric"}, {non_x: 1}],
medianSpec: {$median: {input: "$x", method: "continuous"}},
expectedResult: null,
msg: "Median of completely non-numeric data."
msg: "Median of completely non-numeric data.",
});
/**
@ -51,5 +48,5 @@ testWithMultipleGroupsMedian({
docs: [{k: 0, x: 2}, {k: 0, x: 1}, {k: 1, x: 2}, {k: 2}, {k: 0, x: "str"}, {k: 1, x: 0}],
medianSpec: {$median: {input: "$x", method: "continuous"}},
expectedResult: [/* k:0 */ 1.5, /* k:1 */ 1, /* k:2 */ null],
msg: "Median of multiple groups"
msg: "Median of multiple groups",
});

View File

@ -6,10 +6,7 @@
* featureFlagAccuratePercentiles
* ]
*/
import {
testWithMultipleGroupsMedian,
testWithSingleGroupMedian
} from "jstests/aggregation/libs/percentiles_util.js";
import {testWithMultipleGroupsMedian, testWithSingleGroupMedian} from "jstests/aggregation/libs/percentiles_util.js";
const coll = db[jsTestName()];
@ -24,7 +21,7 @@ testWithSingleGroupMedian({
docs: [{x: 0}, {x: "non-numeric"}, {x: 1}, {no_x: 0}, {x: 2}],
medianSpec: {$median: {input: "$x", method: "discrete"}},
expectedResult: 1,
msg: "Non-numeric data should be ignored"
msg: "Non-numeric data should be ignored",
});
testWithSingleGroupMedian({
@ -32,7 +29,7 @@ testWithSingleGroupMedian({
docs: [{x: "non-numeric"}, {non_x: 1}],
medianSpec: {$median: {input: "$x", method: "discrete"}},
expectedResult: null,
msg: "Median of completely non-numeric data."
msg: "Median of completely non-numeric data.",
});
/**
@ -43,5 +40,5 @@ testWithMultipleGroupsMedian({
docs: [{k: 0, x: 2}, {k: 0, x: 1}, {k: 1, x: 2}, {k: 2}, {k: 0, x: "str"}, {k: 1, x: 0}],
medianSpec: {$median: {input: "$x", method: "discrete"}},
expectedResult: [/* k:0 */ 1, /* k:1 */ 0, /* k:2 */ null],
msg: "Median of multiple groups"
msg: "Median of multiple groups",
});

View File

@ -10,18 +10,17 @@ coll.drop();
let docs = [];
const n = 4;
const largestInt =
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
// Some big number that will allow us to test big without running this testcase into next decade.
const bigN = 10000;
const states = [
{state: 'CA', sales: 10},
{state: 'NY', sales: 7},
{state: 'TX', sales: 4},
{state: 'WY', sales: bigN}
{state: "CA", sales: 10},
{state: "NY", sales: 7},
{state: "TX", sales: 4},
{state: "WY", sales: bigN},
];
let expectedMinNResults = [];
let expectedMaxNResults = [];
@ -31,8 +30,8 @@ let expectedBigNMinNResults = [];
let expectedBigNMaxNResults = [];
for (const stateDoc of states) {
const state = stateDoc['state'];
const sales = stateDoc['sales'];
const state = stateDoc["state"];
const sales = stateDoc["sales"];
let minArr = [];
let maxArr = [];
let minArrForLargestInt = [];
@ -80,12 +79,9 @@ assert.commandWorked(coll.insert(docs));
// Note that the output documents are sorted by '_id' so that we can compare actual groups against
// expected groups (we cannot perform unordered comparison because order matters for $minN/$maxN).
function runAndCompareMinMaxN(nFunction, n, expectedResults) {
const actualResults =
coll.aggregate([
{$group: {_id: '$state', sales: {[nFunction]: {input: '$sales', n: n}}}},
{$sort: {_id: 1}}
])
.toArray();
const actualResults = coll
.aggregate([{$group: {_id: "$state", sales: {[nFunction]: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}])
.toArray();
assert.eq(expectedResults, actualResults);
// Basic correctness test for $minN/$maxN used in $bucketAuto. Though $bucketAuto uses
@ -94,19 +90,19 @@ function runAndCompareMinMaxN(nFunction, n, expectedResults) {
// to compare the $bucketAuto results to the expected $group results (because there are more
// buckets than groups, it will always be the case that the min value of each bucket
// corresponds to the group key).
let actualBucketAutoResults =
coll.aggregate([
{
$bucketAuto: {
groupBy: '$state',
buckets: 10 * 1000,
output: {sales: {[nFunction]: {input: '$sales', n: n}}}
}
let actualBucketAutoResults = coll
.aggregate([
{
$bucketAuto: {
groupBy: "$state",
buckets: 10 * 1000,
output: {sales: {[nFunction]: {input: "$sales", n: n}}},
},
{$project: {_id: "$_id.min", sales: 1}},
{$sort: {_id: 1}},
])
.toArray();
},
{$project: {_id: "$_id.min", sales: 1}},
{$sort: {_id: 1}},
])
.toArray();
// Using a computed projection will put the fields out of order. As such, we re-order them
// below.
@ -131,23 +127,25 @@ runAndCompareMinMaxN("$maxN", bigN - 1, expectedBigNMaxNResults);
// Verify that we can dynamically compute 'n' based on the group key for $group.
const groupKeyNExpr = {
$cond: {if: {$eq: ['$st', 'CA']}, then: 10, else: 4}
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4},
};
const dynamicMinNResults =
coll.aggregate([{
$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
}])
.toArray();
const dynamicMinNResults = coll
.aggregate([
{
$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: groupKeyNExpr}}},
},
])
.toArray();
// Verify that the 'CA' group has 10 results, while all others have only 4.
for (const result of dynamicMinNResults) {
assert(result.hasOwnProperty('_id'), tojson(result));
const groupKey = result['_id'];
assert(groupKey.hasOwnProperty('st'), tojson(groupKey));
const state = groupKey['st'];
assert(result.hasOwnProperty('minSales'), tojson(result));
const salesArray = result['minSales'];
if (state === 'CA') {
assert(result.hasOwnProperty("_id"), tojson(result));
const groupKey = result["_id"];
assert(groupKey.hasOwnProperty("st"), tojson(groupKey));
const state = groupKey["st"];
assert(result.hasOwnProperty("minSales"), tojson(result));
const salesArray = result["minSales"];
if (state === "CA") {
assert.eq(salesArray.length, 10, tojson(salesArray));
} else {
assert.eq(salesArray.length, 4, tojson(salesArray));
@ -157,75 +155,94 @@ for (const result of dynamicMinNResults) {
// Error cases
// Cannot reference the group key in $minN when using $bucketAuto.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{
$bucketAuto: {
groupBy: "$state",
buckets: 2,
output: {minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
}
}],
cursor: {}
}),
4544714);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$bucketAuto: {
groupBy: "$state",
buckets: 2,
output: {minSales: {$minN: {input: "$sales", n: groupKeyNExpr}}},
},
},
],
cursor: {},
}),
4544714,
);
// Reject non-integral/negative values of n.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline:
[{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 'string'}}}}],
cursor: {}
}),
5787902);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: "string"}}}}],
cursor: {},
}),
5787902,
);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 3.2}}}}],
cursor: {}
}),
5787903);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: 3.2}}}}],
cursor: {},
}),
5787903,
);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: -1}}}}],
cursor: {}
}),
5787908);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: -1}}}}],
cursor: {},
}),
5787908,
);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 0}}}}],
cursor: {}
}),
5787908);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: 0}}}}],
cursor: {},
}),
5787908,
);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [
{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: largestIntPlus1}}}}
],
cursor: {}
}),
5787903);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: largestIntPlus1}}}}],
cursor: {},
}),
5787903,
);
// Reject invalid specifications.
// Missing arguments.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales'}}}}],
cursor: {}
}),
5787906);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales"}}}}],
cursor: {},
}),
5787906,
);
assert.commandFailedWithCode(
coll.runCommand(
"aggregate",
{pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {n: 2}}}}], cursor: {}}),
5787907);
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {n: 2}}}}],
cursor: {},
}),
5787907,
);
// Extra field.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{
$group: {
_id: {'st': '$state'},
minSales: {$minN: {input: '$sales', n: 2, randomField: "randomArg"}}
}
}],
cursor: {}
}),
5787901);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$group: {
_id: {"st": "$state"},
minSales: {$minN: {input: "$sales", n: 2, randomField: "randomArg"}},
},
},
],
cursor: {},
}),
5787901,
);

View File

@ -9,7 +9,7 @@ import {
testLargeUniformDataset_Decimal,
testLargeUniformDataset_WithInfinities,
testWithMultipleGroups,
testWithSingleGroup
testWithSingleGroup,
} from "jstests/aggregation/libs/percentiles_util.js";
const coll = db[jsTestName()];
@ -23,7 +23,7 @@ testWithSingleGroup({
docs: [{x: 0}, {x: "non-numeric"}, {x: 1}, {no_x: 0}, {x: 2}],
percentileSpec: {$percentile: {p: [0.5], input: "$x", method: "approximate"}},
expectedResult: [1],
msg: "Non-numeric data should be ignored"
msg: "Non-numeric data should be ignored",
});
testWithSingleGroup({
@ -31,7 +31,7 @@ testWithSingleGroup({
docs: [{x: "non-numeric"}, {no_x: 0}, {x: new Date()}, {x: [42, 43]}, {x: null}, {x: NaN}],
percentileSpec: {$percentile: {p: [0.5], input: "$x", method: "approximate"}},
expectedResult: [null],
msg: "Single percentile of completely non-numeric data"
msg: "Single percentile of completely non-numeric data",
});
testWithSingleGroup({
@ -39,7 +39,7 @@ testWithSingleGroup({
docs: [{x: "non-numeric"}, {no_x: 0}, {x: new Date()}, {x: [42, 43]}, {x: null}, {x: NaN}],
percentileSpec: {$percentile: {p: [0.5, 0.9], input: "$x", method: "approximate"}},
expectedResult: [null, null],
msg: "Multiple percentiles of completely non-numeric data"
msg: "Multiple percentiles of completely non-numeric data",
});
testWithSingleGroup({
@ -47,7 +47,7 @@ testWithSingleGroup({
docs: [{x: 10}, {x: 5}, {x: 27}],
percentileSpec: {$percentile: {p: [0], input: "$x", method: "approximate"}},
expectedResult: [5],
msg: "Minimum percentile"
msg: "Minimum percentile",
});
testWithSingleGroup({
@ -55,7 +55,7 @@ testWithSingleGroup({
docs: [{x: 10}, {x: 5}, {x: 27}],
percentileSpec: {$percentile: {p: [1], input: "$x", method: "approximate"}},
expectedResult: [27],
msg: "Maximum percentile"
msg: "Maximum percentile",
});
testWithSingleGroup({
@ -63,7 +63,7 @@ testWithSingleGroup({
docs: [{x: 0}, {x: 1}, {x: 2}],
percentileSpec: {$percentile: {p: [0.5, 0.9, 0.1], input: "$x", method: "approximate"}},
expectedResult: [1, 2, 0],
msg: "Multiple percentiles"
msg: "Multiple percentiles",
});
testWithSingleGroup({
@ -72,7 +72,7 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: "$$ps", input: "$x", method: "approximate"}},
letSpec: {ps: [0.5, 0.9, 0.1]},
expectedResult: [1, 2, 0],
msg: "Multiple percentiles using variable in the percentile spec for the whole array"
msg: "Multiple percentiles using variable in the percentile spec for the whole array",
});
testWithSingleGroup({
@ -81,19 +81,18 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: ["$$p90"], input: "$x", method: "approximate"}},
letSpec: {p90: 0.9},
expectedResult: [2],
msg: "Single percentile using variable in the percentile spec for the array elements"
msg: "Single percentile using variable in the percentile spec for the array elements",
});
testWithSingleGroup({
coll: coll,
docs: [{x: 0}, {x: 1}, {x: 2}],
percentileSpec: {
$percentile:
{p: {$concatArrays: [[0.1, 0.5], ["$$p90"]]}, input: "$x", method: "approximate"}
$percentile: {p: {$concatArrays: [[0.1, 0.5], ["$$p90"]]}, input: "$x", method: "approximate"},
},
letSpec: {p90: 0.9},
expectedResult: [0, 1, 2],
msg: "Multiple percentiles using const expression in the percentile spec"
msg: "Multiple percentiles using const expression in the percentile spec",
});
testWithSingleGroup({
@ -102,7 +101,7 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: "$$ps", input: {$add: [42, "$x"]}, method: "approximate"}},
letSpec: {ps: [0.5, 0.9, 0.1]},
expectedResult: [42 + 1, 42 + 2, 42 + 0],
msg: "Multiple percentiles using expression as input"
msg: "Multiple percentiles using expression as input",
});
/**
@ -112,8 +111,8 @@ testWithMultipleGroups({
coll: coll,
docs: [{k: 0, x: 0}, {k: 0, x: 1}, {k: 1, x: 2}, {k: 2}, {k: 0, x: "str"}, {k: 1, x: 0}],
percentileSpec: {$percentile: {p: [0.9], input: "$x", method: "approximate"}},
expectedResult: [/* k:0 */[1], /* k:1 */[2], /* k:2 */[null]],
msg: "Multiple groups"
expectedResult: [/* k:0 */ [1], /* k:1 */ [2], /* k:2 */ [null]],
msg: "Multiple groups",
});
/**
@ -143,8 +142,7 @@ const p = [0.0, 0.001, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.99,
testLargeUniformDataset(coll, samples, sortedSamples, p, accuracyError, "approximate");
testLargeUniformDataset_WithInfinities(
coll, samples, sortedSamples, p, accuracyError, "approximate");
testLargeUniformDataset_WithInfinities(coll, samples, sortedSamples, p, accuracyError, "approximate");
// Same dataset but using Decimal128 type.
testLargeUniformDataset_Decimal(coll, samples, sortedSamples, p, accuracyError, "approximate");

View File

@ -10,7 +10,7 @@ import {
testLargeUniformDataset_Decimal,
testLargeUniformDataset_WithInfinities,
testWithMultipleGroups,
testWithSingleGroup
testWithSingleGroup,
} from "jstests/aggregation/libs/percentiles_util.js";
const coll = db[jsTestName()];
@ -24,7 +24,7 @@ testWithSingleGroup({
docs: [{x: 0}, {x: "non-numeric"}, {x: 1}, {no_x: 0}, {x: 2}],
percentileSpec: {$percentile: {p: [0.5], input: "$x", method: "continuous"}},
expectedResult: [1],
msg: "Non-numeric data should be ignored"
msg: "Non-numeric data should be ignored",
});
testWithSingleGroup({
@ -32,7 +32,7 @@ testWithSingleGroup({
docs: [{x: "non-numeric"}, {no_x: 0}, {x: new Date()}, {x: [42, 43]}, {x: null}, {x: NaN}],
percentileSpec: {$percentile: {p: [0.5], input: "$x", method: "continuous"}},
expectedResult: [null],
msg: "Single percentile of completely non-numeric data"
msg: "Single percentile of completely non-numeric data",
});
testWithSingleGroup({
@ -40,7 +40,7 @@ testWithSingleGroup({
docs: [{x: "non-numeric"}, {no_x: 0}, {x: new Date()}, {x: [42, 43]}, {x: null}, {x: NaN}],
percentileSpec: {$percentile: {p: [0.5, 0.9], input: "$x", method: "continuous"}},
expectedResult: [null, null],
msg: "Multiple percentiles of completely non-numeric data"
msg: "Multiple percentiles of completely non-numeric data",
});
testWithSingleGroup({
@ -48,7 +48,7 @@ testWithSingleGroup({
docs: [{x: 10}, {x: 5}, {x: 27}],
percentileSpec: {$percentile: {p: [0], input: "$x", method: "continuous"}},
expectedResult: [5],
msg: "Minimum percentile"
msg: "Minimum percentile",
});
testWithSingleGroup({
@ -56,7 +56,7 @@ testWithSingleGroup({
docs: [{x: 10}, {x: 5}, {x: 27}],
percentileSpec: {$percentile: {p: [1], input: "$x", method: "continuous"}},
expectedResult: [27],
msg: "Maximum percentile"
msg: "Maximum percentile",
});
testWithSingleGroup({
@ -64,7 +64,7 @@ testWithSingleGroup({
docs: [{x: 0}, {x: 1}, {x: 2}],
percentileSpec: {$percentile: {p: [0.5, 0.9, 0.1], input: "$x", method: "continuous"}},
expectedResult: [1, 1.8, 0.2],
msg: "Multiple percentiles"
msg: "Multiple percentiles",
});
testWithSingleGroup({
@ -73,7 +73,7 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: "$$ps", input: "$x", method: "continuous"}},
letSpec: {ps: [0.5, 0.9, 0.1]},
expectedResult: [1, 1.8, 0.2],
msg: "Multiple percentiles using variable in the percentile spec for the whole array"
msg: "Multiple percentiles using variable in the percentile spec for the whole array",
});
testWithSingleGroup({
@ -82,19 +82,18 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: ["$$p90"], input: "$x", method: "continuous"}},
letSpec: {p90: 0.9},
expectedResult: [1.8],
msg: "Single percentile using variable in the percentile spec for the array elements"
msg: "Single percentile using variable in the percentile spec for the array elements",
});
testWithSingleGroup({
coll: coll,
docs: [{x: 0}, {x: 1}, {x: 2}],
percentileSpec: {
$percentile:
{p: {$concatArrays: [[0.1, 0.5], ["$$p90"]]}, input: "$x", method: "continuous"}
$percentile: {p: {$concatArrays: [[0.1, 0.5], ["$$p90"]]}, input: "$x", method: "continuous"},
},
letSpec: {p90: 0.9},
expectedResult: [0.2, 1, 1.8],
msg: "Multiple percentiles using const expression in the percentile spec"
msg: "Multiple percentiles using const expression in the percentile spec",
});
testWithSingleGroup({
@ -103,7 +102,7 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: "$$ps", input: {$add: [42, "$x"]}, method: "continuous"}},
letSpec: {ps: [0.5, 0.9, 0.1]},
expectedResult: [42 + 1, 42 + 1.8, 42 + 0.2],
msg: "Multiple percentiles using expression as input"
msg: "Multiple percentiles using expression as input",
});
/**
@ -113,8 +112,8 @@ testWithMultipleGroups({
coll: coll,
docs: [{k: 0, x: 0}, {k: 0, x: 1}, {k: 1, x: 2}, {k: 2}, {k: 0, x: "str"}, {k: 1, x: 0}],
percentileSpec: {$percentile: {p: [0.9], input: "$x", method: "continuous"}},
expectedResult: [/* k:0 */[0.9], /* k:1 */[1.8], /* k:2 */[null]],
msg: "Multiple groups"
expectedResult: [/* k:0 */ [0.9], /* k:1 */ [1.8], /* k:2 */ [null]],
msg: "Multiple groups",
});
/**
@ -139,8 +138,7 @@ const p = [0.0, 0.001, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.99,
testLargeUniformDataset(coll, samples, sortedSamples, p, accuracyError, "continuous");
testLargeUniformDataset_WithInfinities(
coll, samples, sortedSamples, p, accuracyError, "continuous");
testLargeUniformDataset_WithInfinities(coll, samples, sortedSamples, p, accuracyError, "continuous");
// TODO SERVER-91956: Improve precision so that this test succeeds.
// // Same dataset but using Decimal128 type.

View File

@ -10,7 +10,7 @@ import {
testLargeUniformDataset_Decimal,
testLargeUniformDataset_WithInfinities,
testWithMultipleGroups,
testWithSingleGroup
testWithSingleGroup,
} from "jstests/aggregation/libs/percentiles_util.js";
const coll = db[jsTestName()];
@ -24,7 +24,7 @@ testWithSingleGroup({
docs: [{x: 0}, {x: "non-numeric"}, {x: 1}, {no_x: 0}, {x: 2}],
percentileSpec: {$percentile: {p: [0.5], input: "$x", method: "discrete"}},
expectedResult: [1],
msg: "Non-numeric data should be ignored"
msg: "Non-numeric data should be ignored",
});
testWithSingleGroup({
@ -32,7 +32,7 @@ testWithSingleGroup({
docs: [{x: "non-numeric"}, {no_x: 0}, {x: new Date()}, {x: [42, 43]}, {x: null}, {x: NaN}],
percentileSpec: {$percentile: {p: [0.5], input: "$x", method: "discrete"}},
expectedResult: [null],
msg: "Single percentile of completely non-numeric data"
msg: "Single percentile of completely non-numeric data",
});
testWithSingleGroup({
@ -40,7 +40,7 @@ testWithSingleGroup({
docs: [{x: "non-numeric"}, {no_x: 0}, {x: new Date()}, {x: [42, 43]}, {x: null}, {x: NaN}],
percentileSpec: {$percentile: {p: [0.5, 0.9], input: "$x", method: "discrete"}},
expectedResult: [null, null],
msg: "Multiple percentiles of completely non-numeric data"
msg: "Multiple percentiles of completely non-numeric data",
});
testWithSingleGroup({
@ -48,7 +48,7 @@ testWithSingleGroup({
docs: [{x: 10}, {x: 5}, {x: 27}],
percentileSpec: {$percentile: {p: [0], input: "$x", method: "discrete"}},
expectedResult: [5],
msg: "Minimum percentile"
msg: "Minimum percentile",
});
testWithSingleGroup({
@ -56,7 +56,7 @@ testWithSingleGroup({
docs: [{x: 10}, {x: 5}, {x: 27}],
percentileSpec: {$percentile: {p: [1], input: "$x", method: "discrete"}},
expectedResult: [27],
msg: "Maximum percentile"
msg: "Maximum percentile",
});
testWithSingleGroup({
@ -64,7 +64,7 @@ testWithSingleGroup({
docs: [{x: 0}, {x: 1}, {x: 2}],
percentileSpec: {$percentile: {p: [0.5, 0.9, 0.1], input: "$x", method: "discrete"}},
expectedResult: [1, 2, 0],
msg: "Multiple percentiles"
msg: "Multiple percentiles",
});
testWithSingleGroup({
@ -72,7 +72,7 @@ testWithSingleGroup({
docs: [{x: -Infinity}, {x: 0}, {x: 1}, {x: 2}, {x: Infinity}, {x: Infinity}, {x: Infinity}],
percentileSpec: {$percentile: {p: [0.5, 0.9, 0.1], input: "$x", method: "discrete"}},
expectedResult: [2, Infinity, -Infinity],
msg: "Multiple percentiles with infinities"
msg: "Multiple percentiles with infinities",
});
testWithSingleGroup({
@ -81,7 +81,7 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: "$$ps", input: "$x", method: "discrete"}},
letSpec: {ps: [0.5, 0.9, 0.1]},
expectedResult: [1, 2, 0],
msg: "Multiple percentiles using variable in the percentile spec for the whole array"
msg: "Multiple percentiles using variable in the percentile spec for the whole array",
});
testWithSingleGroup({
@ -90,18 +90,18 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: ["$$p90"], input: "$x", method: "discrete"}},
letSpec: {p90: 0.9},
expectedResult: [2],
msg: "Single percentile using variable in the percentile spec for the array elements"
msg: "Single percentile using variable in the percentile spec for the array elements",
});
testWithSingleGroup({
coll: coll,
docs: [{x: 0}, {x: 1}, {x: 2}],
percentileSpec: {
$percentile: {p: {$concatArrays: [[0.1, 0.5], ["$$p90"]]}, input: "$x", method: "discrete"}
$percentile: {p: {$concatArrays: [[0.1, 0.5], ["$$p90"]]}, input: "$x", method: "discrete"},
},
letSpec: {p90: 0.9},
expectedResult: [0, 1, 2],
msg: "Multiple percentiles using const expression in the percentile spec"
msg: "Multiple percentiles using const expression in the percentile spec",
});
testWithSingleGroup({
@ -110,7 +110,7 @@ testWithSingleGroup({
percentileSpec: {$percentile: {p: "$$ps", input: {$add: [42, "$x"]}, method: "discrete"}},
letSpec: {ps: [0.5, 0.9, 0.1]},
expectedResult: [42 + 1, 42 + 2, 42 + 0],
msg: "Multiple percentiles using expression as input"
msg: "Multiple percentiles using expression as input",
});
/**
@ -120,8 +120,8 @@ testWithMultipleGroups({
coll: coll,
docs: [{k: 0, x: 0}, {k: 0, x: 1}, {k: 1, x: 2}, {k: 2}, {k: 0, x: "str"}, {k: 1, x: 0}],
percentileSpec: {$percentile: {p: [0.9], input: "$x", method: "discrete"}},
expectedResult: [/* k:0 */[1], /* k:1 */[2], /* k:2 */[null]],
msg: "Multiple groups"
expectedResult: [/* k:0 */ [1], /* k:1 */ [2], /* k:2 */ [null]],
msg: "Multiple groups",
});
/**

View File

@ -13,7 +13,7 @@ coll.drop();
coll.insert({x: 42});
function assertInvalidSyntax({pSpec, letSpec, errorCode, msg}) {
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let : letSpec, cursor: {}};
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let: letSpec, cursor: {}};
if (errorCode) {
assert.commandFailedWithCode(coll.runCommand("aggregate", command), errorCode, msg);
} else {
@ -22,34 +22,33 @@ function assertInvalidSyntax({pSpec, letSpec, errorCode, msg}) {
}
function assertValidSyntax({pSpec, letSpec, msg}) {
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let : letSpec, cursor: {}};
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let: letSpec, cursor: {}};
assert.commandWorked(coll.runCommand("aggregate", command), msg);
}
/**
* Test missing or unexpected fields in $percentile spec.
*/
assertInvalidSyntax(
{pSpec: {$percentile: 0.5}, msg: "Should fail if $percentile is not an object"});
assertInvalidSyntax({pSpec: {$percentile: 0.5}, msg: "Should fail if $percentile is not an object"});
assertInvalidSyntax({
pSpec: {$percentile: {input: "$x", method: "approximate"}},
msg: "Should fail if $percentile is missing 'p' field"
msg: "Should fail if $percentile is missing 'p' field",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5], method: "approximate"}},
msg: "Should fail if $percentile is missing 'input' field"
msg: "Should fail if $percentile is missing 'input' field",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5], input: "$x"}},
msg: "Should fail if $percentile is missing 'method' field"
msg: "Should fail if $percentile is missing 'method' field",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5], input: "$x", method: "approximate", extras: 42}},
msg: "Should fail if $percentile contains an unexpected field"
msg: "Should fail if $percentile contains an unexpected field",
});
/**
@ -57,23 +56,22 @@ assertInvalidSyntax({
*/
assertInvalidSyntax({
pSpec: {$percentile: {p: 0.5, input: "$x", method: "approximate"}},
msg: "Should fail if 'p' field in $percentile isn't array"
msg: "Should fail if 'p' field in $percentile isn't array",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [], input: "$x", method: "approximate"}},
msg: "Should fail if 'p' field in $percentile is an empty array"
msg: "Should fail if 'p' field in $percentile is an empty array",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5, "foo"], input: "$x", method: "approximate"}},
msg: "Should fail if 'p' field in $percentile is an array with a non-numeric element"
msg: "Should fail if 'p' field in $percentile is an array with a non-numeric element",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5, 10], input: "$x", method: "approximate"}},
msg:
"Should fail if 'p' field in $percentile is an array with any value outside of [0, 1] range"
msg: "Should fail if 'p' field in $percentile is an array with any value outside of [0, 1] range",
});
/**
@ -81,32 +79,31 @@ assertInvalidSyntax({
*/
assertInvalidSyntax({
pSpec: {$percentile: {p: ["$x"], input: "$x", method: "approximate"}},
msg: "'p' should not accept non-const expressions"
msg: "'p' should not accept non-const expressions",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: {$add: [0.1, 0.5]}, input: "$x", method: "approximate"}},
msg: "'p' should not accept expressions that evaluate to a non-array"
msg: "'p' should not accept expressions that evaluate to a non-array",
});
assertInvalidSyntax({
pSpec: {
$percentile:
{p: {$concatArrays: [[0.01, 0.1], ["foo"]]}, input: "$x", method: "approximate"}
$percentile: {p: {$concatArrays: [[0.01, 0.1], ["foo"]]}, input: "$x", method: "approximate"},
},
msg: "'p' should not accept expressions that evaluate to an array with non-numeric elements"
msg: "'p' should not accept expressions that evaluate to an array with non-numeric elements",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
letSpec: {pvals: 0.5},
msg: "'p' should not accept variables that evaluate to a non-array"
msg: "'p' should not accept variables that evaluate to a non-array",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
letSpec: {pvals: [0.5, "foo"]},
msg: "'p' should not accept variables that evaluate to an array with non-numeric elements"
msg: "'p' should not accept variables that evaluate to an array with non-numeric elements",
});
/**
@ -114,37 +111,36 @@ assertInvalidSyntax({
*/
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: 42}},
msg: "$percentile should fail if 'method' field isn't a string"
msg: "$percentile should fail if 'method' field isn't a string",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "fancy"}},
msg: "$percentile should fail if 'method' isn't one of _predefined_ strings"
msg: "$percentile should fail if 'method' isn't one of _predefined_ strings",
});
if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
assertValidSyntax({
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "discrete"}},
msg: "Should work with discrete 'method'"
msg: "Should work with discrete 'method'",
});
assertValidSyntax({
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "continuous"}},
errorCode: ErrorCodes.InternalErrorNotSupported,
msg: "Should work with continuous 'method'"
msg: "Should work with continuous 'method'",
});
} else {
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "discrete"}},
errorCode: ErrorCodes.BadValue,
msg: "$percentile should fail because discrete 'method' isn't supported yet"
msg: "$percentile should fail because discrete 'method' isn't supported yet",
});
assertInvalidSyntax({
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "continuous"}},
errorCode: ErrorCodes.BadValue,
msg: "$percentile should fail because continuous 'method' isn't supported yet"
msg: "$percentile should fail because continuous 'method' isn't supported yet",
});
}
@ -153,50 +149,48 @@ if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
*/
assertInvalidSyntax({
pSpec: {$median: {p: [0.5], input: "$x", method: "approximate"}},
msg: "$median should fail if 'p' is defined"
msg: "$median should fail if 'p' is defined",
});
assertInvalidSyntax({
pSpec: {$median: {method: "approximate"}},
msg: "$median should fail if 'input' field is missing"
msg: "$median should fail if 'input' field is missing",
});
assertInvalidSyntax(
{pSpec: {$median: {input: "$x"}}, msg: "Median should fail if 'method' field is missing"});
assertInvalidSyntax({pSpec: {$median: {input: "$x"}}, msg: "Median should fail if 'method' field is missing"});
assertInvalidSyntax({
pSpec: {$median: {input: "$x", method: "approximate", extras: 42}},
msg: "$median should fail if there is an unexpected field"
msg: "$median should fail if there is an unexpected field",
});
assertInvalidSyntax({
pSpec: {$median: {input: "$x", method: "fancy"}},
msg: "$median should fail if 'method' isn't one of the _predefined_ strings"
msg: "$median should fail if 'method' isn't one of the _predefined_ strings",
});
if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
assertValidSyntax({
pSpec: {$median: {input: "$x", method: "discrete"}},
msg: "Should work with discrete 'method'"
msg: "Should work with discrete 'method'",
});
assertValidSyntax({
pSpec: {$median: {input: "$x", method: "continuous"}},
errorCode: ErrorCodes.InternalErrorNotSupported,
msg: "Should work with continuous 'method'"
msg: "Should work with continuous 'method'",
});
} else {
assertInvalidSyntax({
pSpec: {$median: {input: "$x", method: "discrete"}},
errorCode: ErrorCodes.BadValue,
msg: "$median should fail because discrete 'method' isn't supported yet"
msg: "$median should fail because discrete 'method' isn't supported yet",
});
assertInvalidSyntax({
pSpec: {$median: {input: "$x", method: "continuous"}},
errorCode: ErrorCodes.BadValue,
msg: "$median should fail because continuous 'method' isn't supported yet"
msg: "$median should fail because continuous 'method' isn't supported yet",
});
}
@ -207,58 +201,65 @@ if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
*/
assertValidSyntax({
pSpec: {$percentile: {p: [0.0, 0.0001, 0.5, 0.995, 1.0], input: "$x", method: "approximate"}},
msg: "Should be able to specify an array of percentiles"
msg: "Should be able to specify an array of percentiles",
});
assertValidSyntax({
pSpec: {$percentile: {p: [0.5, 0.9], input: {$divide: ["$x", 2]}, method: "approximate"}},
msg: "Should be able to specify 'input' as an expression"
msg: "Should be able to specify 'input' as an expression",
});
assertValidSyntax({
pSpec: {$percentile: {p: [0.5, 0.9], input: "x", method: "approximate"}},
msg: "Non-numeric inputs should be gracefully ignored"
msg: "Non-numeric inputs should be gracefully ignored",
});
assertValidSyntax({
pSpec: {$percentile: {p: [0.5, 0.9], input: {$add: [2, "$x"]}, method: "approximate"}},
msg: "'input' should be able to use expressions"
msg: "'input' should be able to use expressions",
});
assertValidSyntax({
pSpec: {
$percentile: {p: [0.5, 0.9], input: {$concatArrays: [[2], ["$x"]]}, method: "approximate"}
$percentile: {p: [0.5, 0.9], input: {$concatArrays: [[2], ["$x"]]}, method: "approximate"},
},
msg: "'input' should be able to use expressions even if the result of their eval is non-numeric"
msg: "'input' should be able to use expressions even if the result of their eval is non-numeric",
});
assertValidSyntax({
pSpec: {
$percentile:
{p: {$concatArrays: [[0.01, 0.1], [0.9, 0.99]]}, input: "$x", method: "approximate"}
$percentile: {
p: {
$concatArrays: [
[0.01, 0.1],
[0.9, 0.99],
],
},
input: "$x",
method: "approximate",
},
},
msg: "'p' should be able to use expressions that evaluate to an array"
msg: "'p' should be able to use expressions that evaluate to an array",
});
assertValidSyntax({
pSpec: {$percentile: {p: [{$add: [0.1, 0.5]}], input: "$x", method: "approximate"}},
msg: "'p' should be able to use expressions for the array elements"
msg: "'p' should be able to use expressions for the array elements",
});
assertValidSyntax({
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
letSpec: {pvals: [0.5, 0.9]},
msg: "'p' should be able to use variables for the array"
msg: "'p' should be able to use variables for the array",
});
assertValidSyntax({
pSpec: {$percentile: {p: ["$$p1", "$$p2"], input: "$x", method: "approximate"}},
letSpec: {p1: 0.5, p2: 0.9},
msg: "'p' should be able to use variables for the array elements"
msg: "'p' should be able to use variables for the array elements",
});
/**
* Tests for valid $median.
*/
assertValidSyntax(
{pSpec: {$median: {input: "$x", method: "approximate"}}, msg: "Simple base case for $median."});
assertValidSyntax({pSpec: {$median: {input: "$x", method: "approximate"}}, msg: "Simple base case for $median."});

View File

@ -10,13 +10,19 @@ assert(coll.drop());
// Test that $setUnion produces a single array containing all the unique values from the input
// arrays. As $setUnion does not provide guarantees on ordering, the result is sorted for easy
// comparison.
assert.commandWorked(coll.insert([{_id: 0, nums: [1, 2, 3]}, {_id: 1, nums: [4, 5, 6]}]));
assert.commandWorked(
coll.insert([
{_id: 0, nums: [1, 2, 3]},
{_id: 1, nums: [4, 5, 6]},
]),
);
let result = coll.aggregate([
{$group: {_id: null, n: {$setUnion: '$nums'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
])
.toArray();
let result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$nums"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6]}]);
@ -24,14 +30,20 @@ assert(coll.drop());
// Test that $setUnion deduplicates the values. That is, each unique value will only appear once in
// the ouput of the accumulation.
assert.commandWorked(coll.insert(
[{_id: 0, nums: [1, 2, 3]}, {_id: 1, nums: [2, 3, 4]}, {_id: 2, nums: [3, 4, 5, 6]}]));
assert.commandWorked(
coll.insert([
{_id: 0, nums: [1, 2, 3]},
{_id: 1, nums: [2, 3, 4]},
{_id: 2, nums: [3, 4, 5, 6]},
]),
);
result = coll.aggregate([
{$group: {_id: null, n: {$setUnion: '$nums'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
])
.toArray();
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$nums"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6]}]);
@ -41,28 +53,32 @@ assert(coll.drop());
// value will only appear once in the ouput of the accumulation.
assert.commandWorked(coll.insert([{_id: 0, nums: [1, 1, 2, 2, 3, 3]}]));
result = coll.aggregate([
{$group: {_id: null, n: {$setUnion: '$nums'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
])
.toArray();
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$nums"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3]}]);
assert(coll.drop());
// Nested arrays should still be arrays in the result of $setUnion.
assert.commandWorked(coll.insert([
{_id: 0, vals: [["nested"], 1, 2]},
{_id: 1, vals: [3, 4, ["nested"]]},
{_id: 2, vals: [4, ["nested", "extra"]]}
]));
assert.commandWorked(
coll.insert([
{_id: 0, vals: [["nested"], 1, 2]},
{_id: 1, vals: [3, 4, ["nested"]]},
{_id: 2, vals: [4, ["nested", "extra"]]},
]),
);
result = coll.aggregate([
{$group: {_id: null, n: {$setUnion: '$vals'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
])
.toArray();
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$vals"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, ["nested"], ["nested", "extra"]]}]);
@ -70,67 +86,100 @@ assert(coll.drop());
// $setUnion should deduplicate objects as well. Note that documents which differ in field order are
// considered unique.
assert.commandWorked(coll.insert([
{_id: 0, vals: [{a: 1, b: 2}]},
{_id: 1, vals: [{b: 2, a: 1}]},
{_id: 2, vals: [{a: 1, b: 2}]},
]));
assert.commandWorked(
coll.insert([
{_id: 0, vals: [{a: 1, b: 2}]},
{_id: 1, vals: [{b: 2, a: 1}]},
{_id: 2, vals: [{a: 1, b: 2}]},
]),
);
result = coll.aggregate([
{$group: {_id: null, n: {$setUnion: '$vals'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
])
.toArray();
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$vals"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [{a: 1, b: 2}, {b: 2, a: 1}]}]);
assert.eq(result, [
{
_id: null,
setUnionArr: [
{a: 1, b: 2},
{b: 2, a: 1},
],
},
]);
assert(coll.drop());
// $setUnion should 'skip over' documents that do not have the array field. Importantly, do not
// insert null for documents that do not have the referenced field.
assert.commandWorked(coll.insert([
{_id: 1, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
{_id: 2, author: "Santiago", publisher: "Pub3"},
{_id: 3, author: "Matt", publisher: "Pub3", books: ["Happy!"]}
]));
result = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$setUnion: '$books'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$allBooks', sortBy: 1}}}}
])
.toArray();
assert.commandWorked(
coll.insert([
{_id: 1, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
{_id: 2, author: "Santiago", publisher: "Pub3"},
{_id: 3, author: "Matt", publisher: "Pub3", books: ["Happy!"]},
]),
);
result = coll
.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$setUnion: "$books"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$allBooks", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: ["Happy!", "Smile :)"]}]);
assert(coll.drop());
// $setUnion dotted field.
assert.commandWorked(coll.insert(
[{_id: 1, a: {b: [1, 2, 3]}}, {_id: 2, a: {b: [3, 4, 5, 6]}}, {_id: 3, a: {b: [7, 8, 9]}}]));
result = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, nums: {$setUnion: '$a.b'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$nums', sortBy: 1}}}}
])
.toArray();
assert.commandWorked(
coll.insert([
{_id: 1, a: {b: [1, 2, 3]}},
{_id: 2, a: {b: [3, 4, 5, 6]}},
{_id: 3, a: {b: [7, 8, 9]}},
]),
);
result = coll
.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, nums: {$setUnion: "$a.b"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$nums", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
assert(coll.drop());
// $setUnion dotted field, array halfway on path.
assert.commandWorked(coll.insert([
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
{_id: 3, a: [{b: [7, 8, 9]}]}
]));
result = coll.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, nums: {$setUnion: '$a.b'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$nums', sortBy: 1}}}}
])
.toArray();
assert.commandWorked(
coll.insert([
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
{_id: 3, a: [{b: [7, 8, 9]}]},
]),
);
result = coll
.aggregate([
{$sort: {_id: 1}},
{$group: {_id: null, nums: {$setUnion: "$a.b"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$nums", sortBy: 1}}}},
])
.toArray();
assert.eq(result, [{_id: null, setUnionArr: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]}]);
assert.eq(result, [
{
_id: null,
setUnionArr: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
],
},
]);
assert(coll.drop());
@ -140,8 +189,7 @@ const notArrays = [1, "string", {object: "object"}, null];
for (const notAnArray of notArrays) {
assert.commandWorked(coll.insert([{_id: "doesNotMatter", vals: notAnArray}]));
assertErrorCode(
coll, [{$group: {_id: null, v: {$setUnion: '$vals'}}}], ErrorCodes.TypeMismatch);
assertErrorCode(coll, [{$group: {_id: null, v: {$setUnion: "$vals"}}}], ErrorCodes.TypeMismatch);
assert.commandWorked(coll.deleteOne({_id: "doesNotMatter"}));
}
@ -149,24 +197,27 @@ for (const notAnArray of notArrays) {
assert(coll.drop());
// Basic test of $setUnion with grouping.
assert.commandWorked(coll.insert([
{_id: 1, author: "Kyra", publisher: "Pub1", books: ["Book 1"]},
{_id: 2, author: "Nick", publisher: "Pub3", books: ["Book 2"]},
{_id: 3, author: "Santiago", publisher: "Pub3"},
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]}
]));
assert.commandWorked(
coll.insert([
{_id: 1, author: "Kyra", publisher: "Pub1", books: ["Book 1"]},
{_id: 2, author: "Nick", publisher: "Pub3", books: ["Book 2"]},
{_id: 3, author: "Santiago", publisher: "Pub3"},
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]},
]),
);
result =
coll.aggregate([
{$group: {_id: '$publisher', booksByPublisher: {$setUnion: '$books'}}},
{$sort: {_id: 1}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$booksByPublisher', sortBy: 1}}}}
])
.toArray();
result = coll
.aggregate([
{$group: {_id: "$publisher", booksByPublisher: {$setUnion: "$books"}}},
{$sort: {_id: 1}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$booksByPublisher", sortBy: 1}}}},
])
.toArray();
assert.eq(
result,
[{_id: "Pub1", setUnionArr: ["Book 1"]}, {_id: "Pub3", setUnionArr: ["Book 2", "Book 3"]}]);
assert.eq(result, [
{_id: "Pub1", setUnionArr: ["Book 1"]},
{_id: "Pub3", setUnionArr: ["Book 2", "Book 3"]},
]);
// Basic correctness tests for $setUnion used in $bucket and $bucketAuto. Though $bucket and
// $bucketAuto use accumulators in the same way that $group does, the tests below verifies that
@ -179,20 +230,23 @@ for (let i = 0; i < 10; i++) {
coll.insertMany(docs);
// $bucket
result =
coll.aggregate([{
$bucket: {groupBy: '$_id', boundaries: [0, 5, 10], output: {nums: {$setUnion: "$arr"}}}
}])
.toArray();
assert.eq(result, [{"_id": 0, "nums": [42]}, {"_id": 5, "nums": [42]}]);
result = coll
.aggregate([
{
$bucket: {groupBy: "$_id", boundaries: [0, 5, 10], output: {nums: {$setUnion: "$arr"}}},
},
])
.toArray();
assert.eq(result, [
{"_id": 0, "nums": [42]},
{"_id": 5, "nums": [42]},
]);
// $bucketAuto
result =
coll.aggregate(
[{$bucketAuto: {groupBy: '$_id', buckets: 2, output: {nums: {$setUnion: "$arr"}}}}])
.toArray();
assert.eq(
result,
[{"_id": {"min": 0, "max": 5}, "nums": [42]}, {"_id": {"min": 5, "max": 9}, "nums": [42]}]);
result = coll.aggregate([{$bucketAuto: {groupBy: "$_id", buckets: 2, output: {nums: {$setUnion: "$arr"}}}}]).toArray();
assert.eq(result, [
{"_id": {"min": 0, "max": 5}, "nums": [42]},
{"_id": {"min": 5, "max": 9}, "nums": [42]},
]);
assert(coll.drop());

View File

@ -10,7 +10,7 @@ coll.drop();
const sharded = FixtureHelpers.isSharded(coll);
const memoryLimitMB = sharded ? 200 : 100;
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
const bulk = coll.initializeUnorderedBulkOp();
for (let i = 0; i < memoryLimitMB + 10; i++) {
@ -22,17 +22,18 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
// so may hit the group error code before we hit ExceededMemoryLimit.
const pipeline = [{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$setUnion: '$bigArr'}}}];
const expectedCodes =
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
const pipeline = [{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$setUnion: "$bigArr"}}}];
const expectedCodes = [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
assert.commandFailedWithCode(
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
expectedCodes);
expectedCodes,
);
// The $setUnion accumulator does not support spilling to disk, so ensure that it will fail even
// when disk use is allowed.
assert.commandFailedWithCode(
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}),
expectedCodes);
expectedCodes,
);

View File

@ -9,18 +9,21 @@ import "jstests/libs/query/sbe_assert_error_override.js";
const coll = db[jsTestName()];
coll.drop();
const largestInt =
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
// Makes a string for a unique sales associate name that looks like 'Jim the 4 from CA'.
const associateName = (i, state) => ["Jim", "Pam", "Dwight", "Phyllis"][i % 4] + " the " +
parseInt(i / 4) + " from " + state;
const associateName = (i, state) =>
["Jim", "Pam", "Dwight", "Phyllis"][i % 4] + " the " + parseInt(i / 4) + " from " + state;
// Basic correctness tests.
let docs = [];
const defaultN = 4;
const states = [{state: "CA", sales: 10}, {state: "NY", sales: 7}, {state: "TX", sales: 4}];
const states = [
{state: "CA", sales: 10},
{state: "NY", sales: 7},
{state: "TX", sales: 4},
];
let expectedBottomNAscResults = [];
let expectedTopNAscResults = [];
let expectedBottomNDescResults = [];
@ -65,17 +68,17 @@ function buildTopNBottomNSpec(op, sortSpec, outputSpec, nValue) {
* Helper that verifies that 'op' and 'sortSpec' produce 'expectedResults'.
*/
function assertExpected(op, sortSpec, expectedResults) {
const actual =
coll.aggregate([
{
$group: {
_id: "$state",
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)
}
const actual = coll
.aggregate([
{
$group: {
_id: "$state",
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN),
},
{$sort: {_id: 1}}
])
.toArray();
},
{$sort: {_id: 1}},
])
.toArray();
assert.eq(expectedResults, actual);
// Basic correctness test for $top/$topN/$bottom/$bottomN used in $bucketAuto. Though
@ -84,20 +87,19 @@ function assertExpected(op, sortSpec, expectedResults) {
// allows us to compare the $bucketAuto results to the expected $group results (because there
// are more buckets than groups, it will always be the case that the min value of each bucket
// corresponds to the group key).
let actualBucketAutoResults =
coll.aggregate([
{
$bucketAuto: {
groupBy: '$state',
buckets: 10 * 1000,
output:
{associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)}
}
let actualBucketAutoResults = coll
.aggregate([
{
$bucketAuto: {
groupBy: "$state",
buckets: 10 * 1000,
output: {associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)},
},
{$project: {_id: "$_id.min", associates: 1}},
{$sort: {_id: 1}},
])
.toArray();
},
{$project: {_id: "$_id.min", associates: 1}},
{$sort: {_id: 1}},
])
.toArray();
// Using a computed projection will put the fields out of order. As such, we re-order them
// below.
@ -116,21 +118,20 @@ assertExpected("$bottomN", {sales: -1}, expectedBottomNDescResults);
assertExpected("$topN", {sales: -1}, expectedTopNDescResults);
// Verify that we can compute multiple topN/bottomN groupings in the same $group.
const combinedGroup =
coll.aggregate([
{
$group: {
_id: "$state",
bottomAsc: buildTopNBottomNSpec("$bottomN", {sales: 1}, "$associate", defaultN),
bottomDesc:
buildTopNBottomNSpec("$bottomN", {sales: -1}, "$associate", defaultN),
topAsc: buildTopNBottomNSpec("$topN", {sales: 1}, "$associate", defaultN),
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN)
}
const combinedGroup = coll
.aggregate([
{
$group: {
_id: "$state",
bottomAsc: buildTopNBottomNSpec("$bottomN", {sales: 1}, "$associate", defaultN),
bottomDesc: buildTopNBottomNSpec("$bottomN", {sales: -1}, "$associate", defaultN),
topAsc: buildTopNBottomNSpec("$topN", {sales: 1}, "$associate", defaultN),
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN),
},
{$sort: {_id: 1}}
])
.toArray();
},
{$sort: {_id: 1}},
])
.toArray();
let bottomAsc = [];
let bottomDesc = [];
@ -143,26 +144,25 @@ for (const doc of combinedGroup) {
topDesc.push({_id: doc["_id"], associates: doc["topDesc"]});
}
assert.eq([bottomAsc, bottomDesc, topAsc, topDesc], [
expectedBottomNAscResults,
expectedBottomNDescResults,
expectedTopNAscResults,
expectedTopNDescResults
]);
assert.eq(
[bottomAsc, bottomDesc, topAsc, topDesc],
[expectedBottomNAscResults, expectedBottomNDescResults, expectedTopNAscResults, expectedTopNDescResults],
);
// Verify that we can dynamically compute 'n' based on the group key for $group.
const groupKeyNExpr = {
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4}
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4},
};
const dynamicBottomNResults =
coll.aggregate([{
const dynamicBottomNResults = coll
.aggregate([
{
$group: {
_id: {"st": "$state"},
bottomAssociates:
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
}
}])
.toArray();
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
},
},
])
.toArray();
// Verify that the 'CA' group has 10 results, while all others have only 4.
for (const result of dynamicBottomNResults) {
@ -181,46 +181,54 @@ for (const result of dynamicBottomNResults) {
// When output evaluates to missing for the single version, it should be promoted to null like in
// $first.
const outputMissing = coll.aggregate({
$group: {
_id: "",
bottom: {$bottom: {output: "$b", sortBy: {sales: 1}}},
top: {$top: {output: "$b", sortBy: {sales: 1}}}
}
})
.toArray();
const outputMissing = coll
.aggregate({
$group: {
_id: "",
bottom: {$bottom: {output: "$b", sortBy: {sales: 1}}},
top: {$top: {output: "$b", sortBy: {sales: 1}}},
},
})
.toArray();
assert.eq(null, outputMissing[0]["top"]);
assert.eq(null, outputMissing[0]["bottom"]);
// Error cases.
// Cannot reference the group key in $bottomN when using $bucketAuto.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{
$bucketAuto: {
groupBy: "$state",
buckets: 2,
output: {
bottomAssociates:
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
}
}
}],
cursor: {}
}),
4544714);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$bucketAuto: {
groupBy: "$state",
buckets: 2,
output: {
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
},
},
},
],
cursor: {},
}),
4544714,
);
// Verify that 'n' cannot be greater than the largest signed 64 bit int.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{
$group: {
_id: {'st': '$state'},
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}}
}
}],
cursor: {}
}),
5787903);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$group: {
_id: {"st": "$state"},
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}},
},
},
],
cursor: {},
}),
5787903,
);
assert(coll.drop());
@ -235,7 +243,7 @@ function gameScoreGenerator(i) {
_id: i,
game: "G" + gameId,
player: players[playerId] + Math.floor(i / players.length),
score: score
score: score,
};
}
const nGames = 100;
@ -247,40 +255,44 @@ assert.commandWorked(coll.insert(games));
const gameSpec = {
player: "$player",
score: "$score"
score: "$score",
};
for (const nVal of [defaultN, largestInt]) {
const gameResults =
coll.aggregate([{
const gameResults = coll
.aggregate([
{
$group: {
_id: "$game",
bottomAsc: buildTopNBottomNSpec("$bottomN", {score: 1}, gameSpec, nVal),
topAsc: buildTopNBottomNSpec("$topN", {score: 1}, gameSpec, nVal),
topDesc: buildTopNBottomNSpec("$topN", {score: -1}, gameSpec, nVal),
bottomDesc: buildTopNBottomNSpec("$bottomN", {score: -1}, gameSpec, nVal),
}
}])
.toArray();
},
},
])
.toArray();
for (const doc of gameResults) {
let assertResultsInOrder = function(index, fieldName, arr, isAsc) {
let assertResultsInOrder = function (index, fieldName, arr, isAsc) {
const [first, second] = [arr[index - 1]["score"], arr[index]["score"]];
const cmpResult = isAsc ? first < second : first > second;
assert(cmpResult,
"Incorrect order from accumulator corresponding to field '" + fieldName +
"'; results: " + tojson(arr));
assert(
cmpResult,
"Incorrect order from accumulator corresponding to field '" + fieldName + "'; results: " + tojson(arr),
);
};
let testFieldNames = function(fNames, isAsc) {
let testFieldNames = function (fNames, isAsc) {
for (const fieldName of fNames) {
const arr = doc[fieldName];
// Verify that 'nVal' is greater or equal to the number of results returned.
// Note that we upconvert to NumberDecimal to account for 'largestInt' being a
// NumberDecimal.
assert.gte(NumberDecimal(nVal),
NumberDecimal(arr.length),
nVal + " is not GTE array length of " + tojson(arr) + " for field " +
fieldName);
assert.gte(
NumberDecimal(nVal),
NumberDecimal(arr.length),
nVal + " is not GTE array length of " + tojson(arr) + " for field " + fieldName,
);
for (let i = 1; i < arr.length; ++i) {
assertResultsInOrder(i, fieldName, arr, isAsc);
}
@ -294,12 +306,14 @@ for (const nVal of [defaultN, largestInt]) {
const rejectInvalidSpec = (op, assign, errCode, delProps = []) => {
let spec = Object.assign({}, {output: "$associate", n: 2, sortBy: {sales: 1}}, assign);
delProps.forEach(delProp => delete spec[delProp]);
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, bottomAssociates: {[op]: spec}}}],
cursor: {}
}),
errCode);
delProps.forEach((delProp) => delete spec[delProp]);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, bottomAssociates: {[op]: spec}}}],
cursor: {},
}),
errCode,
);
};
// Reject non-integral/negative values of n.
@ -326,27 +340,26 @@ rejectInvalidSpec("$bottom", {}, 5788002);
// Sort on embedded field.
assert(coll.drop());
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: {b: i}}))));
const embeddedResult =
coll.aggregate(
{$group: {_id: "", result: {$bottomN: {n: 3, output: "$a.b", sortBy: {"a.b": 1}}}}})
.toArray();
const embeddedResult = coll
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a.b", sortBy: {"a.b": 1}}}}})
.toArray();
assert.eq([2, 3, 4], embeddedResult[0].result);
// Sort on array
assert(coll.drop());
const makeArray = (i) => [i, i + 1, i + 2];
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: makeArray(i)}))));
const nestedResult =
coll.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
.toArray();
const nestedResult = coll
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
.toArray();
assert.eq([makeArray(2), makeArray(3), makeArray(4)], nestedResult[0].result);
// Sort on doubly nested array.
assert(coll.drop());
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: [makeArray(i)]}))));
const doublyNestedResult =
coll.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
.toArray();
const doublyNestedResult = coll
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
.toArray();
assert.eq([[makeArray(2)], [makeArray(3)], [makeArray(4)]], doublyNestedResult[0].result);
// Compound Sorting.
@ -354,56 +367,57 @@ coll.drop();
const as = [1, 2, 3];
const bs = [1, 2, 3];
const crossProduct = (arr1, arr2) =>
arr1.map(a => arr2.map(b => ({a, b}))).reduce((docs, inner) => docs.concat(inner));
arr1.map((a) => arr2.map((b) => ({a, b}))).reduce((docs, inner) => docs.concat(inner));
const fullAscending = crossProduct(as, bs);
const aAscendingBDescending = crossProduct(as, bs.reverse());
assert.commandWorked(coll.insertMany(fullAscending));
const actualFullAscending =
coll.aggregate({
$group: {
_id: "",
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}}
}
})
.toArray();
const actualFullAscending = coll
.aggregate({
$group: {
_id: "",
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}},
},
})
.toArray();
assert.eq(fullAscending, actualFullAscending[0]["sorted"]);
const actualAAscendingBDescending =
coll.aggregate({
$group: {
_id: "",
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}}
}
})
.toArray();
const actualAAscendingBDescending = coll
.aggregate({
$group: {
_id: "",
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}},
},
})
.toArray();
assert.eq(aAscendingBDescending, actualAAscendingBDescending[0]["sorted"]);
// $meta sort specification.
assert(coll.drop());
assert.commandWorked(coll.insertMany(
["apples apples pears", "pears pears", "apples apples apples", "apples doughnuts"].map(
text => ({text}))));
assert.commandWorked(
coll.insertMany(
["apples apples pears", "pears pears", "apples apples apples", "apples doughnuts"].map((text) => ({text})),
),
);
assert.commandWorked(coll.createIndex({text: "text"}));
const sortStageResult =
coll.aggregate(
[{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
.toArray()
.map(doc => doc["text"]);
const sortStageResult = coll
.aggregate([{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
.toArray()
.map((doc) => doc["text"]);
const testOperatorText = (op) => {
const opNResult =
coll.aggregate([
{$match: {$text: {$search: "apples pears"}}},
{
$group: {
_id: "",
result: {
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}}
}
}
}
])
.toArray();
const opNResult = coll
.aggregate([
{$match: {$text: {$search: "apples pears"}}},
{
$group: {
_id: "",
result: {
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}},
},
},
},
])
.toArray();
assert.eq(opNResult.length, 1);
assert.eq(sortStageResult, opNResult[0]["result"]);
};
@ -417,10 +431,9 @@ testOperatorText("$topN");
assert(coll.drop());
assert.commandWorked(coll.insertMany([{a: 1}, {a: 2}, {a: 3}]));
const testConstantOutputAndSort = (op) => {
const results =
coll.aggregate(
[{$group: {_id: null, result: {[op]: {n: 3, output: "abc", sortBy: {a: 1}}}}}])
.toArray();
const results = coll
.aggregate([{$group: {_id: null, result: {[op]: {n: 3, output: "abc", sortBy: {a: 1}}}}}])
.toArray();
assert.eq(results.length, 1, results);
assert.docEq(results[0], {_id: null, result: ["abc", "abc", "abc"]}, results);
};

View File

@ -11,8 +11,11 @@ testDb.article.save({
posted: new Date(1079895594000),
pageViews: 5,
tags: ["fun", "good", "fun"],
comments: [{author: "joe", text: "this is cool"}, {author: "sam", text: "this is bad"}],
other: {foo: 5}
comments: [
{author: "joe", text: "this is cool"},
{author: "sam", text: "this is bad"},
],
other: {foo: 5},
});
testDb.article.save({
@ -24,9 +27,9 @@ testDb.article.save({
tags: ["fun", "nasty"],
comments: [
{author: "barbara", text: "this is interesting"},
{author: "jenny", text: "i like to play pinball", votes: 10}
{author: "jenny", text: "i like to play pinball", votes: 10},
],
other: {bar: 14}
other: {bar: 14},
});
testDb.article.save({
@ -38,7 +41,7 @@ testDb.article.save({
tags: ["nasty", "filthy"],
comments: [
{author: "will", text: "i don't like the color"},
{author: "jenny", text: "can i get that in green?"}
{author: "jenny", text: "can i get that in green?"},
],
other: {bar: 14}
other: {bar: 14},
});

View File

@ -24,7 +24,7 @@ function buildAggCmd(pipeline, batchSize) {
return {
aggregate: t.getName(),
pipeline: pipeline,
cursor: (batchSize === undefined ? {} : {batchSize: batchSize}),
cursor: batchSize === undefined ? {} : {batchSize: batchSize},
};
}
@ -42,8 +42,7 @@ function aggCursor(pipeline, firstBatchSize, followupBatchSize) {
const cmdOut = db.runCommand(buildAggCmd(pipeline, firstBatchSize));
assert.commandWorked(cmdOut);
if (firstBatchSize !== undefined)
assert.lte(cmdOut.cursor.firstBatch.length, firstBatchSize);
if (firstBatchSize !== undefined) assert.lte(cmdOut.cursor.firstBatch.length, firstBatchSize);
return makeCursor(cmdOut, followupBatchSize);
}
@ -53,24 +52,22 @@ function aggCursor(pipeline, firstBatchSize, followupBatchSize) {
//
let bigArray = [];
for (let i = 0; i < 1000; i++)
bigArray.push(i);
for (let i = 0; i < 1000; i++) bigArray.push(i);
let bigStr = Array(1001).toString(); // 1000 bytes of ','
let bigStr = Array(1001).toString(); // 1000 bytes of ','
for (let i = 0; i < 100; i++)
t.insert({_id: i, bigArray: bigArray, bigStr: bigStr});
for (let i = 0; i < 100; i++) t.insert({_id: i, bigArray: bigArray, bigStr: bigStr});
//
// do testing
//
// successfully handles results > 16MB (bigArray.length * bytes in bigStr * t.count() == 100MB)
let cursor = aggCursor([{$unwind: '$bigArray'}]); // default settings
let cursor = aggCursor([{$unwind: "$bigArray"}]); // default settings
assert.eq(cursor.itcount(), bigArray.length * t.count());
cursor = aggCursor([{$unwind: '$bigArray'}], 0); // empty first batch
cursor = aggCursor([{$unwind: "$bigArray"}], 0); // empty first batch
assert.eq(cursor.itcount(), bigArray.length * t.count());
cursor = aggCursor([{$unwind: '$bigArray'}], 5, 5); // many small batches
cursor = aggCursor([{$unwind: "$bigArray"}], 5, 5); // many small batches
assert.eq(cursor.itcount(), bigArray.length * t.count());
// empty result set results in cursor.id == 0 unless batchSize is 0;
@ -87,21 +84,21 @@ res = t.runCommand(buildAggCmd([{$noSuchStage: 1}], 0));
assert.commandFailed(res);
// data dependent errors can get ok:1 but fail in getMore if they don't fail in first batch
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, '$bigStr']}}}], 1));
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, "$bigStr"]}}}], 1));
assert.commandFailed(res);
// Setting batchSize 0 doesn't guarantee that command will succeed: it may fail during plan
// selection.
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, '$bigStr']}}}], 0));
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, "$bigStr"]}}}], 0));
if (res.ok) {
assert.throws(function() {
assert.throws(function () {
makeCursor(res).itcount();
});
}
// error if collection dropped after first batch
cursor = aggCursor([{$unwind: '$bigArray'}], 0);
cursor = aggCursor([{$unwind: "$bigArray"}], 0);
t.drop();
assert.throws(function() {
assert.throws(function () {
cursor.itcount();
});

View File

@ -4,7 +4,7 @@ const docsPerBatch = 3;
coll.drop();
// Initialize collection with eight 1M documents, and index on field "a".
const longString = 'x'.repeat(1024 * 1024);
const longString = "x".repeat(1024 * 1024);
for (let i = 0; i < 100; ++i) {
assert.commandWorked(coll.insert({a: 1, bigField: longString}));
}
@ -25,8 +25,7 @@ assert.commandWorked(coll.dropIndex({a: 1}));
try {
cursor.hasNext();
cursor.next();
} catch (e) {
}
} catch (e) {}
// Verify that the server hasn't crashed.
assert.commandWorked(db.adminCommand({ping: 1}));

View File

@ -5,17 +5,19 @@ t.drop();
// first 63MB
for (let i = 0; i < 63; i++) {
assert.commandWorked(t.insert({a: 'a'.repeat(1024 * 1024)}));
assert.commandWorked(t.insert({a: "a".repeat(1024 * 1024)}));
}
// the remaining ~1MB with room for field names and other overhead
assert.commandWorked(t.insert({a: 'a'.repeat(1024 * 1024 - 1106)}));
assert.commandWorked(t.insert({a: "a".repeat(1024 * 1024 - 1106)}));
// do not use cursor form, since it has a different workaroud for this issue.
assert.commandFailed(db.runCommand({
aggregate: t.getName(),
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: '$a'}}}}]
}));
assert.commandFailed(
db.runCommand({
aggregate: t.getName(),
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: "$a"}}}}],
}),
);
// Make sure the server is still up.
assert.commandWorked(db.runCommand('ping'));
assert.commandWorked(db.runCommand("ping"));

View File

@ -20,23 +20,24 @@ coll.drop();
assert.commandWorked(coll.insertOne({date: new Timestamp(1341337661, 1)}));
assert.commandWorked(coll.insertOne({date: new Date(1341337661000)}));
// Aggregate checking various combinations of the constant and the field
let agg_timestamp = coll.aggregate({
$project: {
_id: 0,
dayOfMonth: {$dayOfMonth: '$date'},
dayOfWeek: {$dayOfWeek: '$date'},
dayOfYear: {$dayOfYear: '$date'},
hour: {$hour: '$date'},
minute: {$minute: '$date'},
month: {$month: '$date'},
second: {$second: '$date'},
week: {$week: '$date'},
year: {$year: '$date'}
}
})
.toArray();
let agg_timestamp = coll
.aggregate({
$project: {
_id: 0,
dayOfMonth: {$dayOfMonth: "$date"},
dayOfWeek: {$dayOfWeek: "$date"},
dayOfYear: {$dayOfYear: "$date"},
hour: {$hour: "$date"},
minute: {$minute: "$date"},
month: {$month: "$date"},
second: {$second: "$date"},
week: {$week: "$date"},
year: {$year: "$date"},
},
})
.toArray();
// Assert the two entries are equal
assert.eq(agg_timestamp[0], agg_timestamp[1], 'agg_timestamp failed');
assert.eq(agg_timestamp[0], agg_timestamp[1], "agg_timestamp failed");
// Clear db for timestamp to date compare test
// For historical reasons the compare the same if they are the same 64-bit representation.
@ -49,31 +50,30 @@ agg_timestamp = coll.aggregate({
$project: {
_id: 0,
// comparison is different code path based on order (same as in bson)
ts_date: {$eq: ['$time', '$date']},
date_ts: {$eq: ['$date', '$time']}
}
ts_date: {$eq: ["$time", "$date"]},
date_ts: {$eq: ["$date", "$time"]},
},
});
assert.eq(agg_timestamp.toArray(),
[{ts_date: false, date_ts: false}, {ts_date: false, date_ts: false}]);
assert.eq(agg_timestamp.toArray(), [
{ts_date: false, date_ts: false},
{ts_date: false, date_ts: false},
]);
// Clear db for timestamp comparison tests
assert(coll.drop());
assert.commandWorked(
coll.insertOne({time: new Timestamp(1341337661, 1), time2: new Timestamp(1341337661, 2)}));
assert.commandWorked(coll.insertOne({time: new Timestamp(1341337661, 1), time2: new Timestamp(1341337661, 2)}));
agg_timestamp = coll.aggregate({
$project: {
_id: 0,
cmp: {$cmp: ['$time', '$time2']},
eq: {$eq: ['$time', '$time2']},
gt: {$gt: ['$time', '$time2']},
gte: {$gte: ['$time', '$time2']},
lt: {$lt: ['$time', '$time2']},
lte: {$lte: ['$time', '$time2']},
ne: {$ne: ['$time', '$time2']}
}
cmp: {$cmp: ["$time", "$time2"]},
eq: {$eq: ["$time", "$time2"]},
gt: {$gt: ["$time", "$time2"]},
gte: {$gte: ["$time", "$time2"]},
lt: {$lt: ["$time", "$time2"]},
lte: {$lte: ["$time", "$time2"]},
ne: {$ne: ["$time", "$time2"]},
},
});
var agg_timestampresult =
[{cmp: -1, eq: false, gt: false, gte: false, lt: true, lte: true, ne: true}];
var agg_timestampresult = [{cmp: -1, eq: false, gt: false, gte: false, lt: true, lte: true, ne: true}];
// Assert the results are as expected
assert.eq(
agg_timestamp.toArray(), agg_timestampresult, 'agg_timestamp failed comparing two timestamps');
assert.eq(agg_timestamp.toArray(), agg_timestampresult, "agg_timestamp failed comparing two timestamps");

View File

@ -20,14 +20,16 @@ otherColl.drop();
assert.commandWorked(otherColl.insert({_id: "id0", x: 1}));
assert.commandWorked(otherColl.insert({_id: "id1", x: 2}));
assert.commandWorked(coll.insert({
_id: 0,
link: new DBRef(otherColl.getName(), "id0", db.getName()),
linkArray: [
new DBRef(otherColl.getName(), "id0", db.getName()),
new DBRef(otherColl.getName(), "id1", db.getName())
]
}));
assert.commandWorked(
coll.insert({
_id: 0,
link: new DBRef(otherColl.getName(), "id0", db.getName()),
linkArray: [
new DBRef(otherColl.getName(), "id0", db.getName()),
new DBRef(otherColl.getName(), "id1", db.getName()),
],
}),
);
function projectOnlyPipeline(projection) {
const aggRes = coll.aggregate({$project: projection}).toArray();
@ -38,34 +40,36 @@ function projectOnlyPipeline(projection) {
// Refer to a DBRef sub-field in a projection.
assert.eq(projectOnlyPipeline({refVal: "$link.$ref"}), [{_id: 0, refVal: otherColl.getName()}]);
assert.eq(projectOnlyPipeline({refVal: "$linkArray.$ref"}),
[{_id: 0, refVal: [otherColl.getName(), otherColl.getName()]}]);
assert.eq(projectOnlyPipeline({refVal: "$linkArray.$ref"}), [
{_id: 0, refVal: [otherColl.getName(), otherColl.getName()]},
]);
assert.eq(projectOnlyPipeline({idVal: "$link.$id"}), [{_id: 0, idVal: "id0"}]);
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$id"}), [{_id: 0, idVal: ["id0", "id1"]}]);
assert.eq(projectOnlyPipeline({idVal: "$link.$db"}), [{_id: 0, idVal: db.getName()}]);
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$db"}),
[{_id: 0, idVal: [db.getName(), db.getName()]}]);
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$db"}), [{_id: 0, idVal: [db.getName(), db.getName()]}]);
// Use a DBRef sub-field in an expression.
assert.eq(projectOnlyPipeline({idLen: {$strLenCP: "$link.$id"}}), [{_id: 0, idLen: "id0".length}]);
// Project away DBRef values.
assert.eq(projectOnlyPipeline({link: {$ref: 0}, linkArray: 0}),
[{_id: 0, link: {$id: "id0", $db: db.getName()}}]);
assert.eq(projectOnlyPipeline({link: {$ref: 0}, linkArray: 0}), [{_id: 0, link: {$id: "id0", $db: db.getName()}}]);
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [{
_id: 0,
linkArray: [
{$ref: otherColl.getName(), $db: db.getName()},
{$ref: otherColl.getName(), $db: db.getName()}
]
}]);
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [
{
_id: 0,
linkArray: [
{$ref: otherColl.getName(), $db: db.getName()},
{$ref: otherColl.getName(), $db: db.getName()},
],
},
]);
// Assigning to a DBRef field.
assert.eq(projectOnlyPipeline({link: {$ref: 1, $id: 1, $db: "someOtherDB"}}),
[{_id: 0, link: new DBRef(otherColl.getName(), "id0", "someOtherDB")}]);
assert.eq(projectOnlyPipeline({link: {$ref: 1, $id: 1, $db: "someOtherDB"}}), [
{_id: 0, link: new DBRef(otherColl.getName(), "id0", "someOtherDB")},
]);
// While not a 'feature' we advertise, it is allowed to assign to top-level DBRef fields.
assert.eq(projectOnlyPipeline({$ref: "$link.$ref"}), [{_id: 0, $ref: otherColl.getName()}]);
@ -75,46 +79,61 @@ assert.eq(projectOnlyPipeline({$ref: "$link.$ref"}), [{_id: 0, $ref: otherColl.g
assert.throwsWithCode(() => coll.aggregate({$project: {x: "$$ref"}}).toArray(), 17276);
// It can be accessed through $$ROOT, however.
assert.eq(coll.aggregate([
// Rather than go through the trouble of inserting a document with a top-level
// $-prefixed field, create one in an intermediate $project stage.
{$project: {"$ref": "hello world"}},
// Make sure that no optimization coalesces the above projection stage with the
// below one.
{$_internalInhibitOptimization: {}},
{$project: {x: "$$ROOT.$ref"}}
])
.toArray(),
[{_id: 0, x: "hello world"}]);
assert.eq(
coll
.aggregate([
// Rather than go through the trouble of inserting a document with a top-level
// $-prefixed field, create one in an intermediate $project stage.
{$project: {"$ref": "hello world"}},
// Make sure that no optimization coalesces the above projection stage with the
// below one.
{$_internalInhibitOptimization: {}},
{$project: {x: "$$ROOT.$ref"}},
])
.toArray(),
[{_id: 0, x: "hello world"}],
);
// Do a count (using $group) on a DBRef field.
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: 1}}}).toArray(),
[{_id: db.getName(), count: 1}]);
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: 1}}}).toArray(), [{_id: db.getName(), count: 1}]);
// Refer to a DBRef field in an accumulator.
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: {$size: "$linkArray.$ref"}}}})
.toArray(),
[{_id: db.getName(), count: 2}]);
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: {$size: "$linkArray.$ref"}}}}).toArray(), [
{_id: db.getName(), count: 2},
]);
// Use $lookup with a DBRef.
// Equality match version.
const lookupEqualityPipeline = [{$lookup: {from: otherColl.getName(),
localField: "link.$id",
foreignField: "_id",
as: "joinedField"}},
{$project: {link: 0, linkArray: 0}}];
assert.eq(coll.aggregate(lookupEqualityPipeline).toArray(),
[{_id: 0, joinedField: [{_id: "id0", x: 1}]}]);
const lookupEqualityPipeline = [
{$lookup: {from: otherColl.getName(), localField: "link.$id", foreignField: "_id", as: "joinedField"}},
{$project: {link: 0, linkArray: 0}},
];
assert.eq(coll.aggregate(lookupEqualityPipeline).toArray(), [{_id: 0, joinedField: [{_id: "id0", x: 1}]}]);
// Foreign pipeline.
const lookupSubPipePipeline = [{$lookup: {from: otherColl.getName(),
let: {idsWanted: "$linkArray.$id"},
pipeline: [{$match: {$expr: {$in: ["$_id", "$$idsWanted"]}}}],
as: "joinedField"}},
{$project: {link: 0, linkArray: 0}}];
assert(anyEq(coll.aggregate(lookupSubPipePipeline).toArray(),
[{_id: 0, joinedField: [{_id: "id0", x: 1}, {_id: "id1", x: 2}]}]));
const lookupSubPipePipeline = [
{
$lookup: {
from: otherColl.getName(),
let: {idsWanted: "$linkArray.$id"},
pipeline: [{$match: {$expr: {$in: ["$_id", "$$idsWanted"]}}}],
as: "joinedField",
},
},
{$project: {link: 0, linkArray: 0}},
];
assert(
anyEq(coll.aggregate(lookupSubPipePipeline).toArray(), [
{
_id: 0,
joinedField: [
{_id: "id0", x: 1},
{_id: "id1", x: 2},
],
},
]),
);
(function testGraphLookup() {
// $graphLookup using DBRef.
@ -122,28 +141,34 @@ assert(anyEq(coll.aggregate(lookupSubPipePipeline).toArray(),
graphLookupColl.drop();
// id0 -> id1 -> id2 -> id0
assert.commandWorked(graphLookupColl.insert(
{_id: "id0", link: new DBRef(graphLookupColl.getName(), "id1", db.getName())}));
assert.commandWorked(graphLookupColl.insert(
{_id: "id1", link: new DBRef(graphLookupColl.getName(), "id2", db.getName())}));
assert.commandWorked(graphLookupColl.insert(
{_id: "id2", link: new DBRef(graphLookupColl.getName(), "id0", db.getName())}));
assert.commandWorked(
graphLookupColl.insert({_id: "id0", link: new DBRef(graphLookupColl.getName(), "id1", db.getName())}),
);
assert.commandWorked(
graphLookupColl.insert({_id: "id1", link: new DBRef(graphLookupColl.getName(), "id2", db.getName())}),
);
assert.commandWorked(
graphLookupColl.insert({_id: "id2", link: new DBRef(graphLookupColl.getName(), "id0", db.getName())}),
);
// id3 -> id4
assert.commandWorked(graphLookupColl.insert(
{_id: "id3", link: new DBRef(graphLookupColl.getName(), "id4", db.getName())}));
assert.commandWorked(
graphLookupColl.insert({_id: "id3", link: new DBRef(graphLookupColl.getName(), "id4", db.getName())}),
);
assert.commandWorked(graphLookupColl.insert({_id: "id4", link: null}));
const graphLookupPipeline = [{
$graphLookup: {
from: graphLookupColl.getName(),
startWith: "$link.$id",
connectFromField: "link.$id",
connectToField: "_id",
as: "connectedDocuments"
}
},
{$sort: {_id: 1}}];
const graphLookupPipeline = [
{
$graphLookup: {
from: graphLookupColl.getName(),
startWith: "$link.$id",
connectFromField: "link.$id",
connectToField: "_id",
as: "connectedDocuments",
},
},
{$sort: {_id: 1}},
];
const res = graphLookupColl.aggregate(graphLookupPipeline).toArray();
// id0, id1, and id2 are all connected.
@ -175,7 +200,7 @@ assert.commandWorked(coll.createIndex({"link.$ref": 1}, {unique: true}));
thirdColl
.aggregate([
{$project: {"link.$ref": otherColl.getName(), "link.$id": "id0", sentinel: "foo"}},
{$merge: {into: coll.getName(), on: "link.$ref", whenMatched: "replace"}}
{$merge: {into: coll.getName(), on: "link.$ref", whenMatched: "replace"}},
])
.itcount();
@ -190,13 +215,14 @@ thirdColl
$merge: {
into: coll.getName(),
on: "link.$ref",
whenMatched: [{
$project:
{"link.$ref": "otherRef", "link.$id": "otherId", "link.$db": "otherDB"}
}],
whenNotMatched: "discard"
}
}
whenMatched: [
{
$project: {"link.$ref": "otherRef", "link.$id": "otherId", "link.$db": "otherDB"},
},
],
whenNotMatched: "discard",
},
},
])
.itcount();
assert.eq(coll.find().toArray()[0], {_id: 0, link: new DBRef("otherRef", "otherId", "otherDB")});
assert.eq(coll.find().toArray()[0], {_id: 0, link: new DBRef("otherRef", "otherId", "otherDB")});

View File

@ -9,10 +9,7 @@ for (let i = 0; i < 50; ++i) {
assert.commandWorked(coll.insert({"": 123, "b": 456, sortField: i}));
}
assert.eq(coll.aggregate([
{$sort: {sortField: 1}},
{$addFields: {"m": {$meta: "sortKey"}}},
{$match: {"b": 456}}
])
.itcount(),
kNumDocs);
assert.eq(
coll.aggregate([{$sort: {sortField: 1}}, {$addFields: {"m": {$meta: "sortKey"}}}, {$match: {"b": 456}}]).itcount(),
kNumDocs,
);

View File

@ -6,7 +6,7 @@ const testDB = db.getSiblingDB("jsTestName");
assert.commandWorked(testDB.dropDatabase());
const coll = testDB.coll;
const largeString = 'x'.repeat(10 * 1024 * 1024);
const largeString = "x".repeat(10 * 1024 * 1024);
assert.commandWorked(coll.insert({a: 1, b: largeString}));
// Use '$addFields' to create extra-large documents in the middle of the pipeline followed by
@ -15,7 +15,7 @@ const pipeline = [
{$_internalInhibitOptimization: {}},
{$addFields: {c: {$concat: ["$b", "-"]}}},
{$match: {c: {$exists: true}}},
{$project: {a: 1}}
{$project: {a: 1}},
];
assert.doesNotThrow(() => coll.aggregate(pipeline).toArray());
assert.doesNotThrow(() => coll.aggregate(pipeline).toArray());

View File

@ -47,10 +47,10 @@ function runAgg(pipeline) {
function testLargeIn() {
jsTestLog("Testing large $in");
// Int limit is different than double limit.
const filterValsInts = range(1200000).map(i => NumberInt(i));
const filterValsInts = range(1200000).map((i) => NumberInt(i));
runAgg([{$match: {a: {$in: filterValsInts}}}]);
const filterValsDoubles = range(1000000).map(i => i * 1.0);
const filterValsDoubles = range(1000000).map((i) => i * 1.0);
runAgg([{$match: {a: {$in: filterValsDoubles}}}]);
}
@ -58,10 +58,10 @@ function testLargeIn() {
function testLargeSwitch() {
jsTestLog("Testing large $switch");
const cases = range(150000)
.map(function(i) {
return {case: {$gt: ["$a", i]}, then: i};
})
.reverse();
.map(function (i) {
return {case: {$gt: ["$a", i]}, then: i};
})
.reverse();
runAgg([{$project: {b: {$switch: {branches: cases, default: 345678}}}}]);
}
@ -72,21 +72,23 @@ function testLargeBucket() {
for (let i = 0; i < 100000; i++) {
boundaries.push(i);
}
runAgg([{
$bucket: {
groupBy: "$a",
boundaries: boundaries,
default: "default",
output: {"count": {$sum: 1}}
}
}]);
runAgg([
{
$bucket: {
groupBy: "$a",
boundaries: boundaries,
default: "default",
output: {"count": {$sum: 1}},
},
},
]);
}
// Construct a {$project: {a0: 1, a1: 1, ...}}.
function testLargeProject() {
jsTestLog("Testing large $project");
const projectFields = {};
range(1000000).forEach(function(i) {
range(1000000).forEach(function (i) {
projectFields["a" + i] = NumberInt(1);
});
runAgg([{$project: projectFields}]);
@ -105,51 +107,51 @@ function testLargeAndOrPredicates() {
// Large $match of the form {$match: {a0: 1, a1: 1, ...}}
const largeMatch = {};
range(800000).forEach(function(i) {
range(800000).forEach(function (i) {
largeMatch["a" + i] = NumberInt(1);
});
runAgg([{$match: largeMatch}]);
function intStream(n) {
return range(n).map(i => NumberInt(i));
return range(n).map((i) => NumberInt(i));
}
const andOrFilters = [
// Plain a=i filter.
intStream(500000).map(function(i) {
intStream(500000).map(function (i) {
return {a: i};
}),
// a_i = i filter. Different field for each value.
intStream(500000).map(function(i) {
intStream(500000).map(function (i) {
const field = "a" + i;
return {[field]: i};
}),
// Mix of lt and gt with the same field.
intStream(500000).map(function(i) {
intStream(500000).map(function (i) {
const predicate = i % 2 ? {$lt: i} : {$gt: i};
return {a: predicate};
}),
// Mix of lt and gt with different fields.
intStream(400000).map(function(i) {
intStream(400000).map(function (i) {
const field = "a" + i;
const predicate = i % 2 ? {$lt: i} : {$gt: i};
return {[field]: predicate};
}),
// Mix of lt and gt wrapped in not with different fields.
intStream(300000).map(function(i) {
intStream(300000).map(function (i) {
const field = "a" + i;
const predicate = i % 2 ? {$lt: i} : {$gt: i};
return {[field]: {$not: predicate}};
}),
// $exists on different fields.
intStream(400000).map(function(i) {
intStream(400000).map(function (i) {
const field = "a" + i;
return {[field]: {$exists: true}};
}),
intStream(400000).map(function(i) {
intStream(400000).map(function (i) {
const field = "a" + i;
return {[field]: {$exists: false}};
})
}),
];
for (const m of andOrFilters) {
runAgg([{$match: {$and: m}}]);
@ -161,7 +163,7 @@ function testLongFieldNames() {
jsTestLog("Testing $match with long field name");
// Test with a long field name that's accepted by the server.
{
const longFieldName = 'a'.repeat(10_000_000);
const longFieldName = "a".repeat(10_000_000);
const predicate = {[longFieldName]: 1};
runAgg([{$match: predicate}]);
runAgg([{$match: {$and: [predicate]}}]);
@ -170,7 +172,7 @@ function testLongFieldNames() {
// Test with a field name that's too long, where the server rejects it.
{
const extraLongFieldName = 'a'.repeat(17_000_000);
const extraLongFieldName = "a".repeat(17_000_000);
const predicate = {[extraLongFieldName]: 1};
assert.throwsWithCode(() => runAgg([{$match: predicate}]), 17260);
assert.throwsWithCode(() => runAgg([{$match: {$and: [predicate]}}]), 17260);
@ -192,9 +194,9 @@ function testDeeplyNestedPath() {
// Test pipeline length.
function testPipelineLimits() {
jsTestLog("Testing large agg pipelines");
const pipelineLimit =
assert.commandWorked(db.adminCommand({getParameter: 1, internalPipelineLengthLimit: 1}))
.internalPipelineLengthLimit;
const pipelineLimit = assert.commandWorked(
db.adminCommand({getParameter: 1, internalPipelineLengthLimit: 1}),
).internalPipelineLengthLimit;
let stages = [
{$limit: 1},
{$skip: 1},
@ -209,7 +211,7 @@ function testPipelineLimits() {
];
for (const stage of stages) {
const pipeline = range(pipelineLimit).map(_ => stage);
const pipeline = range(pipelineLimit).map((_) => stage);
jsTestLog(stage);
runAgg(pipeline);
}
@ -223,13 +225,13 @@ function testPipelineLimits() {
let fieldIndex = 0;
function generateNestedAndOrHelper(type, branchingFactor, maxDepth) {
if (maxDepth === 0) {
const field = 'a' + fieldIndex;
const field = "a" + fieldIndex;
const query = {[field]: NumberInt(fieldIndex)};
fieldIndex++;
return query;
}
const oppositeType = type === '$and' ? '$or' : '$and';
const oppositeType = type === "$and" ? "$or" : "$and";
const children = [];
for (let i = 0; i < branchingFactor; i++) {
const childQuery = generateNestedAndOrHelper(oppositeType, branchingFactor, maxDepth - 1);
@ -246,7 +248,7 @@ function generateNestedAndOr(type, branchingFactor, maxDepth) {
function testNestedAndOr() {
jsTestLog("Testing nested $and/$or");
for (const topLevelType of ['$and', '$or']) {
for (const topLevelType of ["$and", "$or"]) {
// Test different types of nested queries
let [branchingFactor, maxDepth] = [3, 10];
const deepNarrowQuery = generateNestedAndOr(topLevelType, branchingFactor, maxDepth);
@ -303,7 +305,7 @@ const tests = [
testPipelineLimits,
testLargeSetFunction,
testLargeConcatFunction,
testLargeArrayToObjectFunction
testLargeArrayToObjectFunction,
];
for (const test of tests) {

View File

@ -7,15 +7,18 @@
const coll = db[jsTestName()];
coll.drop();
assert.commandWorked(coll.insertMany([
{_id: 1, "obj": {"obj": {}}},
{_id: 2},
]));
assert.commandWorked(coll.insertMany([{_id: 1, "obj": {"obj": {}}}, {_id: 2}]));
// Update a field to scalar
let results =
coll.aggregate([{$addFields: {"obj": null}}, {$sort: {"obj.obj": 1, _id: 1}}]).toArray();
assert.eq(results, [{_id: 1, "obj": null}, {_id: 2, "obj": null}], results);
let results = coll.aggregate([{$addFields: {"obj": null}}, {$sort: {"obj.obj": 1, _id: 1}}]).toArray();
assert.eq(
results,
[
{_id: 1, "obj": null},
{_id: 2, "obj": null},
],
results,
);
// Remove a field
results = coll.aggregate([{$project: {"obj": 0}}, {$sort: {"obj.obj": 1, _id: 1}}]).toArray();

View File

@ -33,7 +33,7 @@ function setMemoryParamHelper(paramName, memoryLimit) {
cmdObj: {
setParameter: 1,
[paramName]: memoryLimit,
}
},
});
assert.gt(commandResArr.length, 0, "Setting memory limit on primaries failed");
const oldMemoryLimit = assert.commandWorked(commandResArr[0]).was;
@ -57,7 +57,7 @@ const memoryLimitMB = sharded ? 200 : 100;
const isSbeGroupLookupPushdownEnabled = checkSbeRestrictedOrFullyEnabled(db);
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
for (let i = 0; i < memoryLimitMB + 1; i++)
assert.commandWorked(coll.insert({_id: i, bigStr: i + bigStr, random: Math.random()}));
@ -66,24 +66,21 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
function test({pipeline, expectedCodes, canSpillToDisk}) {
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
assert.commandFailedWithCode(
db.runCommand(
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
expectedCodes);
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
expectedCodes,
);
// If this command supports spilling to disk, ensure that it will succeed when disk use is
// allowed.
const res = db.runCommand(
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true});
const res = db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true});
if (canSpillToDisk) {
assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(),
coll.count()); // all tests output one doc per input doc
assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(), coll.count()); // all tests output one doc per input doc
if (isSbeGroupLookupPushdownEnabled) {
const explain = db.runCommand({
explain:
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}
explain: {aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true},
});
const hashAggGroups = getSbePlanStages(explain, 'group');
const hashAggGroups = getSbePlanStages(explain, "group");
if (hashAggGroups.length > 0) {
assert.eq(hashAggGroups.length, 1, explain);
const hashAggGroup = hashAggGroups[0];
@ -101,7 +98,7 @@ function setHashAggParameters(memoryLimit, atLeast) {
cmdObj: {
setParameter: 1,
internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill: memoryLimit,
}
},
});
assert.gt(memLimitCommandResArr.length, 0, "Setting memory limit on primaries failed.");
const oldMemoryLimit = assert.commandWorked(memLimitCommandResArr[0]).was;
@ -111,7 +108,7 @@ function setHashAggParameters(memoryLimit, atLeast) {
cmdObj: {
setParameter: 1,
internalQuerySlotBasedExecutionHashAggMemoryCheckPerAdvanceAtLeast: atLeast,
}
},
});
assert.gt(atLeastCommandResArr.length, 0, "Setting atLeast limit on primaries failed.");
const oldAtLeast = assert.commandWorked(atLeastCommandResArr[0]).was;
@ -129,10 +126,10 @@ function testWithHashAggMemoryLimit({pipeline, expectedCodes, canSpillToDisk, me
}
testWithHashAggMemoryLimit({
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}],
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true,
memoryLimit: 1024
memoryLimit: 1024,
});
// Sorting with _id would use index which doesn't require external sort, so sort by 'random'
@ -140,73 +137,70 @@ testWithHashAggMemoryLimit({
test({
pipeline: [{$sort: {random: 1}}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
test({
pipeline: [{$sort: {bigStr: 1}}], // big key and value
pipeline: [{$sort: {bigStr: 1}}], // big key and value
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
// Test that sort + large limit won't crash the server (SERVER-10136)
test({
pipeline: [{$sort: {bigStr: 1}}, {$limit: 1000 * 1000 * 1000}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
// Test combining two external sorts in both same and different orders.
test({
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {_id: 1}}],
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {_id: 1}}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
test({
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {_id: -1}}],
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {_id: -1}}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
test({
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {random: 1}}],
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {random: 1}}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
test({
pipeline: [{$sort: {random: 1}}, {$group: {_id: '$_id', bigStr: {$first: '$bigStr'}}}],
pipeline: [{$sort: {random: 1}}, {$group: {_id: "$_id", bigStr: {$first: "$bigStr"}}}],
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
canSpillToDisk: true
canSpillToDisk: true,
});
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
// so may hit the group error code before we hit ExceededMemoryLimit.
test({
pipeline: [{$group: {_id: null, bigArray: {$push: '$bigStr'}}}],
expectedCodes:
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
canSpillToDisk: false
pipeline: [{$group: {_id: null, bigArray: {$push: "$bigStr"}}}],
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
canSpillToDisk: false,
});
test({
pipeline:
[{$group: {_id: null, bigArray: {$addToSet: {$concat: ['$bigStr', {$toString: "$_id"}]}}}}],
expectedCodes:
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
canSpillToDisk: false
pipeline: [{$group: {_id: null, bigArray: {$addToSet: {$concat: ["$bigStr", {$toString: "$_id"}]}}}}],
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
canSpillToDisk: false,
});
for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) {
for (const op of ["$firstN", "$lastN", "$minN", "$maxN", "$topN", "$bottomN"]) {
jsTestLog("Testing op " + op);
let spec = {n: 100000000};
if (op === '$topN' || op === '$bottomN') {
spec['sortBy'] = {random: 1};
spec['output'] = '$bigStr';
if (op === "$topN" || op === "$bottomN") {
spec["sortBy"] = {random: 1};
spec["output"] = "$bigStr";
} else {
// $firstN/$lastN/$minN/$maxN accept 'input'.
spec['input'] = '$bigStr';
spec["input"] = "$bigStr";
}
// By grouping all of the entries in the same group, it is the case that we will either
@ -216,18 +210,17 @@ for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) {
// reduce the memory consumption of our group in this case.
test({
pipeline: [{$group: {_id: null, bigArray: {[op]: spec}}}],
expectedCodes:
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
canSpillToDisk: false
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
canSpillToDisk: false,
});
// Because each group uses less than the configured limit, but cumulatively they exceed
// the limit for $group, we only check for 'QueryExceededMemoryLimitNoDiskUseAllowed'
// when disk use is disabled.
test({
pipeline: [{$group: {_id: '$_id', bigArray: {[op]: spec}}}],
pipeline: [{$group: {_id: "$_id", bigArray: {[op]: spec}}}],
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed],
canSpillToDisk: true
canSpillToDisk: true,
});
}
// don't leave large collection laying around
@ -245,25 +238,23 @@ for (let i = 0; i < numGroups; ++i) {
_id: counter++,
a: i,
b: 100 * i + j,
c: 100 * i + j % 5,
c: 100 * i + (j % 5),
obj: {a: i, b: j},
random: Math.random()
random: Math.random(),
};
assert.commandWorked(coll.insert(doc));
}
}
function setHashGroupMemoryParameters(memoryLimit) {
return setMemoryParamHelper(
"internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", memoryLimit);
return setMemoryParamHelper("internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", memoryLimit);
}
// Runs a group query containing the given 'accumulator' after sorting the data by the given
// 'sortInputBy' field. Then verifies that the query results are equal to 'expectedOutput'. If SBE
// is enabled, also runs explain and checks that the execution stats show that spilling occurred.
function testAccumulator({accumulator, sortInputBy, expectedOutput, ignoreArrayOrder = false}) {
const pipeline =
[{$sort: {[sortInputBy]: 1}}, {$group: {_id: "$a", acc: accumulator}}, {$sort: {_id: 1}}];
const pipeline = [{$sort: {[sortInputBy]: 1}}, {$group: {_id: "$a", acc: accumulator}}, {$sort: {_id: 1}}];
const results = coll.aggregate(pipeline).toArray();
if (ignoreArrayOrder) {
@ -289,9 +280,8 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: 100},
{_id: 2, acc: 200},
{_id: 3, acc: 300},
{_id: 4, acc: 400}
]
{_id: 4, acc: 400},
],
});
testAccumulator({
@ -302,8 +292,8 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: 109},
{_id: 2, acc: 209},
{_id: 3, acc: 309},
{_id: 4, acc: 409}
]
{_id: 4, acc: 409},
],
});
testAccumulator({
@ -314,8 +304,8 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: 100},
{_id: 2, acc: 200},
{_id: 3, acc: 300},
{_id: 4, acc: 400}
]
{_id: 4, acc: 400},
],
});
testAccumulator({
@ -326,8 +316,8 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: 109},
{_id: 2, acc: 209},
{_id: 3, acc: 309},
{_id: 4, acc: 409}
]
{_id: 4, acc: 409},
],
});
testAccumulator({
@ -338,8 +328,8 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: 1045},
{_id: 2, acc: 2045},
{_id: 3, acc: 3045},
{_id: 4, acc: 4045}
]
{_id: 4, acc: 4045},
],
});
testAccumulator({
@ -350,8 +340,8 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: 104.5},
{_id: 2, acc: 204.5},
{_id: 3, acc: 304.5},
{_id: 4, acc: 404.5}
]
{_id: 4, acc: 404.5},
],
});
testAccumulator({
@ -388,19 +378,19 @@ function testSpillingForVariousAccumulators() {
{_id: 1, acc: {a: 1, b: 9}},
{_id: 2, acc: {a: 2, b: 9}},
{_id: 3, acc: {a: 3, b: 9}},
{_id: 4, acc: {a: 4, b: 9}}
{_id: 4, acc: {a: 4, b: 9}},
],
});
}
(function() {
const kMemLimit = 100;
let oldMemSettings = setHashGroupMemoryParameters(kMemLimit);
try {
testSpillingForVariousAccumulators();
} finally {
setHashGroupMemoryParameters(oldMemSettings);
}
(function () {
const kMemLimit = 100;
let oldMemSettings = setHashGroupMemoryParameters(kMemLimit);
try {
testSpillingForVariousAccumulators();
} finally {
setHashGroupMemoryParameters(oldMemSettings);
}
})();
assert(coll.drop());
@ -418,7 +408,9 @@ function setupCollections(localRecords, foreignRecords, foreignField) {
function setHashLookupParameters(memoryLimit) {
return setMemoryParamHelper(
"internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill", memoryLimit);
"internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill",
memoryLimit,
);
}
/**
@ -433,23 +425,26 @@ function runTest_MultipleLocalForeignRecords({
foreignRecords,
foreignField,
idsExpectedToMatch,
spillsToDisk
spillsToDisk,
}) {
setupCollections(localRecords, foreignRecords, foreignField);
const pipeline = [{
$lookup: {
from: foreignColl.getName(),
localField: localField,
foreignField: foreignField,
as: "matched"
}}];
const pipeline = [
{
$lookup: {
from: foreignColl.getName(),
localField: localField,
foreignField: foreignField,
as: "matched",
},
},
];
const results = localColl.aggregate(pipeline, {allowDiskUse: true}).toArray();
const explain = localColl.explain('executionStats').aggregate(pipeline, {allowDiskUse: true});
const explain = localColl.explain("executionStats").aggregate(pipeline, {allowDiskUse: true});
const pipelineWasSplit = !!explain.splitPipeline;
// If sharding is enabled, or the pipeline was split, then '$lookup' is not pushed down to SBE.
if (isSbeGroupLookupPushdownEnabled && !sharded && !pipelineWasSplit) {
const hLookups = getSbePlanStages(explain, 'hash_lookup');
const hLookups = getSbePlanStages(explain, "hash_lookup");
assert.eq(hLookups.length, 1, explain);
const hLookup = hLookups[0];
assert(hLookup, explain);
@ -465,12 +460,12 @@ function runTest_MultipleLocalForeignRecords({
// Extract matched foreign ids from the "matched" field.
for (let i = 0; i < results.length; i++) {
const matchedIds = results[i].matched.map(x => (x._id));
const matchedIds = results[i].matched.map((x) => x._id);
// Order of the elements within the arrays is not significant for 'assertArrayEq'.
assertArrayEq({
actual: matchedIds,
expected: idsExpectedToMatch[i],
extraErrorMsg: " **TEST** " + testDescription + " " + tojson(explain)
extraErrorMsg: " **TEST** " + testDescription + " " + tojson(explain),
});
}
}
@ -494,7 +489,7 @@ function runHashLookupSpill({memoryLimit, spillsToDisk}) {
foreignRecords: docs,
foreignField: "a",
idsExpectedToMatch: [[1, 2, 4]],
spillsToDisk: spillsToDisk
spillsToDisk: spillsToDisk,
});
})();
@ -526,20 +521,19 @@ function runHashLookupSpill({memoryLimit, spillsToDisk}) {
foreignRecords: foreignDocs,
foreignField: "b",
idsExpectedToMatch: [[], [8], [9, 10], [11, 12, 13], [11, 12, 13], []],
spillsToDisk: spillsToDisk
spillsToDisk: spillsToDisk,
});
})();
return oldSettings;
}
const oldMemSettings =
assert
.commandWorked(db.adminCommand({
getParameter: 1,
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1
}))
.internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
const oldMemSettings = assert.commandWorked(
db.adminCommand({
getParameter: 1,
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1,
}),
).internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
(function runAllDiskTest() {
try {

File diff suppressed because it is too large Load Diff

View File

@ -13,4 +13,4 @@ assert.eq(res[0].x, 0);
// Make sure having an undefined doesn't break pipelines that do use the field
res = t.aggregate({$project: {undef: 1}}).toArray();
assert.eq(res[0].undef, undefined);
assert.eq(typeof (res[0].undef), "undefined");
assert.eq(typeof res[0].undef, "undefined");

View File

@ -29,8 +29,8 @@ assertBoolValue(true, 1.0);
assertBoolValue(false, null);
// Always true types.
assertBoolValue(true, '');
assertBoolValue(true, 'a');
assertBoolValue(true, "");
assertBoolValue(true, "a");
assertBoolValue(true, "$object");
assertBoolValue(true, []);
assertBoolValue(true, [1]);
@ -40,4 +40,4 @@ assertBoolValue(true, /a/);
assertBoolValue(true, new Timestamp());
// Missing field.
assertBoolValue(false, '$missingField');
assertBoolValue(false, "$missingField");

View File

@ -11,8 +11,8 @@ assert.commandWorked(coll.createIndex({y: 1}));
let result = coll.explain().aggregate([{$match: {x: 1, y: 1}}]);
assert.eq(null, getAggPlanStage(result, "CACHED_PLAN"));
assert(result.hasOwnProperty('serverInfo'), result);
assert.hasFields(result.serverInfo, ['host', 'port', 'version', 'gitVersion']);
assert(result.hasOwnProperty("serverInfo"), result);
assert.hasFields(result.serverInfo, ["host", "port", "version", "gitVersion"]);
// At this point, there should be no entries in the plan cache.
result = coll.explain().aggregate([{$match: {x: 1, y: 1}}]);
@ -29,6 +29,6 @@ assert.eq(null, getAggPlanStage(result, "CACHED_PLAN"));
// into the query layer. In these cases we use a different explain mechanism. Using $lookup will
// prevent this optimization and stress an explain implementation in the aggregation layer. Test
// that this implementation also includes serverInfo.
result = coll.explain().aggregate([{$lookup: {from: 'other_coll', pipeline: [], as: 'docs'}}]);
assert(result.hasOwnProperty('serverInfo'), result);
assert.hasFields(result.serverInfo, ['host', 'port', 'version', 'gitVersion']);
result = coll.explain().aggregate([{$lookup: {from: "other_coll", pipeline: [], as: "docs"}}]);
assert(result.hasOwnProperty("serverInfo"), result);
assert.hasFields(result.serverInfo, ["host", "port", "version", "gitVersion"]);

View File

@ -38,8 +38,8 @@ function checkResults({results, verbosity}) {
const execStatsStages = getAggPlanStages(results, "limit");
assert.gt(queryPlannerStages.length, 0, results);
assert.gt(execStatsStages.length, 0, results);
const planNodeIds = new Set(queryPlannerStages.map(stage => stage.planNodeId));
return [execStatsStages.filter(stage => planNodeIds.has(stage.planNodeId)), "limit"];
const planNodeIds = new Set(queryPlannerStages.map((stage) => stage.planNodeId));
return [execStatsStages.filter((stage) => planNodeIds.has(stage.planNodeId)), "limit"];
}
return [getAggPlanStages(results, "LIMIT"), "limitAmount"];
})();

View File

@ -9,7 +9,7 @@ for (let i = 0; i < 10; ++i) {
}
const collation = {
collation: {locale: "zh", backwards: false}
collation: {locale: "zh", backwards: false},
};
const firstResults = coll.aggregate([{$sort: {_id: 1}}], collation).toArray();
@ -19,4 +19,4 @@ assert.commandWorked(coll.explain().aggregate([], collation));
const secondResults = coll.aggregate([{$sort: {_id: 1}}], collation).toArray();
// Assert that the result didn't change after an explain helper is issued.
assert.eq(firstResults, secondResults);
assert.eq(firstResults, secondResults);

View File

@ -33,7 +33,7 @@ const pipelineShardedStages = [
{$skip: 100},
{$project: {arr: 0}},
{$group: {_id: "$b", count: {$sum: 1}}},
{$limit: 10}
{$limit: 10},
];
// Test explain output where the shards part of the pipeline can be optimized away.
@ -48,7 +48,7 @@ const pipelineNoShardedStages = [
{$sort: {a: -1}},
{$project: {arr: 0}},
{$group: {_id: "$b", count: {$sum: 1}}},
{$limit: 10}
{$limit: 10},
];
// Verify behavior of a nested pipeline.
@ -115,18 +115,14 @@ function checkResults(result, assertExecutionStatsCallback) {
}
for (let pipeline of [pipelineShardedStages, pipelineNoShardedStages, facet]) {
checkResults(coll.explain("executionStats").aggregate(pipeline),
assertStageExecutionStatsPresent);
checkResults(coll.explain("allPlansExecution").aggregate(pipeline),
assertStageExecutionStatsPresent);
checkResults(coll.explain("executionStats").aggregate(pipeline), assertStageExecutionStatsPresent);
checkResults(coll.explain("allPlansExecution").aggregate(pipeline), assertStageExecutionStatsPresent);
}
// Only test $changeStream if we are on a replica set or on a sharded cluster.
if (FixtureHelpers.isReplSet(db) || FixtureHelpers.isSharded(coll)) {
checkResults(coll.explain("executionStats").aggregate(changeStream),
assertStageExecutionStatsPresent);
checkResults(coll.explain("allPlansExecution").aggregate(changeStream),
assertStageExecutionStatsPresent);
checkResults(coll.explain("executionStats").aggregate(changeStream), assertStageExecutionStatsPresent);
checkResults(coll.explain("allPlansExecution").aggregate(changeStream), assertStageExecutionStatsPresent);
}
// Returns the number of documents
@ -142,15 +138,14 @@ function numberOfDocsReturnedByMatchStage(explain) {
}
const matchPipeline = [{$_internalInhibitOptimization: {}}, {$match: {a: {$gte: 500}}}];
assert.eq(numberOfDocsReturnedByMatchStage(coll.explain("executionStats").aggregate(matchPipeline)),
500);
assert.eq(numberOfDocsReturnedByMatchStage(coll.explain("executionStats").aggregate(matchPipeline)), 500);
// Checks $group totalOutputDataSizeBytes execution statistic.
(function testGroupStatTotalDataSizeBytes() {
const pipeline = [{$group: {_id: null, count: {$sum: 1}}}];
const result = coll.explain("executionStats").aggregate(pipeline);
let assertOutputBytesSize = function(stage) {
let assertOutputBytesSize = function (stage) {
if (stage.hasOwnProperty("$group")) {
assert(stage.hasOwnProperty("totalOutputDataSizeBytes"), stage);

View File

@ -21,10 +21,12 @@ assert.commandWorked(sourceColl.insert({_id: 1}));
// Verifies that running the execution explains do not error, perform any writes, or create the
// target collection.
function assertExecutionExplainOk(writingStage, verbosity) {
assert.commandWorked(db.runCommand({
explain: {aggregate: sourceColl.getName(), pipeline: [writingStage], cursor: {}},
verbosity: verbosity
}));
assert.commandWorked(
db.runCommand({
explain: {aggregate: sourceColl.getName(), pipeline: [writingStage], cursor: {}},
verbosity: verbosity,
}),
);
assert.eq(targetColl.find().itcount(), 0);
// Verify that the collection was not created.
const collectionList = db.getCollectionInfos({name: targetColl.getName()});
@ -34,8 +36,7 @@ function assertExecutionExplainOk(writingStage, verbosity) {
// Test that $out can be explained with 'queryPlanner' explain verbosity and does not perform
// any writes.
let explain = sourceColl.explain("queryPlanner").aggregate([{$out: targetColl.getName()}]);
let explainedPipeline =
getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
let explainedPipeline = getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
assert.eq(1, explainedPipeline.length);
assert(explainedPipeline[0].$out);
let outExplain = explainedPipeline[0];
@ -50,13 +51,13 @@ assertExecutionExplainOk({$out: targetColl.getName()}, "executionStats");
assertExecutionExplainOk({$out: targetColl.getName()}, "allPlansExecution");
// Test each $merge mode with each explain verbosity.
withEachMergeMode(function({whenMatchedMode, whenNotMatchedMode}) {
withEachMergeMode(function ({whenMatchedMode, whenNotMatchedMode}) {
const mergeStage = {
$merge: {
into: targetColl.getName(),
whenMatched: whenMatchedMode,
whenNotMatched: whenNotMatchedMode
}
whenNotMatched: whenNotMatchedMode,
},
};
// Verify that execution explains don't error for $merge.
@ -64,8 +65,7 @@ withEachMergeMode(function({whenMatchedMode, whenNotMatchedMode}) {
assertExecutionExplainOk(mergeStage, "allPlansExecution");
const explain = sourceColl.explain("queryPlanner").aggregate([mergeStage]);
let explainedPipeline =
getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
let explainedPipeline = getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
assert.eq(1, explainedPipeline.length);
assert(explainedPipeline[0].$merge);
const mergeExplain = explainedPipeline[0];

View File

@ -14,24 +14,21 @@ assert.eq(result[0]["y"], 0);
// Confirm that we're not using DoubleDoubleSummation for $add expression with a set of double
// values.
let arr = [
1.4831356930199802e-05, -3.121724665346865, 3041897608700.073, 1001318343149.7166,
-1714.6229586696593, 1731390114894580.8, 6.256645803154374e-08, -107144114533844.25,
-0.08839485091750919, -265119153.02185738, -0.02450615965231944, 0.0002684331017079073,
32079040427.68358, -0.04733295911845742, 0.061381859083076085, -25329.59126796951,
-0.0009567520620034965, -1553879364344.9932, -2.1101077525869814e-08, -298421079729.5547,
0.03182394834273594, 22.201944843278916, -33.35667991109125, 11496013.960449915,
-40652595.33210472, 3.8496066090328163, 2.5074042398147304e-08, -0.02208724071782122,
-134211.37290639878, 0.17640433666616578, 4.463787499171126, 9.959669945399718,
129265976.35224283, 1.5865526187526546e-07, -4746011.710555799, -712048598925.0789,
582214206210.4034, 0.025236204812875362, 530078170.91147506, -14.865307666195053,
1.6727994895185032e-05, -113386276.03121366, -6.135827207137054, 10644945799901.145,
-100848907797.1582, 2.2404406961625282e-08, 1.315662618424494e-09, -0.832190208349044,
-9.779323414999364, -546522170658.2997
1.4831356930199802e-5, -3.121724665346865, 3041897608700.073, 1001318343149.7166, -1714.6229586696593,
1731390114894580.8, 6.256645803154374e-8, -107144114533844.25, -0.08839485091750919, -265119153.02185738,
-0.02450615965231944, 0.0002684331017079073, 32079040427.68358, -0.04733295911845742, 0.061381859083076085,
-25329.59126796951, -0.0009567520620034965, -1553879364344.9932, -2.1101077525869814e-8, -298421079729.5547,
0.03182394834273594, 22.201944843278916, -33.35667991109125, 11496013.960449915, -40652595.33210472,
3.8496066090328163, 2.5074042398147304e-8, -0.02208724071782122, -134211.37290639878, 0.17640433666616578,
4.463787499171126, 9.959669945399718, 129265976.35224283, 1.5865526187526546e-7, -4746011.710555799,
-712048598925.0789, 582214206210.4034, 0.025236204812875362, 530078170.91147506, -14.865307666195053,
1.6727994895185032e-5, -113386276.03121366, -6.135827207137054, 10644945799901.145, -100848907797.1582,
2.2404406961625282e-8, 1.315662618424494e-9, -0.832190208349044, -9.779323414999364, -546522170658.2997,
];
let doc = {_id: 0};
let i = 0;
let queryArr = [];
arr.forEach(num => {
arr.forEach((num) => {
i++;
doc[`f_${i}`] = num;
queryArr.push(`$f_${i}`);
@ -43,7 +40,10 @@ assert.commandWorked(coll.insert(doc));
let addResult = coll.aggregate([{$project: {add: {$add: queryArr}}}]).toArray();
let sumResult = coll.aggregate([{$project: {sum: {$sum: queryArr}}}]).toArray();
assert.neq(addResult[0]["add"], sumResult[0]["sum"]);
assert.eq(addResult[0]["add"], arr.reduce((a, b) => a + b));
assert.eq(
addResult[0]["add"],
arr.reduce((a, b) => a + b),
);
assert.eq(true, coll.drop());
// Doubles are rounded to int64 when added to Date
@ -52,17 +52,12 @@ assert.commandWorked(coll.insert({_id: 1, lhs: new Date(1683794065002), rhs: 1.4
assert.commandWorked(coll.insert({_id: 2, lhs: new Date(1683794065002), rhs: 1.5}));
assert.commandWorked(coll.insert({_id: 3, lhs: new Date(1683794065002), rhs: 1.7}));
// Decimals are rounded to int64, when tie rounded to even, when added to Date
assert.commandWorked(
coll.insert({_id: 4, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.4")}));
assert.commandWorked(
coll.insert({_id: 5, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.5")}));
assert.commandWorked(
coll.insert({_id: 6, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.7")}));
assert.commandWorked(
coll.insert({_id: 7, lhs: new Date(1683794065002), rhs: new NumberDecimal("2.5")}));
assert.commandWorked(coll.insert({_id: 4, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.4")}));
assert.commandWorked(coll.insert({_id: 5, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.5")}));
assert.commandWorked(coll.insert({_id: 6, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.7")}));
assert.commandWorked(coll.insert({_id: 7, lhs: new Date(1683794065002), rhs: new NumberDecimal("2.5")}));
let result1 =
coll.aggregate([{$project: {sum: {$add: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}]).toArray();
let result1 = coll.aggregate([{$project: {sum: {$add: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}]).toArray();
assert.eq(result1[0].sum, new Date(1683794065003));
assert.eq(result1[1].sum, new Date(1683794065003));
assert.eq(result1[2].sum, new Date(1683794065004));
@ -74,12 +69,16 @@ assert.eq(result1[7].sum, new Date(1683794065004));
coll.drop();
assert.commandWorked(coll.insert([{
_id: 0,
veryBigPositiveLong: NumberLong("9223372036854775806"),
veryBigPositiveDouble: 9223372036854775806,
veryBigPositiveDecimal: NumberDecimal("9223372036854775806")
}]));
assert.commandWorked(
coll.insert([
{
_id: 0,
veryBigPositiveLong: NumberLong("9223372036854775806"),
veryBigPositiveDouble: 9223372036854775806,
veryBigPositiveDecimal: NumberDecimal("9223372036854775806"),
},
]),
);
let pipeline = [{$project: {res: {$add: [new Date(10), "$veryBigPositiveLong"]}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow");

View File

@ -8,18 +8,10 @@ c.drop();
c.save({x: 17, y: "foo"});
// 16554 was the code used instead of TypeMismatch before 6.1.
assertErrorCode(
c, {$project: {string_fields: {$add: [3, "$y", 4, "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c,
{$project: {number_fields: {$add: ["a", "$x", "b", "$x"]}}},
[16554, ErrorCodes.TypeMismatch]);
assertErrorCode(
c, {$project: {all_strings: {$add: ["c", "$y", "d", "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(
c, {$project: {potpourri_1: {$add: [5, "$y", "e", "$x"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(
c, {$project: {potpourri_2: {$add: [6, "$x", "f", "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(
c, {$project: {potpourri_3: {$add: ["g", "$y", 7, "$x"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(
c, {$project: {potpourri_4: {$add: ["h", "$x", 8, "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {string_fields: {$add: [3, "$y", 4, "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {number_fields: {$add: ["a", "$x", "b", "$x"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {all_strings: {$add: ["c", "$y", "d", "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {potpourri_1: {$add: [5, "$y", "e", "$x"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {potpourri_2: {$add: [6, "$x", "f", "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {potpourri_3: {$add: ["g", "$y", 7, "$x"]}}}, [16554, ErrorCodes.TypeMismatch]);
assertErrorCode(c, {$project: {potpourri_4: {$add: ["h", "$x", 8, "$y"]}}}, [16554, ErrorCodes.TypeMismatch]);

View File

@ -22,14 +22,14 @@ function fail(expression, code) {
assertErrorCode(coll, {$project: {out: expression}}, code);
}
test({$subtract: ['$date', '$date']}, NumberLong(0));
test({$subtract: ['$date', '$num']}, new Date(millis - num));
fail({$subtract: ['$num', '$date']}, [16556, ErrorCodes.TypeMismatch]);
test({$subtract: ["$date", "$date"]}, NumberLong(0));
test({$subtract: ["$date", "$num"]}, new Date(millis - num));
fail({$subtract: ["$num", "$date"]}, [16556, ErrorCodes.TypeMismatch]);
fail({$add: ['$date', '$date']}, 16612);
test({$add: ['$date', '$num']}, new Date(millis + num));
test({$add: ['$num', '$date']}, new Date(millis + num));
fail({$add: ["$date", "$date"]}, 16612);
test({$add: ["$date", "$num"]}, new Date(millis + num));
test({$add: ["$num", "$date"]}, new Date(millis + num));
// addition supports any number of arguments
test({$add: ['$date']}, new Date(millis));
test({$add: ['$num', '$date', '$num']}, new Date(millis + num + num));
test({$add: ["$date"]}, new Date(millis));
test({$add: ["$num", "$date", "$num"]}, new Date(millis + num + num));

View File

@ -9,122 +9,128 @@ function getResultOfExpression(expr) {
return resultArray[0].computed;
}
assert.commandWorked(coll.insert({
_id: 0,
decimalVal: NumberDecimal("819.5359123621083"),
doubleVal: 819.536,
int64Val: NumberLong(820),
int32Val: NumberInt(820),
dateVal: ISODate("2019-01-30T07:30:10.137Z"),
overflowDecimal: NumberDecimal("1e5000"),
overflowDouble: 1e1000,
overflowInt64: NumberLong("9223372036854775807"),
nanDouble: NaN,
nanDecimal: NumberDecimal("NaN"),
}));
assert.commandWorked(
coll.insert({
_id: 0,
decimalVal: NumberDecimal("819.5359123621083"),
doubleVal: 819.536,
int64Val: NumberLong(820),
int32Val: NumberInt(820),
dateVal: ISODate("2019-01-30T07:30:10.137Z"),
overflowDecimal: NumberDecimal("1e5000"),
overflowDouble: 1e1000,
overflowInt64: NumberLong("9223372036854775807"),
nanDouble: NaN,
nanDecimal: NumberDecimal("NaN"),
}),
);
// Adding a Decimal128 value to a date literal.
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$decimalVal", ISODate("2019-01-30T07:30:10.137Z")]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: [ISODate("2019-01-30T07:30:10.137Z"), "$decimalVal"]}));
assert.eq(
ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$decimalVal", ISODate("2019-01-30T07:30:10.137Z")]}),
);
assert.eq(
ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: [ISODate("2019-01-30T07:30:10.137Z"), "$decimalVal"]}),
);
// Adding a Decimal128 literal to a date value.
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$dateVal", NumberDecimal("819.5359123621083")]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: [NumberDecimal("819.5359123621083"), "$dateVal"]}));
assert.eq(
ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$dateVal", NumberDecimal("819.5359123621083")]}),
);
assert.eq(
ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: [NumberDecimal("819.5359123621083"), "$dateVal"]}),
);
// Adding a Decimal128 value to a date value.
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$dateVal", "$decimalVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$decimalVal", "$dateVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$decimalVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$decimalVal", "$dateVal"]}));
// Adding a double value to a date value.
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$dateVal", "$doubleVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$doubleVal", "$dateVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$doubleVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$doubleVal", "$dateVal"]}));
// Adding an int64_t value to date value.
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$dateVal", "$int64Val"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$int64Val", "$dateVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$int64Val"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int64Val", "$dateVal"]}));
// Adding an int32_t value to date value.
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$dateVal", "$int32Val"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
getResultOfExpression({$add: ["$int32Val", "$dateVal"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$int32Val"]}));
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int32Val", "$dateVal"]}));
// Addition with a date and multiple values of differing data types.
assert.eq(ISODate("2019-01-30T07:30:12.597Z"),
getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]}));
assert.eq(ISODate("2019-01-30T07:30:12.597Z"),
getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]}));
assert.eq(ISODate("2019-01-30T07:30:12.596Z"),
getResultOfExpression({$add: ["$decimalVal", "$doubleVal", "$int64Val", "$dateVal"]}));
assert.eq(
ISODate("2019-01-30T07:30:12.597Z"),
getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]}),
);
assert.eq(
ISODate("2019-01-30T07:30:12.597Z"),
getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]}),
);
assert.eq(
ISODate("2019-01-30T07:30:12.596Z"),
getResultOfExpression({$add: ["$decimalVal", "$doubleVal", "$int64Val", "$dateVal"]}),
);
// The result of an addition must remain in the range of int64_t in order to convert back to a Date;
// an overflow into the domain of double-precision floating point numbers triggers a query-fatal
// error.
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble"]}), ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowInt64"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowInt64"]}), ErrorCodes.Overflow);
assert.throwsWithCode(
() => getResultOfExpression({$add: ["$dateVal", "$int64Val", "$overflowDouble"]}),
ErrorCodes.Overflow);
ErrorCodes.Overflow,
);
assert.throwsWithCode(
() => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDouble"]}),
ErrorCodes.Overflow);
ErrorCodes.Overflow,
);
// An overflow into the domain of Decimal128 results in an overflow exception.
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}), ErrorCodes.Overflow);
assert.throwsWithCode(
() => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDecimal"]}),
ErrorCodes.Overflow);
ErrorCodes.Overflow,
);
assert.throwsWithCode(
() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble", "$overflowDecimal"]}),
ErrorCodes.Overflow);
ErrorCodes.Overflow,
);
// Adding a double-typed NaN to a date value.
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}), ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}), ErrorCodes.Overflow);
// An NaN Decimal128 added to date results in an overflow exception.
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}), ErrorCodes.Overflow);
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}), ErrorCodes.Overflow);
// Addition with a date, a double-typed NaN, and a third value.
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}),
ErrorCodes.Overflow);
assert.throwsWithCode(
() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}),
ErrorCodes.Overflow,
);
// Addition with a date, and both types of NaN.
assert.throwsWithCode(
() => getResultOfExpression({$add: ["$dateVal", "$nanDouble", "$nanDecimal"]}),
ErrorCodes.Overflow);
ErrorCodes.Overflow,
);
// Throw error when there're two or more date in $add.
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", 1, "$dateVal"]}), 4974202);
// Test very large long and verify that we're maintaining the precision of long arithmetic.
// 2397083434877565865 and 239708343487756586 both cast to the same double value from longs
assert.eq(ISODate("2019-01-30T07:30:10.958Z"), getResultOfExpression({
$add: [
"$dateVal",
NumberLong("2397083434877565865"),
"$doubleVal",
NumberLong("-2397083434877565864")
]
}));
assert.eq(
ISODate("2019-01-30T07:30:10.958Z"),
getResultOfExpression({
$add: ["$dateVal", NumberLong("2397083434877565865"), "$doubleVal", NumberLong("-2397083434877565864")],
}),
);

View File

@ -3,8 +3,7 @@
*/
const coll = db.all_elements_true;
coll.drop();
assert.commandWorked(
coll.insert({_id: 0, allTrue: [true, true], someTrue: [true, false], noneTrue: [0, false]}));
assert.commandWorked(coll.insert({_id: 0, allTrue: [true, true], someTrue: [true, false], noneTrue: [0, false]}));
function testOp(expression, expected) {
const results = coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray();
@ -20,4 +19,4 @@ testOp({$allElementsTrue: {$literal: [true, 0]}}, false);
testOp({$allElementsTrue: {$literal: [true, 1, false]}}, false);
testOp({$allElementsTrue: "$allTrue"}, true);
testOp({$allElementsTrue: "$someTrue"}, false);
testOp({$allElementsTrue: "$noneTrue"}, false);
testOp({$allElementsTrue: "$noneTrue"}, false);

View File

@ -5,18 +5,20 @@ import "jstests/libs/query/sbe_assert_error_override.js";
const coll = db.any_element_true;
coll.drop();
assert.commandWorked(coll.insert({
_id: 0,
allTrue: [true, true],
someTrue: [true, false],
noneTrue: [0, false],
nonArray: 1,
nullInput: [null],
undefinedInput: [undefined],
undefinedTrue: [undefined, true],
nullTrue: [null, true],
empty: []
}));
assert.commandWorked(
coll.insert({
_id: 0,
allTrue: [true, true],
someTrue: [true, false],
noneTrue: [0, false],
nonArray: 1,
nullInput: [null],
undefinedInput: [undefined],
undefinedTrue: [undefined, true],
nullTrue: [null, true],
empty: [],
}),
);
function testOp(expression, expected) {
const results = coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray();
@ -27,8 +29,7 @@ function testOp(expression, expected) {
}
function assertThrows(expression) {
const error =
assert.throws(() => coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray());
const error = assert.throws(() => coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray());
assert.commandFailedWithCode(error, 5159200);
}

View File

@ -3,25 +3,25 @@
const coll = db.arith_overflow;
function runTest(operator, expectedResults) {
const result =
coll.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
.toArray()
.map(r => r.res);
const result = coll
.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
.toArray()
.map((r) => r.res);
assert.eq(result, expectedResults);
}
// $add
coll.drop();
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e+9), rhs: NumberInt(2e+9)}));
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e+18), rhs: NumberLong(9e+18)}));
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e9), rhs: NumberInt(2e9)}));
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e18), rhs: NumberLong(9e18)}));
runTest("$add", [NumberLong(4e+9), 1.8e+19]);
runTest("$add", [NumberLong(4e9), 1.8e19]);
// $subtract
coll.drop();
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e+9), rhs: NumberInt(-2e+9)}));
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e+18), rhs: NumberLong(-9e+18)}));
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e9), rhs: NumberInt(-2e9)}));
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e18), rhs: NumberLong(-9e18)}));
runTest("$subtract", [NumberLong(4e+9), 1.8e+19]);
runTest("$subtract", [NumberLong(4e9), 1.8e19]);
// $multiply uses same arguments
runTest("$multiply", [NumberLong(-4e+18), -8.1e+37]);
runTest("$multiply", [NumberLong(-4e18), -8.1e37]);

View File

@ -6,212 +6,239 @@
* requires_pipeline_optimization,
* ]
*/
import {
assertArrayEq,
getExplainedPipelineFromAggregation
} from "jstests/aggregation/extras/utils.js";
import {assertArrayEq, getExplainedPipelineFromAggregation} from "jstests/aggregation/extras/utils.js";
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
// TODO(SERVER-18047): Remove database creation once explain behavior is unified between replica
// sets and sharded clusters.
if (FixtureHelpers.isMongos(db) || TestData.testingReplicaSetEndpoint) {
// Create database
assert.commandWorked(db.adminCommand({'enableSharding': db.getName()}));
assert.commandWorked(db.adminCommand({"enableSharding": db.getName()}));
}
(function() {
const collName = jsTest.name();
const coll = db[collName];
coll.drop();
(function () {
const collName = jsTest.name();
const coll = db[collName];
coll.drop();
const $x = "$x"; // fieldpath to "block" constant folding
const $x = "$x"; // fieldpath to "block" constant folding
/**
* Verify constant folding with explain output.
* @param {(number | number[])[]} input Input arithmetic parameters, optionally nested deeply.
* @param {number[] | number} expectedOutput Expected output parameters after constant folding, or a
* scalar if the operation was calculated statically.
* @param {string} message error message
* @returns true if the explain output matches expectedOutput, and an assertion failure otherwise.
*/
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
const buildExpressionFromArguments = (arr, op) => {
if (Array.isArray(arr)) {
return {[op]: arr.map(elt => buildExpressionFromArguments(elt, op))};
} else if (typeof arr === 'string' || arr instanceof String) {
return arr;
} else {
return {$const: arr};
}
};
const expected = buildExpressionFromArguments(expectedOutput, op);
/**
* Verify constant folding with explain output.
* @param {(number | number[])[]} input Input arithmetic parameters, optionally nested deeply.
* @param {number[] | number} expectedOutput Expected output parameters after constant folding, or a
* scalar if the operation was calculated statically.
* @param {string} message error message
* @returns true if the explain output matches expectedOutput, and an assertion failure otherwise.
*/
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
const buildExpressionFromArguments = (arr, op) => {
if (Array.isArray(arr)) {
return {[op]: arr.map((elt) => buildExpressionFromArguments(elt, op))};
} else if (typeof arr === "string" || arr instanceof String) {
return arr;
} else {
return {$const: arr};
}
};
const expected = buildExpressionFromArguments(expectedOutput, op);
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
{$group: {_id: buildExpressionFromArguments(input, op), sum: {$sum: 1}}},
]);
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
{$group: {_id: buildExpressionFromArguments(input, op), sum: {$sum: 1}}},
]);
assert(processedPipeline[0] && processedPipeline[0].$group);
assert.eq(processedPipeline[0].$group._id, expected, message);
assert(processedPipeline[0] && processedPipeline[0].$group);
assert.eq(processedPipeline[0].$group._id, expected, message);
return true;
}
return true;
}
function assertConstantFoldingResults(input, addOutput, multiplyOutput, message) {
assertConstantFoldingResultForOp("$add", input, addOutput, message);
assertConstantFoldingResultForOp("$multiply", input, multiplyOutput, message);
}
function assertConstantFoldingResults(input, addOutput, multiplyOutput, message) {
assertConstantFoldingResultForOp("$add", input, addOutput, message);
assertConstantFoldingResultForOp("$multiply", input, multiplyOutput, message);
}
// Totally fold constants.
assertConstantFoldingResults([1, 2, 3], 6, 6, "All constants should fold.");
assertConstantFoldingResults(
[[1, 2], 3, 4, 5], 15, 120, "Nested operations with all constants should be folded away.");
// Totally fold constants.
assertConstantFoldingResults([1, 2, 3], 6, 6, "All constants should fold.");
assertConstantFoldingResults(
[[1, 2], 3, 4, 5],
15,
120,
"Nested operations with all constants should be folded away.",
);
// Left-associative test cases.
assertConstantFoldingResults([1, 2, $x],
[3, $x],
[2, $x],
"Constants should fold left-to-right before the first non-constant.");
assertConstantFoldingResults(
[$x, 1, 2],
[$x, 1, 2],
[$x, 1, 2],
"Constants should not fold left-to-right after the first non-constant.");
assertConstantFoldingResults(
[1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
// Left-associative test cases.
assertConstantFoldingResults(
[1, 2, $x],
[3, $x],
[2, $x],
"Constants should fold left-to-right before the first non-constant.",
);
assertConstantFoldingResults(
[$x, 1, 2],
[$x, 1, 2],
[$x, 1, 2],
"Constants should not fold left-to-right after the first non-constant.",
);
assertConstantFoldingResults([1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
assertConstantFoldingResults([5, 2, $x, 3, 4],
[7, $x, 3, 4],
[10, $x, 3, 4],
"Constants should fold up until a non-constant.");
assertConstantFoldingResults(
[5, 2, $x, 3, 4],
[7, $x, 3, 4],
[10, $x, 3, 4],
"Constants should fold up until a non-constant.",
);
assertConstantFoldingResults([$x, 1, 2, 3],
[$x, 1, 2, 3],
[$x, 1, 2, 3],
"Non-constant at start of operand list blocks folding constants.");
assertConstantFoldingResults(
[$x, 1, 2, 3],
[$x, 1, 2, 3],
[$x, 1, 2, 3],
"Non-constant at start of operand list blocks folding constants.",
);
assertConstantFoldingResults([[1, 2, $x], 3, 4, $x, 5],
[[3, $x], 3, 4, $x, 5],
[[2, $x], 3, 4, $x, 5],
"Nested operation folds as expected.");
assertConstantFoldingResults(
[[1, 2, $x], 3, 4, $x, 5],
[[3, $x], 3, 4, $x, 5],
[[2, $x], 3, 4, $x, 5],
"Nested operation folds as expected.",
);
assertConstantFoldingResults(
[1, 2, [1, 2, $x], 3, 4, $x, 5],
[3, [3, $x], 3, 4, $x, 5],
[2, [2, $x], 3, 4, $x, 5],
"Nested operation folds along with outer operation following left-associative rules.");
assertConstantFoldingResults(
[1, 2, [1, 2, $x], 3, 4, $x, 5],
[3, [3, $x], 3, 4, $x, 5],
[2, [2, $x], 3, 4, $x, 5],
"Nested operation folds along with outer operation following left-associative rules.",
);
assertConstantFoldingResults(
[1, 2, [1, 2, $x, 5, 6], 3, 4, 5],
[3, [3, $x, 5, 6], 3, 4, 5],
[2, [2, $x, 5, 6], 3, 4, 5],
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.");
assertConstantFoldingResults(
[1, 2, [1, 2, $x, 5, 6], 3, 4, 5],
[3, [3, $x, 5, 6], 3, 4, 5],
[2, [2, $x, 5, 6], 3, 4, 5],
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.",
);
assertConstantFoldingResults(
[1, 2, $x, 4, [1, 2, $x, 5, 6], 3, 4, 5],
[3, $x, 4, [3, $x, 5, 6], 3, 4, 5],
[2, $x, 4, [2, $x, 5, 6], 3, 4, 5],
"Nested operation folds along and even when fieldpath exists before it.");
}());
assertConstantFoldingResults(
[1, 2, $x, 4, [1, 2, $x, 5, 6], 3, 4, 5],
[3, $x, 4, [3, $x, 5, 6], 3, 4, 5],
[2, $x, 4, [2, $x, 5, 6], 3, 4, 5],
"Nested operation folds along and even when fieldpath exists before it.",
);
})();
// Mixing $add and $multiply
(function() {
const collName = jsTest.name();
const coll = db[collName];
coll.drop();
(function () {
const collName = jsTest.name();
const coll = db[collName];
coll.drop();
const assertFoldedResult = (expr, expected, message) => {
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
{$group: {_id: expr, sum: {$sum: 1}}},
]);
const wrapLits = (arr) => {
if (Array.isArray(arr)) {
return arr.map(wrapLits);
} else if (typeof arr === 'object') {
let out = {};
Object.keys(arr).forEach(k => {
out[k] = wrapLits(arr[k]);
});
return out;
} else if (typeof arr === 'string' || arr instanceof String) {
return arr;
} else {
return {$const: arr};
}
const assertFoldedResult = (expr, expected, message) => {
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
{$group: {_id: expr, sum: {$sum: 1}}},
]);
const wrapLits = (arr) => {
if (Array.isArray(arr)) {
return arr.map(wrapLits);
} else if (typeof arr === "object") {
let out = {};
Object.keys(arr).forEach((k) => {
out[k] = wrapLits(arr[k]);
});
return out;
} else if (typeof arr === "string" || arr instanceof String) {
return arr;
} else {
return {$const: arr};
}
};
assert(processedPipeline[0] && processedPipeline[0].$group);
assert.eq(processedPipeline[0].$group._id, wrapLits(expected), message);
};
assert(processedPipeline[0] && processedPipeline[0].$group);
assert.eq(processedPipeline[0].$group._id, wrapLits(expected), message);
};
assertFoldedResult(
{$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
{$add: [3, {$multiply: [12, "$x", 5, 6]}, 6, 7]},
"Multiply inside add will fold as much as it can.",
);
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
{$add: [3, {$multiply: [12, "$x", 5, 6]}, 6, 7]},
"Multiply inside add will fold as much as it can.");
assertFoldedResult(
{$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
{$multiply: [2, {$add: [7, "$x", 5, 6]}, 6, 7]},
"Add inside multiply will fold as much as it can.",
);
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
{$multiply: [2, {$add: [7, "$x", 5, 6]}, 6, 7]},
"Add inside multiply will fold as much as it can.");
assertFoldedResult(
{$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
{$add: [369, "$x", 7, 8]},
"Multiply without fieldpath will fold away and add will continue folding.",
);
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
{$add: [369, "$x", 7, 8]},
"Multiply without fieldpath will fold away and add will continue folding.");
assertFoldedResult(
{$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
{$multiply: [216, "$x", 7, 8]},
"Add without fieldpath will fold away and multiply will continue folding.",
);
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
{$multiply: [216, "$x", 7, 8]},
"Add without fieldpath will fold away and multiply will continue folding.");
assertFoldedResult(
{$add: [1, 2, "$x", {$multiply: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
{$add: [3, "$x", {$multiply: [12, "$x", 5, 6]}, 6, 7, 8]},
"Constant folding nested $multiply proceeds even after outer $add stops folding.",
);
assertFoldedResult(
{$add: [1, 2, "$x", {$multiply: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
{$add: [3, "$x", {$multiply: [12, "$x", 5, 6]}, 6, 7, 8]},
"Constant folding nested $multiply proceeds even after outer $add stops folding.");
assertFoldedResult(
{$multiply: [1, 2, "$x", {$add: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
{$multiply: [2, "$x", {$add: [7, "$x", 5, 6]}, 6, 7, 8]},
"Constant folding nested $add proceeds even after outer multiply stops folding.");
}());
assertFoldedResult(
{$multiply: [1, 2, "$x", {$add: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
{$multiply: [2, "$x", {$add: [7, "$x", 5, 6]}, 6, 7, 8]},
"Constant folding nested $add proceeds even after outer multiply stops folding.",
);
})();
// Regression tests for BFs related to SERVER-63099.
(function() {
const coll = db[jsTest.name()];
coll.drop();
(function () {
const coll = db[jsTest.name()];
coll.drop();
const makePipeline = (id) => [{$group: {_id: id, sum: {$sum: 1}}}];
const makePipeline = (id) => [{$group: {_id: id, sum: {$sum: 1}}}];
// Non-optimized comparisons -- make sure that non-optimized pipelines will give the same result as
// optimized ones.
// This is a regression test for BF-24149.
coll.insert({_id: 0, v: NumberDecimal("917.6875119062092")});
coll.insert({_id: 1, v: NumberDecimal("927.3345924210555")});
// Non-optimized comparisons -- make sure that non-optimized pipelines will give the same result as
// optimized ones.
// This is a regression test for BF-24149.
coll.insert({_id: 0, v: NumberDecimal("917.6875119062092")});
coll.insert({_id: 1, v: NumberDecimal("927.3345924210555")});
const idToString = d => d._id.toJSON().$numberDecimal;
const idToString = (d) => d._id.toJSON().$numberDecimal;
assertArrayEq({
actual: coll.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
.toArray()
.map(idToString),
expected: [
"915242528741.9469524422272990976000",
"905721242210.0453137831269007622941",
]
});
assertArrayEq({
actual: coll
.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
.toArray()
.map(idToString),
expected: ["915242528741.9469524422272990976000", "905721242210.0453137831269007622941"],
});
// BF-24945
coll.drop();
coll.insert({x: 0, y: 4.1});
assert(numberDecimalsEqual(
coll
.aggregate(makePipeline(
{$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$x", "$y"]}))
.toArray()[0]
._id,
NumberDecimal(0)));
assertArrayEq({
actual: coll.aggregate(makePipeline({
$multiply:
[NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$y", "$x"]
}))
.toArray()
.map(idToString),
expected: ["NaN"]
});
}());
// BF-24945
coll.drop();
coll.insert({x: 0, y: 4.1});
assert(
numberDecimalsEqual(
coll
.aggregate(
makePipeline({
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$x", "$y"],
}),
)
.toArray()[0]._id,
NumberDecimal(0),
),
);
assertArrayEq({
actual: coll
.aggregate(
makePipeline({
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$y", "$x"],
}),
)
.toArray()
.map(idToString),
expected: ["NaN"],
});
})();

View File

@ -16,9 +16,7 @@ assert.commandWorked(coll.insert({_id: "sentinel", a: 1}));
function assertCollapsed(expanded, expectedCollapsed) {
assert(coll.drop());
assert.commandWorked(coll.insert({expanded: expanded}));
const result = coll.aggregate([{$project: {collapsed: {$arrayToObject: "$expanded"}}}])
.toArray()[0]
.collapsed;
const result = coll.aggregate([{$project: {collapsed: {$arrayToObject: "$expanded"}}}]).toArray()[0].collapsed;
assert.eq(result, expectedCollapsed);
}
@ -26,10 +24,11 @@ function assertCollapsed(expanded, expectedCollapsed) {
function assertCollapsedWithCollation(expanded, expectedCollapsed) {
assert(coll.drop());
assert.commandWorked(coll.insert({expanded: expanded}));
const result = coll.aggregate([{$project: {collapsed: {$arrayToObject: "$expanded"}}}],
{collation: {locale: "en_US", strength: 2}})
.toArray()[0]
.collapsed;
const result = coll
.aggregate([{$project: {collapsed: {$arrayToObject: "$expanded"}}}], {
collation: {locale: "en_US", strength: 2},
})
.toArray()[0].collapsed;
assert.eq(result, expectedCollapsed);
}
@ -43,19 +42,59 @@ function assertPipelineErrors(expanded, errorCode) {
}
// $arrayToObject correctly converts a key-value pairs to an object.
assertCollapsed([["price", 24], ["item", "apple"]], {"price": 24, "item": "apple"});
assertCollapsed([{"k": "price", "v": 24}, {"k": "item", "v": "apple"}],
{"price": 24, "item": "apple"});
assertCollapsed(
[
["price", 24],
["item", "apple"],
],
{"price": 24, "item": "apple"},
);
assertCollapsed(
[
{"k": "price", "v": 24},
{"k": "item", "v": "apple"},
],
{"price": 24, "item": "apple"},
);
// If duplicate field names are in the array, $arrayToObject should use value from the last one.
assertCollapsed([{"k": "price", "v": 24}, {"k": "price", "v": 100}], {"price": 100});
assertCollapsed([["price", 24], ["price", 100]], {"price": 100});
assertCollapsed(
[
{"k": "price", "v": 24},
{"k": "price", "v": 100},
],
{"price": 100},
);
assertCollapsed(
[
["price", 24],
["price", 100],
],
{"price": 100},
);
// Test with collation
assertCollapsedWithCollation([{"k": "price", "v": 24}, {"k": "PRICE", "v": 100}],
{"price": 24, "PRICE": 100});
assertCollapsedWithCollation([["price", 24], ["PRICE", 100]], {"price": 24, "PRICE": 100});
assertCollapsedWithCollation(
[
{"k": "price", "v": 24},
{"k": "PRICE", "v": 100},
],
{"price": 24, "PRICE": 100},
);
assertCollapsedWithCollation(
[
["price", 24],
["PRICE", 100],
],
{"price": 24, "PRICE": 100},
);
assertCollapsed([["price", 24], ["item", "apple"]], {"price": 24, "item": "apple"});
assertCollapsed(
[
["price", 24],
["item", "apple"],
],
{"price": 24, "item": "apple"},
);
assertCollapsed([], {});
assertCollapsed(null, null);
@ -90,15 +129,15 @@ assertPipelineErrors(NaN, 40386);
assertPipelineErrors([["a\0b", "abra cadabra"]], 4940400);
assertPipelineErrors([{k: "a\0b", v: "blah"}], 4940401);
assertErrorCode(
coll, [{$replaceWith: {$arrayToObject: {$literal: [["a\0b", "abra cadabra"]]}}}], 4940400);
assertErrorCode(
coll, [{$replaceWith: {$arrayToObject: {$literal: [{k: "a\0b", v: "blah"}]}}}], 4940401);
assertErrorCode(coll, [{$replaceWith: {$arrayToObject: {$literal: [["a\0b", "abra cadabra"]]}}}], 4940400);
assertErrorCode(coll, [{$replaceWith: {$arrayToObject: {$literal: [{k: "a\0b", v: "blah"}]}}}], 4940401);
assertErrorCode(
coll,
[{$replaceWith: {$arrayToObject: {$literal: [["a\0b", "abra cadabra"]]}}}, {$out: "output"}],
4940400);
4940400,
);
assertErrorCode(
coll,
[{$replaceWith: {$arrayToObject: {$literal: [{k: "a\0b", v: "blah"}]}}}, {$out: "output"}],
4940401);
4940401,
);

View File

@ -10,41 +10,41 @@ coll.drop();
assert.commandWorked(coll.insert({a: [1, 2, 3, 4, 5]}));
// Normal indexing.
var pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', 2]}}}];
var pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", 2]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: 3}]);
// Indexing with a float.
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', 1.0]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", 1.0]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: 2}]);
// Indexing with a decimal
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', NumberDecimal('2.0')]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", NumberDecimal("2.0")]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: 3}]);
// Negative indexing.
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', -1]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", -1]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: 5}]);
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', -5]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", -5]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: 1}]);
// Out of bounds positive.
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', 5]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", 5]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{}]);
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', Math.pow(2, 31) - 1]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", Math.pow(2, 31) - 1]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{}]);
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', NumberLong(Math.pow(2, 31) - 1)]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", NumberLong(Math.pow(2, 31) - 1)]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{}]);
// Out of bounds negative.
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', -6]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", -6]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{}]);
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', -Math.pow(2, 31)]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", -Math.pow(2, 31)]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{}]);
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', NumberLong(-Math.pow(2, 31))]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", NumberLong(-Math.pow(2, 31))]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{}]);
// Null inputs.
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ['$a', null]}}}];
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: ["$a", null]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: null}]);
pipeline = [{$project: {_id: 0, x: {$arrayElemAt: [null, 4]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{x: null}]);
@ -52,16 +52,16 @@ assert.eq(coll.aggregate(pipeline).toArray(), [{x: null}]);
// Error cases.
// Wrong number of arguments.
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [['one', 'arg']]}}}], 16020);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [["one", "arg"]]}}}], 16020);
// First argument is not an array.
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: ['one', 2]}}}], 28689);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: ["one", 2]}}}], 28689);
// Second argument is not numeric.
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], '2']}}}], 28690);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], "2"]}}}], 28690);
// Second argument is not integral.
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], 1.5]}}}], 28691);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], NumberDecimal('1.5')]}}}], 28691);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], NumberDecimal("1.5")]}}}], 28691);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], Math.pow(2, 32)]}}}], 28691);
assertErrorCode(coll, [{$project: {x: {$arrayElemAt: [[1, 2], -Math.pow(2, 31) - 1]}}}], 28691);

View File

@ -26,4 +26,4 @@ assertArray([null, null]);
assert(coll.drop());
assert.commandWorked(coll.insert({}));
let result = coll.aggregate([{$project: {out: []}}]).toArray()[0].out;
assert.eq(result, []);
assert.eq(result, []);

View File

@ -30,7 +30,7 @@ coll.insertOne({
"array2": [1, 2, 3, "string"],
"array3": [12.4, 5.6, 9.805],
"arrayNested1": [[1, 2, 3], new Map()],
"arrayNested2": [[1, 2, 3], 4, null]
"arrayNested2": [[1, 2, 3], 4, null],
});
// Single non-array input.

View File

@ -6,21 +6,21 @@ import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
const coll = db.expression_binarySize;
coll.drop();
assert.commandWorked(coll.insert([
{_id: 0, x: ""},
{_id: 1, x: "abc"},
{_id: 2, x: "ab\0c"},
{_id: 3, x: "abc\0"},
{_id: 4, x: BinData(0, "")},
{_id: 5, x: BinData(0, "1234")},
{_id: 6, x: null},
{_id: 7},
]));
assert.commandWorked(
coll.insert([
{_id: 0, x: ""},
{_id: 1, x: "abc"},
{_id: 2, x: "ab\0c"},
{_id: 3, x: "abc\0"},
{_id: 4, x: BinData(0, "")},
{_id: 5, x: BinData(0, "1234")},
{_id: 6, x: null},
{_id: 7},
]),
);
const result =
coll.aggregate([{$sort: {_id: 1}}, {$addFields: {s: {$binarySize: "$x"}}}]).toArray();
const result = coll.aggregate([{$sort: {_id: 1}}, {$addFields: {s: {$binarySize: "$x"}}}]).toArray();
assert.eq(result, [
{_id: 0, x: "", s: 0},
{_id: 1, x: "abc", s: 3},
// Javascript strings and BSON strings can contain '\0', so both of these have length 4.
@ -38,4 +38,4 @@ assert.eq(result, [
// $binarySize only accepts strings and BinData.
assert.commandWorked(coll.insert({x: 42}));
assertErrorCode(coll, {$project: {s: {$binarySize: "$x"}}}, 51276);
assertErrorCode(coll, {$project: {s: {$binarySize: "$x"}}}, 51276);

View File

@ -9,19 +9,22 @@ const collName = jsTestName();
const coll = db[collName];
coll.drop();
assert.commandWorked(coll.insert([
{_id: 0, a: NumberInt(0), b: NumberInt(127), c: [NumberInt(0), NumberInt(127)]},
{_id: 1, a: NumberInt(1), b: NumberInt(2), c: [NumberInt(1), NumberInt(2)]},
{_id: 2, a: NumberInt(2), b: NumberInt(3), c: [NumberInt(2), NumberInt(3)]},
{_id: 3, a: NumberInt(3), b: NumberInt(5), c: [NumberInt(3), NumberInt(5)]},
]));
assert.commandWorked(
coll.insert([
{_id: 0, a: NumberInt(0), b: NumberInt(127), c: [NumberInt(0), NumberInt(127)]},
{_id: 1, a: NumberInt(1), b: NumberInt(2), c: [NumberInt(1), NumberInt(2)]},
{_id: 2, a: NumberInt(2), b: NumberInt(3), c: [NumberInt(2), NumberInt(3)]},
{_id: 3, a: NumberInt(3), b: NumberInt(5), c: [NumberInt(3), NumberInt(5)]},
]),
);
function runAndAssert(expression, expectedResult) {
assertArrayEq({
actual: coll.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
.toArray()
.map(doc => doc.r),
expected: expectedResult
actual: coll
.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
.toArray()
.map((doc) => doc.r),
expected: expectedResult,
});
}
@ -30,60 +33,74 @@ runAndAssert("$bitOr", [127, 3, 3, 7]);
runAndAssert("$bitXor", [127, 3, 1, 6]);
for (const operator of ["$bitAnd", "$bitOr", "$bitXor"]) {
for (const operand
of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
assert.commandFailedWithCode(coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
$project: {
r: {[operator]: ["$a", operand]},
}
}]
}),
ErrorCodes.TypeMismatch);
for (const operand of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [
{
$project: {
r: {[operator]: ["$a", operand]},
},
},
],
}),
ErrorCodes.TypeMismatch,
);
}
for (const argument of ["$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
assert.commandFailedWithCode(coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
$project: {
r: {[operator]: argument},
}
}]
}),
ErrorCodes.TypeMismatch);
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [
{
$project: {
r: {[operator]: argument},
},
},
],
}),
ErrorCodes.TypeMismatch,
);
}
}
assertArrayEq({
actual: coll.aggregate([
{$project: {r: {$bitNot: "$a"}}},
])
.toArray()
.map(doc => doc.r),
expected: [-1, -2, -3, -4]
actual: coll
.aggregate([{$project: {r: {$bitNot: "$a"}}}])
.toArray()
.map((doc) => doc.r),
expected: [-1, -2, -3, -4],
});
assert.commandFailedWithCode(coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
$project: {
r: {$bitNot: 12.5},
}
}]
}),
ErrorCodes.TypeMismatch);
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [
{
$project: {
r: {$bitNot: 12.5},
},
},
],
}),
ErrorCodes.TypeMismatch,
);
assert.commandFailedWithCode(coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
$project: {
r: {$bitNot: ["$a", "$b"]},
}
}]
}),
16020); // Error for incorrect number of arguments.
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [
{
$project: {
r: {$bitNot: ["$a", "$b"]},
},
},
],
}),
16020,
); // Error for incorrect number of arguments.

View File

@ -3,8 +3,7 @@ coll.drop();
assert.commandWorked(coll.insert({_id: 1}));
function checkBsonSize() {
assert.eq(Object.bsonsize(coll.findOne()),
coll.aggregate([{$project: {s: {$bsonSize: "$$CURRENT"}}}]).next().s);
assert.eq(Object.bsonsize(coll.findOne()), coll.aggregate([{$project: {s: {$bsonSize: "$$CURRENT"}}}]).next().s);
}
checkBsonSize();
@ -15,13 +14,13 @@ checkBsonSize();
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: {subdoc: 12345}}}));
checkBsonSize();
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(7)}}));
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(7)}}));
checkBsonSize();
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(500)}}));
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(500)}}));
checkBsonSize();
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(16 * 1e6)}}));
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(16 * 1e6)}}));
checkBsonSize();
// embedded arrays
@ -34,9 +33,11 @@ checkBsonSize();
// bsonSize's argument must be a document
function checkExpectsDocument(badInput) {
assert.throws(() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
[],
"$bsonSize requires a document input");
assert.throws(
() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
[],
"$bsonSize requires a document input",
);
}
checkExpectsDocument(123);
checkExpectsDocument("abc");

View File

@ -6,15 +6,15 @@ assert.commandWorked(coll.insertOne({string: "foo"}));
// check that without $literal we end up comparing a field with itself and the result is true
var result = db.runCommand({
aggregate: collName,
pipeline: [{$project: {stringis$string: {$eq: ["$string", '$string']}}}],
cursor: {}
pipeline: [{$project: {stringis$string: {$eq: ["$string", "$string"]}}}],
cursor: {},
});
assert.eq(result.cursor.firstBatch[0].stringis$string, true);
// check that with $literal we end up comparing a field with '$string' and the result is true
result = db.runCommand({
aggregate: collName,
pipeline: [{$project: {stringis$string: {$eq: ["$string", {$literal: '$string'}]}}}],
cursor: {}
pipeline: [{$project: {stringis$string: {$eq: ["$string", {$literal: "$string"}]}}}],
cursor: {},
});
assert.eq(result.cursor.firstBatch[0].stringis$string, false);

View File

@ -12,11 +12,11 @@ coll.drop();
var results;
const caseInsensitive = {
locale: "en_US",
strength: 2
strength: 2,
};
const numericOrdering = {
locale: "en_US",
numericOrdering: true
numericOrdering: true,
};
// Test that $cmp respects the collection-default collation.
@ -52,8 +52,7 @@ testExpressionWithCollation(coll, {$gte: ["b", "B"]}, true, caseInsensitive);
testExpressionWithCollation(coll, {$in: ["A", [1, 2, "a", 3, 4]]}, true, caseInsensitive);
// Test that $indexOfArray respects the collation.
testExpressionWithCollation(
coll, {$indexOfArray: [[1, 2, "a", "b", "c", "B"], "B"]}, 3, caseInsensitive);
testExpressionWithCollation(coll, {$indexOfArray: [[1, 2, "a", "b", "c", "B"], "B"]}, 3, caseInsensitive);
// Test that $indexOfBytes doesn't respect the collation.
testExpressionWithCollation(coll, {$indexOfBytes: ["12abcB", "B"]}, 5, caseInsensitive);
@ -65,20 +64,57 @@ testExpressionWithCollation(coll, {$indexOfCP: ["12abcB", "B"]}, 5, caseInsensit
testExpressionWithCollation(coll, {$strcasecmp: ["100", "2"]}, -1, numericOrdering);
// Test that $setEquals respects the collation.
testExpressionWithCollation(coll, {$setEquals: [["a", "B"], ["b", "A"]]}, true, caseInsensitive);
testExpressionWithCollation(
coll,
{
$setEquals: [
["a", "B"],
["b", "A"],
],
},
true,
caseInsensitive,
);
// Test that $setIntersection respects the collation.
results =
coll.aggregate([{$project: {out: {$setIntersection: [["a", "B", "c"], ["d", "b", "A"]]}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate(
[
{
$project: {
out: {
$setIntersection: [
["a", "B", "c"],
["d", "b", "A"],
],
},
},
},
],
{collation: caseInsensitive},
)
.toArray();
assert.eq(1, results.length);
assert.eq(2, results[0].out.length);
// Test that $setUnion respects the collation.
results = coll.aggregate([{$project: {out: {$setUnion: [["a", "B", "c"], ["d", "b", "A"]]}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate(
[
{
$project: {
out: {
$setUnion: [
["a", "B", "c"],
["d", "b", "A"],
],
},
},
},
],
{collation: caseInsensitive},
)
.toArray();
assert.eq(1, results.length);
assert.eq(4, results[0].out.length);
@ -87,20 +123,39 @@ assert.eq(4, results[0].out.length);
assert(coll.drop);
coll.drop();
assert.commandWorked(coll.insert({_id: 1, upper: "A", lower: "a"}));
var results1 = coll.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}],
{collation: caseInsensitive})
.toArray();
var results2 = coll.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}],
{collation: caseInsensitive})
.toArray();
var results1 = coll
.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}], {collation: caseInsensitive})
.toArray();
var results2 = coll
.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}], {collation: caseInsensitive})
.toArray();
assert.eq(results1, results2);
// Test that $setDifference respects the collation.
testExpressionWithCollation(coll, {$setDifference: [["a", "B"], ["b", "A"]]}, [], caseInsensitive);
testExpressionWithCollation(
coll,
{
$setDifference: [
["a", "B"],
["b", "A"],
],
},
[],
caseInsensitive,
);
// Test that $setIsSubset respects the collation.
testExpressionWithCollation(
coll, {$setIsSubset: [["a", "B"], ["b", "A", "c"]]}, true, caseInsensitive);
coll,
{
$setIsSubset: [
["a", "B"],
["b", "A", "c"],
],
},
true,
caseInsensitive,
);
// Test that $split doesn't respect the collation.
testExpressionWithCollation(coll, {$split: ["abc", "B"]}, ["abc"], caseInsensitive);
@ -108,130 +163,138 @@ testExpressionWithCollation(coll, {$split: ["abc", "B"]}, ["abc"], caseInsensiti
// Test that an $and which can be optimized out respects the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 1, str: "A"}));
results = coll.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["b", "B"]}]}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["b", "B"]}]}}}], {collation: caseInsensitive})
.toArray();
assert.eq(1, results.length);
assert.eq(true, results[0].out);
// Test that an $and which cannot be optimized out respects the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 1, str: "A", str2: "B"}));
results = coll.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["$str2", "b"]}]}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["$str2", "b"]}]}}}], {collation: caseInsensitive})
.toArray();
assert.eq(1, results.length);
assert.eq(true, results[0].out);
// Test that an $or which can be optimized out respects the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 1, str: "A"}));
results = coll.aggregate([{$project: {out: {$or: [{$eq: ["$str", "a"]}, {$eq: ["b", "c"]}]}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate([{$project: {out: {$or: [{$eq: ["$str", "a"]}, {$eq: ["b", "c"]}]}}}], {collation: caseInsensitive})
.toArray();
assert.eq(1, results.length);
assert.eq(true, results[0].out);
// Test that an $or which cannot be optimized out respects the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 1, str: "A", str2: "B"}));
results = coll.aggregate([{$project: {out: {$or: [{$eq: ["$str", "c"]}, {$eq: ["$str2", "b"]}]}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate([{$project: {out: {$or: [{$eq: ["$str", "c"]}, {$eq: ["$str2", "b"]}]}}}], {collation: caseInsensitive})
.toArray();
assert.eq(1, results.length);
assert.eq(true, results[0].out);
// Test that $filter's subexpressions respect the collation.
testExpressionWithCollation(coll,
{
$filter: {
input: {
$cond: {
if: {$eq: ["FOO", "foo"]},
then: ["a", "b", "A", "c", "C", "d"],
else: null
}
},
as: "str",
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]}
}
},
["a", "A", "c", "C"],
caseInsensitive);
testExpressionWithCollation(
coll,
{
$filter: {
input: {
$cond: {
if: {$eq: ["FOO", "foo"]},
then: ["a", "b", "A", "c", "C", "d"],
else: null,
},
},
as: "str",
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]},
},
},
["a", "A", "c", "C"],
caseInsensitive,
);
// Test that $let's subexpressions respect the collation.
testExpressionWithCollation(coll,
{
$let: {
vars: {str: {$cond: [{$eq: ["A", "a"]}, "b", "c"]}},
in : {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]}
}
},
"d",
caseInsensitive);
testExpressionWithCollation(
coll,
{
$let: {
vars: {str: {$cond: [{$eq: ["A", "a"]}, "b", "c"]}},
in: {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]},
},
},
"d",
caseInsensitive,
);
// Test that $map's subexpressions respect the collation.
testExpressionWithCollation(
coll,
{
$map: {
input: {$cond: [{$eq: ["A", "a"]}, ["aa", "a", "AA", "b"], null]},
as: "val",
in : {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]}
}
coll,
{
$map: {
input: {$cond: [{$eq: ["A", "a"]}, ["aa", "a", "AA", "b"], null]},
as: "val",
in: {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]},
},
[true, false, true, false],
caseInsensitive);
},
[true, false, true, false],
caseInsensitive,
);
// Test that $group stage's _id expressions respect the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 1}));
results = coll.aggregate([{$group: {_id: {a: {$eq: ["a", "A"]}, b: {$eq: ["b", "B"]}}}}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate([{$group: {_id: {a: {$eq: ["a", "A"]}, b: {$eq: ["b", "B"]}}}}], {collation: caseInsensitive})
.toArray();
assert.eq(1, results.length);
assert.eq(true, results[0]._id.a);
assert.eq(true, results[0]._id.b);
// Test that $reduce's subexpressions respect the collation.
testExpressionWithCollation(
coll,
{
$reduce: {
input: {$cond: [{$eq: ["a", "A"]}, [1, 2, 3], null]},
initialValue: {$cond: [{$eq: ["a", "A"]}, {sum: 1}, {sum: 0}]},
in : {
$cond: [{$eq: ["a", "A"]}, {sum: {$add: ["$$value.sum", "$$this"]}}, {sum: 0}]
}
}
coll,
{
$reduce: {
input: {$cond: [{$eq: ["a", "A"]}, [1, 2, 3], null]},
initialValue: {$cond: [{$eq: ["a", "A"]}, {sum: 1}, {sum: 0}]},
in: {
$cond: [{$eq: ["a", "A"]}, {sum: {$add: ["$$value.sum", "$$this"]}}, {sum: 0}],
},
},
{sum: 7},
caseInsensitive);
},
{sum: 7},
caseInsensitive,
);
// Test that $switch's subexpressions respect the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 1, a: "A"}));
assert.commandWorked(coll.insert({_id: 2, b: "B"}));
assert.commandWorked(coll.insert({_id: 3, c: "C"}));
results = coll.aggregate(
[
{$sort: {_id: 1}},
{
$project: {
out: {
$switch: {
branches: [
{case: {$eq: ["$a", "a"]}, then: "foo"},
{case: {$eq: ["$b", "b"]}, then: "bar"}
],
default: "baz"
}
}
}
}
],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate(
[
{$sort: {_id: 1}},
{
$project: {
out: {
$switch: {
branches: [
{case: {$eq: ["$a", "a"]}, then: "foo"},
{case: {$eq: ["$b", "b"]}, then: "bar"},
],
default: "baz",
},
},
},
},
],
{collation: caseInsensitive},
)
.toArray();
assert.eq(3, results.length);
assert.eq("foo", results[0].out);
assert.eq("bar", results[1].out);
@ -240,21 +303,33 @@ assert.eq("baz", results[2].out);
// Test that a $zip's subexpressions respect the collation.
coll.drop();
assert.commandWorked(coll.insert({_id: 0, evens: [0, 2, 4], odds: [1, 3]}));
results = coll.aggregate([{
$project: {
out: {
$zip: {
inputs: [
{$cond: [{$eq: ["A", "a"]}, "$evens", "$odds"]},
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]}
],
defaults: [0, {$cond: [{$eq: ["C", "c"]}, 5, 7]}],
useLongestLength: true
}
}
}
}],
{collation: caseInsensitive})
.toArray();
results = coll
.aggregate(
[
{
$project: {
out: {
$zip: {
inputs: [
{$cond: [{$eq: ["A", "a"]}, "$evens", "$odds"]},
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]},
],
defaults: [0, {$cond: [{$eq: ["C", "c"]}, 5, 7]}],
useLongestLength: true,
},
},
},
},
],
{collation: caseInsensitive},
)
.toArray();
assert.eq(1, results.length);
assert.eq([[0, 1], [2, 3], [4, 5]], results[0].out);
assert.eq(
[
[0, 1],
[2, 3],
[4, 5],
],
results[0].out,
);

View File

@ -3,7 +3,10 @@
const coll = db[jsTest.name()];
coll.drop();
const numbers = [1.0, NumberInt("1"), NumberLong("1"), NumberDecimal("1.0")];
const specials = [{val: NaN, path: "$nan"}, {val: Infinity, path: "$inf"}];
const specials = [
{val: NaN, path: "$nan"},
{val: Infinity, path: "$inf"},
];
assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
@ -11,15 +14,9 @@ assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
(function testCommutativityWithConstArguments() {
specials.forEach((special) => {
numbers.forEach((num) => {
const expected = [
{a: (num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val)}
];
assert.eq(expected,
coll.aggregate([{$project: {a: {[op]: [special.val, num]}, _id: 0}}])
.toArray());
assert.eq(expected,
coll.aggregate([{$project: {a: {[op]: [num, special.val]}, _id: 0}}])
.toArray());
const expected = [{a: num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val}];
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [special.val, num]}, _id: 0}}]).toArray());
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [num, special.val]}, _id: 0}}]).toArray());
});
});
})();
@ -27,16 +24,10 @@ assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
(function testCommutativityWithNonConstArgument() {
specials.forEach((special) => {
numbers.forEach((num) => {
const expected = [
{a: (num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val)}
];
assert.eq(expected,
coll.aggregate([{$project: {a: {[op]: [special.path, num]}, _id: 0}}])
.toArray());
assert.eq(expected,
coll.aggregate([{$project: {a: {[op]: [num, special.path]}, _id: 0}}])
.toArray());
const expected = [{a: num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val}];
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [special.path, num]}, _id: 0}}]).toArray());
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [num, special.path]}, _id: 0}}]).toArray());
});
});
})();
});
});

View File

@ -8,4 +8,4 @@ coll.drop();
assert.commandWorked(coll.insert({}));
assert.eq(coll.findOne({}, {_id: false, "a": {$concat: [{$toLower: "$b"}]}}), {a: ""});
assert.eq(coll.findOne({}, {_id: false, "a": {$concat: []}}), {a: ""});
assert.eq(coll.findOne({}, {_id: false, "a": {$concat: []}}), {a: ""});

View File

@ -19,27 +19,29 @@ import {checkSbeFullyEnabled} from "jstests/libs/query/sbe_util.js";
const coll = db.projection_expr_concat_arrays;
coll.drop();
assert.commandWorked(coll.insertOne({
int_arr: [1, 2, 3, 4],
dbl_arr: [10.0, 20.1, 20.4, 50.5],
nested_arr: [["an", "array"], "arr", [[], [[], "a", "b"]]],
str_arr: ["a", "b", "c"],
obj_arr: [{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}],
null_arr: [null, null, null],
one_null_arr: [null],
one_str_arr: ["one"],
empty_arr: [],
null_val: null,
str_val: "a string",
dbl_val: 2.0,
int_val: 1,
obj_val: {a: 1, b: "two"}
}));
assert.commandWorked(
coll.insertOne({
int_arr: [1, 2, 3, 4],
dbl_arr: [10.0, 20.1, 20.4, 50.5],
nested_arr: [["an", "array"], "arr", [[], [[], "a", "b"]]],
str_arr: ["a", "b", "c"],
obj_arr: [{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}],
null_arr: [null, null, null],
one_null_arr: [null],
one_str_arr: ["one"],
empty_arr: [],
null_val: null,
str_val: "a string",
dbl_val: 2.0,
int_val: 1,
obj_val: {a: 1, b: "two"},
}),
);
function runAndAssert(operands, expectedResult) {
assertArrayEq({
actual: coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).map(doc => doc.f),
expected: expectedResult
actual: coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).map((doc) => doc.f),
expected: expectedResult,
});
}
function runAndAssertNull(operands) {
@ -47,8 +49,7 @@ function runAndAssertNull(operands) {
}
function runAndAssertThrows(operands) {
const error =
assert.throws(() => coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).toArray());
const error = assert.throws(() => coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).toArray());
assert.commandFailedWithCode(error, 28664);
}
@ -65,37 +66,54 @@ runAndAssert([[0], "$int_arr", [5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7]]);
runAndAssert(["$int_arr", "$str_arr"], [[1, 2, 3, 4, "a", "b", "c"]]);
runAndAssert(
["$obj_arr", "$obj_arr", "$null_arr"],
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, null, null, null]]);
runAndAssert(["$int_arr", "$str_arr", "$nested_arr"],
[[1, 2, 3, 4, "a", "b", "c", ["an", "array"], "arr", [[], [[], "a", "b"]]]]);
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, null, null, null]],
);
runAndAssert(
["$int_arr", "$str_arr", "$nested_arr"],
[[1, 2, 3, 4, "a", "b", "c", ["an", "array"], "arr", [[], [[], "a", "b"]]]],
);
runAndAssert(["$int_arr", "$obj_arr"], [[1, 2, 3, 4, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}]]);
runAndAssert(["$obj_arr"], [[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}]]);
runAndAssert(["$obj_arr", [{o: 123, b: 1}, {y: "o", d: "a"}]],
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {o: 123, b: 1}, {y: "o", d: "a"}]]);
runAndAssert(
[
"$obj_arr",
[
{o: 123, b: 1},
{y: "o", d: "a"},
],
],
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {o: 123, b: 1}, {y: "o", d: "a"}]],
);
// Confirm that arrays containing null can be concatenated.
runAndAssert(["$null_arr"], [[null, null, null]]);
runAndAssert([[null], "$null_arr"], [[null, null, null, null]]);
runAndAssert("$one_null_arr", [[null]]);
runAndAssert(["$null_arr", "$one_null_arr", "$int_arr", "$null_arr"],
[[null, null, null, null, 1, 2, 3, 4, null, null, null]]);
runAndAssert(
["$null_arr", "$one_null_arr", "$int_arr", "$null_arr"],
[[null, null, null, null, 1, 2, 3, 4, null, null, null]],
);
// Test operands that form more complex expressions.
runAndAssert([{$concatArrays: "$int_arr"}], [[1, 2, 3, 4]]);
runAndAssert([{$concatArrays: "$int_arr"}, {$concatArrays: {$concatArrays: "$str_arr"}}],
[[1, 2, 3, 4, "a", "b", "c"]]);
runAndAssert(["$str_arr", {$filter: {input: "$int_arr",
as: "num",
cond: { $and: [
{ $gte: [ "$$num", 2 ] },
{ $lte: [ "$$num", 3 ] }
] }}}, "$int_arr"],
[["a", "b", "c", 2, 3, 1, 2, 3, 4]]);
runAndAssert(
[{$concatArrays: "$int_arr"}, {$concatArrays: {$concatArrays: "$str_arr"}}],
[[1, 2, 3, 4, "a", "b", "c"]],
);
runAndAssert(
[
"$str_arr",
{$filter: {input: "$int_arr", as: "num", cond: {$and: [{$gte: ["$$num", 2]}, {$lte: ["$$num", 3]}]}}},
"$int_arr",
],
[["a", "b", "c", 2, 3, 1, 2, 3, 4]],
);
// Confirm that empty arrays can be concatenated with variables.
runAndAssert(
["$str_arr", {$filter: {input: [], cond: {$isArray: [{$concatArrays: [[], "$$this"]}]}}}],
[["a", "b", "c"]]);
[["a", "b", "c"]],
);
// Concatenation with no arguments results in the empty array.
runAndAssert([], [[]]);
@ -113,11 +131,7 @@ runAndAssertNull(["$not_a_field"]);
runAndAssertNull(["$null_val"]);
runAndAssertNull(["$not_a_field", "$null_val"]);
runAndAssertNull(["$null_val", "$not_a_field"]);
runAndAssertNull([
{$concatArrays: "$int_arr"},
null,
{$concatArrays: {$concatArrays: ["$obj_arr", "$str_arr"]}}
]);
runAndAssertNull([{$concatArrays: "$int_arr"}, null, {$concatArrays: {$concatArrays: ["$obj_arr", "$str_arr"]}}]);
// Confirm edge case where if null precedes non-array input, null is returned.
runAndAssertNull(["$int_arr", "$null_val", "$int_val"]);
@ -153,30 +167,38 @@ runAndAssertThrows(["$int_arr", 32]);
assert(coll.drop());
// Test case where find returns multiple documents.
assert.commandWorked(coll.insertMany([
{arr1: [42, 35.0, 197865432], arr2: ["albatross", "abbacus", "alien"]},
{arr1: [1], arr2: ["albatross", "abbacus", "alien"]},
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: []},
{arr1: [], arr2: ["foo", "bar"]},
{arr1: [], arr2: []},
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: null},
{some_field: "foo"},
]));
runAndAssert(["$arr1", "$arr2"], [
[42, 35.0, 197865432, "albatross", "abbacus", "alien"],
[1, "albatross", "abbacus", "alien"],
[1, 2, 3, 4, 5, 6, 11, 12, 23],
["foo", "bar"],
[],
null,
null
]);
runAndAssert(["$arr1", [1, 2, 3], "$arr2"], [
[42, 35.0, 197865432, 1, 2, 3, "albatross", "abbacus", "alien"],
[1, 1, 2, 3, "albatross", "abbacus", "alien"],
[1, 2, 3, 4, 5, 6, 11, 12, 23, 1, 2, 3],
["foo", 1, 2, 3, "bar"],
[1, 2, 3],
null,
null
]);
assert.commandWorked(
coll.insertMany([
{arr1: [42, 35.0, 197865432], arr2: ["albatross", "abbacus", "alien"]},
{arr1: [1], arr2: ["albatross", "abbacus", "alien"]},
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: []},
{arr1: [], arr2: ["foo", "bar"]},
{arr1: [], arr2: []},
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: null},
{some_field: "foo"},
]),
);
runAndAssert(
["$arr1", "$arr2"],
[
[42, 35.0, 197865432, "albatross", "abbacus", "alien"],
[1, "albatross", "abbacus", "alien"],
[1, 2, 3, 4, 5, 6, 11, 12, 23],
["foo", "bar"],
[],
null,
null,
],
);
runAndAssert(
["$arr1", [1, 2, 3], "$arr2"],
[
[42, 35.0, 197865432, 1, 2, 3, "albatross", "abbacus", "alien"],
[1, 1, 2, 3, "albatross", "abbacus", "alien"],
[1, 2, 3, 4, 5, 6, 11, 12, 23, 1, 2, 3],
["foo", 1, 2, 3, "bar"],
[1, 2, 3],
null,
null,
],
);

View File

@ -7,26 +7,26 @@ import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
var coll = db.agg_concat_arrays_expr;
coll.drop();
assert.commandWorked(coll.insert({a: [1, 2], b: ['three'], c: [], d: [[3], 4], e: null, str: 'x'}));
assert.commandWorked(coll.insert({a: [1, 2], b: ["three"], c: [], d: [[3], 4], e: null, str: "x"}));
// Basic concatenation.
var pipeline = [{$project: {_id: 0, all: {$concatArrays: ['$a', '$b', '$c']}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{all: [1, 2, 'three']}]);
var pipeline = [{$project: {_id: 0, all: {$concatArrays: ["$a", "$b", "$c"]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{all: [1, 2, "three"]}]);
// Concatenation with nested arrays.
pipeline = [{$project: {_id: 0, all: {$concatArrays: ['$a', '$d']}}}];
pipeline = [{$project: {_id: 0, all: {$concatArrays: ["$a", "$d"]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{all: [1, 2, [3], 4]}]);
// Concatenation with 1 argument.
pipeline = [{$project: {_id: 0, all: {$concatArrays: ['$a']}}}];
pipeline = [{$project: {_id: 0, all: {$concatArrays: ["$a"]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{all: [1, 2]}]);
// Any nullish inputs will result in null.
pipeline = [{$project: {_id: 0, all: {$concatArrays: ['$a', '$e']}}}];
pipeline = [{$project: {_id: 0, all: {$concatArrays: ["$a", "$e"]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{all: null}]);
pipeline = [{$project: {_id: 0, all: {$concatArrays: ['$a', '$f']}}}];
pipeline = [{$project: {_id: 0, all: {$concatArrays: ["$a", "$f"]}}}];
assert.eq(coll.aggregate(pipeline).toArray(), [{all: null}]);
// Error on any non-array, non-null inputs.
pipeline = [{$project: {_id: 0, all: {$concatArrays: ['$a', '$str']}}}];
pipeline = [{$project: {_id: 0, all: {$concatArrays: ["$a", "$str"]}}}];
assertErrorCode(coll, pipeline, 28664);

View File

@ -8,14 +8,10 @@ let c = db.s6570;
c.drop();
c.save({v: "$", w: ".", x: "foo", y: "bar", z: "z\0z"});
assert.eq(c.aggregate({$project: {str: {$concat: ["X", "$x", "Y", "$y"]}}}).toArray()[0].str,
"XfooYbar");
assert.eq(c.aggregate({$project: {str: {$concat: ["$v", "X", "$w", "Y"]}}}).toArray()[0].str,
"$X.Y");
assert.eq(c.aggregate({$project: {str: {$concat: ["$w", "X", "$v", "Y"]}}}).toArray()[0].str,
".X$Y");
assert.eq(c.aggregate({$project: {str: {$concat: ["X", "$z", "a\0a", "Y"]}}}).toArray()[0].str,
"Xz\0za\0aY");
assert.eq(c.aggregate({$project: {str: {$concat: ["X", "$x", "Y", "$y"]}}}).toArray()[0].str, "XfooYbar");
assert.eq(c.aggregate({$project: {str: {$concat: ["$v", "X", "$w", "Y"]}}}).toArray()[0].str, "$X.Y");
assert.eq(c.aggregate({$project: {str: {$concat: ["$w", "X", "$v", "Y"]}}}).toArray()[0].str, ".X$Y");
assert.eq(c.aggregate({$project: {str: {$concat: ["X", "$z", "a\0a", "Y"]}}}).toArray()[0].str, "Xz\0za\0aY");
// Nullish (both with and without other strings)
assert.isnull(c.aggregate({$project: {str: {$concat: ["$missing"]}}}).toArray()[0].str);
@ -31,7 +27,7 @@ assertErrorCode(c, {$project: {str: {$concat: [1]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [NumberInt(1)]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [NumberLong(1)]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [true]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [function() {}]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [function () {}]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [{}]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [[]]}}}, 16702);
assertErrorCode(c, {$project: {str: {$concat: [new Timestamp(0, 0)]}}}, 16702);

View File

@ -2,8 +2,8 @@
let c = db.c;
c.drop();
c.save({x: '3'});
c.save({x: "3"});
let project = {$project: {a: {$concat: ['1', {$concat: ['foo', '$x', 'bar']}, '2']}}};
let project = {$project: {a: {$concat: ["1", {$concat: ["foo", "$x", "bar"]}, "2"]}}};
assert.eq('1foo3bar2', c.aggregate(project).toArray()[0].a);
assert.eq("1foo3bar2", c.aggregate(project).toArray()[0].a);

View File

@ -46,39 +46,42 @@ assertResult(1, [{$and: []}, {$add: [1]}, {$add: [1, 1]}]);
assertResult(2, [{$or: []}, {$add: [1]}, {$add: [1, 1]}]);
assert(coll.drop());
assert.commandWorked(coll.insert({t: true, f: false, x: 'foo', y: 'bar'}));
assert.commandWorked(coll.insert({t: true, f: false, x: "foo", y: "bar"}));
// Field path expressions.
assertResult('foo', ['$t', '$x', '$y']);
assertResult('bar', ['$f', '$x', '$y']);
assertResult("foo", ["$t", "$x", "$y"]);
assertResult("bar", ["$f", "$x", "$y"]);
assert(coll.drop());
assert.commandWorked(coll.insert({}));
// Coerce to bool.
assertResult('a', [1, 'a', 'b']);
assertResult('a', ['', 'a', 'b']);
assertResult('b', [0, 'a', 'b']);
assertResult("a", [1, "a", "b"]);
assertResult("a", ["", "a", "b"]);
assertResult("b", [0, "a", "b"]);
// Nested.
assert(coll.drop());
assert.commandWorked(coll.insert({noonSense: 'am', mealCombined: 'no'}));
assert.commandWorked(coll.insert({noonSense: 'am', mealCombined: 'yes'}));
assert.commandWorked(coll.insert({noonSense: 'pm', mealCombined: 'yes'}));
assert.commandWorked(coll.insert({noonSense: 'pm', mealCombined: 'no'}));
assert.eq(['breakfast', 'brunch', 'dinner', 'linner'],
coll.aggregate([
{
$project: {
meal: {
$cond: [
{$eq: ['$noonSense', 'am']},
{$cond: [{$eq: ['$mealCombined', 'yes']}, 'brunch', 'breakfast']},
{$cond: [{$eq: ['$mealCombined', 'yes']}, 'linner', 'dinner']}
]
}
}
},
{$sort: {meal: 1}}
])
.map(doc => doc.meal));
assert.commandWorked(coll.insert({noonSense: "am", mealCombined: "no"}));
assert.commandWorked(coll.insert({noonSense: "am", mealCombined: "yes"}));
assert.commandWorked(coll.insert({noonSense: "pm", mealCombined: "yes"}));
assert.commandWorked(coll.insert({noonSense: "pm", mealCombined: "no"}));
assert.eq(
["breakfast", "brunch", "dinner", "linner"],
coll
.aggregate([
{
$project: {
meal: {
$cond: [
{$eq: ["$noonSense", "am"]},
{$cond: [{$eq: ["$mealCombined", "yes"]}, "brunch", "breakfast"]},
{$cond: [{$eq: ["$mealCombined", "yes"]}, "linner", "dinner"]},
],
},
},
},
{$sort: {meal: 1}},
])
.map((doc) => doc.meal),
);

View File

@ -28,14 +28,14 @@ const conversionTestDocs = [
_id: 9,
input: "0123456789abcdef01234567",
target: "objectId",
expected: ObjectId("0123456789abcdef01234567")
expected: ObjectId("0123456789abcdef01234567"),
},
{_id: 10, input: "", target: "bool", expected: true},
{
_id: 11,
input: "1970-01-01T00:00:00.001Z",
target: "date",
expected: ISODate("1970-01-01T00:00:00.001Z")
expected: ISODate("1970-01-01T00:00:00.001Z"),
},
{_id: 12, input: "1", target: "int", expected: NumberInt(1)},
{_id: 13, input: "1", target: "long", expected: NumberLong(1)},
@ -45,20 +45,20 @@ const conversionTestDocs = [
_id: 15,
input: ObjectId("0123456789abcdef01234567"),
target: "string",
expected: "0123456789abcdef01234567"
expected: "0123456789abcdef01234567",
},
{_id: 16, input: ObjectId("0123456789abcdef01234567"), target: "bool", expected: true},
{
_id: 17,
input: ObjectId("0123456789abcdef01234567"),
target: "objectId",
expected: ObjectId("0123456789abcdef01234567")
expected: ObjectId("0123456789abcdef01234567"),
},
{
_id: 18,
input: ObjectId("0123456789abcdef01234567"),
target: "date",
expected: ISODate("1970-08-09T22:25:43Z")
expected: ISODate("1970-08-09T22:25:43Z"),
},
{_id: 19, input: false, target: "double", expected: 0.0},
@ -73,26 +73,26 @@ const conversionTestDocs = [
_id: 26,
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "string",
expected: "1970-01-01T00:00:00.123Z"
expected: "1970-01-01T00:00:00.123Z",
},
{_id: 27, input: ISODate("1970-01-01T00:00:00.123Z"), target: "bool", expected: true},
{
_id: 28,
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "date",
expected: ISODate("1970-01-01T00:00:00.123Z")
expected: ISODate("1970-01-01T00:00:00.123Z"),
},
{
_id: 29,
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "long",
expected: NumberLong(123)
expected: NumberLong(123),
},
{
_id: 30,
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "decimal",
expected: NumberDecimal("123")
expected: NumberDecimal("123"),
},
{_id: 31, input: NumberInt(1), target: "double", expected: 1.0},
@ -117,7 +117,7 @@ const conversionTestDocs = [
_id: 47,
input: NumberDecimal("1.9"),
target: "date",
expected: ISODate("1970-01-01T00:00:00.001Z")
expected: ISODate("1970-01-01T00:00:00.001Z"),
},
{_id: 48, input: NumberDecimal("1.9"), target: "int", expected: NumberInt(1)},
{_id: 49, input: NumberDecimal("1.9"), target: "long", expected: NumberLong(1)},
@ -129,7 +129,7 @@ const conversionTestDocs = [
{_id: 54, input: BinData(0, "BBBBBBBBBBBBBBBBBBBBBBBBBBBB"), target: "bool", expected: true},
{_id: 55, input: /B*/, target: "bool", expected: true},
{_id: 56, input: new DBRef("db.test", "oid"), target: "bool", expected: true},
{_id: 57, input: function() {}, target: "bool", expected: true},
{_id: 57, input: function () {}, target: "bool", expected: true},
// Symbol and CodeWScope are not supported from JavaScript, so we can't test them here.
{_id: 58, input: new Timestamp(1 / 1000, 1), target: "bool", expected: true},
{_id: 59, input: MinKey, target: "bool", expected: true},
@ -138,7 +138,7 @@ const conversionTestDocs = [
_id: 60,
input: Timestamp(1, 1),
target: "date",
expected: ISODate("1970-01-01T00:00:01.000Z")
expected: ISODate("1970-01-01T00:00:01.000Z"),
},
];
@ -184,7 +184,6 @@ const illegalConversionTestDocs = [
//
// One test document for each "nullish" value.
//
const nullTestDocs =
[{_id: 0, input: null}, {_id: 1, input: undefined}, {_id: 2, /* input is missing */}];
const nullTestDocs = [{_id: 0, input: null}, {_id: 1, input: undefined}, {_id: 2 /* input is missing */}];
runConvertTests({coll, requiresFCV80, conversionTestDocs, illegalConversionTestDocs, nullTestDocs});

View File

@ -148,12 +148,12 @@ const illegalConversionTestDocs = [
{
_id: 3,
input: ObjectId("0123456789abcdef01234567"),
target: {type: "binData", subtype: kUUIDSubtype}
target: {type: "binData", subtype: kUUIDSubtype},
},
{
_id: 4,
input: ObjectId("0123456789abcdef01234567"),
target: {type: "binData", subtype: kNonUUIDSubtype}
target: {type: "binData", subtype: kNonUUIDSubtype},
},
// Can't convert UUID string to non-UUID BinData.
@ -161,7 +161,7 @@ const illegalConversionTestDocs = [
_id: 5,
input: "867dee52-c331-484e-92d1-c56479b8e67e",
target: {type: "binData", subtype: kNonUUIDSubtype},
format: "uuid"
format: "uuid",
},
// Input is not a valid UUID, base64, hex or utf8 string.
@ -169,25 +169,25 @@ const illegalConversionTestDocs = [
_id: 6,
input: "867dee--52-c331-484e-",
target: {type: "binData", subtype: kUUIDSubtype},
format: "uuid"
format: "uuid",
},
{
_id: 7,
input: "867dee--52-c331-484e-",
target: {type: "binData", subtype: kNonUUIDSubtype},
format: "base64"
format: "base64",
},
{
_id: 8,
input: "867dee--52-c331-484e-",
target: {type: "binData", subtype: kNonUUIDSubtype},
format: "base64url"
format: "base64url",
},
{
_id: 9,
input: "867dee--52-c331-484e-zx",
target: {type: "binData", subtype: kNonUUIDSubtype},
format: "hex"
format: "hex",
},
// When converting from string to BinData, the "auto" format is not allowed, and the "uuid"
@ -196,25 +196,25 @@ const illegalConversionTestDocs = [
_id: 10,
input: "867dee52-c331-484e-92d1-c56479b8e67e",
target: {type: "binData", subtype: kNonUUIDSubtype},
format: "uuid"
format: "uuid",
},
{
_id: 11,
input: "867dee52-c331-484e-92d1-c56479b8e67e",
target: {type: "binData", subtype: kNonUUIDSubtype},
format: "auto"
format: "auto",
},
{
_id: 12,
input: "hn3uUsMxSE6S0cVkebjmfg==",
target: {type: "binData", subtype: kUUIDSubtype},
format: "base64"
format: "base64",
},
{
_id: 13,
input: "hn3uUsMxSE6S0cVkebjmfg==",
target: {type: "binData", subtype: kUUIDSubtype},
format: "auto"
format: "auto",
},
// Forbidden conversions between different binData subtypes
@ -251,14 +251,14 @@ const invalidArgumentValueDocs = [
input: "hn3uUsMxSE6S0cVkebjmfg==",
target: {type: "binData", subtype: -1},
format: "base64",
expectedCode: 4341107
expectedCode: 4341107,
},
{
_id: 2,
input: "hn3uUsMxSE6S0cVkebjmfg==",
target: {type: "binData", subtype: 1000},
format: "base64",
expectedCode: 4341107
expectedCode: 4341107,
},
{
_id: 3,
@ -266,7 +266,7 @@ const invalidArgumentValueDocs = [
// User-defined subtypes must be between 128-255.
target: {type: "binData", subtype: 256},
format: "base64",
expectedCode: 4341107
expectedCode: 4341107,
},
{
_id: 4,
@ -274,12 +274,11 @@ const invalidArgumentValueDocs = [
// User-defined subtypes must be between 128-255.
target: {type: "binData", subtype: 127},
format: "base64",
expectedCode: 4341107
expectedCode: 4341107,
},
// Invalid type.
{_id: 5, input: 123, target: {type: -2}, expectedCode: ErrorCodes.FailedToParse},
{_id: 6, input: 123, target: -2, expectedCode: ErrorCodes.FailedToParse},
];
runConvertTests(
{coll, requiresFCV80, conversionTestDocs, illegalConversionTestDocs, invalidArgumentValueDocs});
runConvertTests({coll, requiresFCV80, conversionTestDocs, illegalConversionTestDocs, invalidArgumentValueDocs});

View File

@ -218,8 +218,7 @@ const invalidArgumentValueDocs = [
},
];
runConvertTests(
{coll, requiresFCV81, conversionTestDocs, illegalConversionTestDocs, invalidArgumentValueDocs});
runConvertTests({coll, requiresFCV81, conversionTestDocs, illegalConversionTestDocs, invalidArgumentValueDocs});
// Additional tests covering shortcuts and string byteOrder.
function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
@ -227,61 +226,66 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
assert.commandWorked(coll.insertMany(documents));
let aggResult = coll.aggregate(convertPipeline).toArray();
aggResult.forEach(doc => {
aggResult.forEach((doc) => {
assert.eq(doc.output, doc.expected);
});
}
(function testConvertBindataToLong() {
let pipeline = [{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}}
}
}];
let pipeline = [
{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}},
},
},
];
testConvertNumeric({
pipeline: pipeline,
// Hex: "0x00000000000000c8", 8 byte long
docs: [{longInput: NumberLong(200), expected: BinData(0, "AAAAAAAAAMg=")}]
docs: [{longInput: NumberLong(200), expected: BinData(0, "AAAAAAAAAMg=")}],
});
})();
(function testConvertBindataToInt() {
let pipeline = [{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}}
}
}];
let pipeline = [
{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}},
},
},
];
testConvertNumeric({
pipeline: pipeline,
// Hex: "0xfffffe89", 4 byte int
docs: [{IntInput: NumberInt(-375), expected: BinData(0, "///+iQ==")}]
docs: [{IntInput: NumberInt(-375), expected: BinData(0, "///+iQ==")}],
});
})();
(function testConvertBindataToDouble() {
let pipeline = [{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}}
}
}];
let pipeline = [
{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}},
},
},
];
testConvertNumeric({
pipeline: pipeline,
// Hex: "0xC004CCCCCCCCCCCD", 8 byte double precision double
docs: [{DoubleInput: -2.6, expected: BinData(0, "wATMzMzMzM0=")}]
docs: [{DoubleInput: -2.6, expected: BinData(0, "wATMzMzMzM0=")}],
});
})();
(function testConvertBindataToIntShortCut() {
let pipeline = [{$project: {_id: 0, expected: 1, output: {$toInt: "$binDataInput"}}}];
// Hex: "0x=02", 1 byte integer
testConvertNumeric(
{pipeline: pipeline, docs: [{binDataInput: BinData(0, "Ag=="), expected: NumberInt(2)}]});
testConvertNumeric({pipeline: pipeline, docs: [{binDataInput: BinData(0, "Ag=="), expected: NumberInt(2)}]});
})();
(function testConvertBindataToLongShortCut() {
@ -289,7 +293,7 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
testConvertNumeric({
pipeline: pipeline,
// Hex: "0xA2020000", 4 byte long
docs: [{binDataInput: BinData(6, "ogIAAA=="), expected: NumberLong(674)}]
docs: [{binDataInput: BinData(6, "ogIAAA=="), expected: NumberLong(674)}],
});
})();
@ -298,6 +302,6 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
testConvertNumeric({
pipeline: pipeline,
// Hex: "0xCDCCCCCCCCCC04C0", 4 byte long
docs: [{binDataInput: BinData(0, "zczMzMzMBMA="), expected: -2.6}]
docs: [{binDataInput: BinData(0, "zczMzMzMBMA="), expected: -2.6}],
});
})();

View File

@ -85,7 +85,7 @@ const conversionTestDocs = [
input: "0123456789abcdef01234567",
target: "objectId",
base: 2,
expected: ObjectId("0123456789abcdef01234567")
expected: ObjectId("0123456789abcdef01234567"),
},
{_id: 53, input: ObjectId("0123456789abcdef01234567"), target: "bool", expected: true},
{
@ -93,7 +93,7 @@ const conversionTestDocs = [
input: ObjectId("0123456789abcdef01234567"),
target: "objectId",
base: 2,
expected: ObjectId("0123456789abcdef01234567")
expected: ObjectId("0123456789abcdef01234567"),
},
{_id: 55, input: false, target: "double", base: 2, expected: 0.0},
{_id: 56, input: false, target: "bool", base: 2, expected: false},
@ -102,14 +102,14 @@ const conversionTestDocs = [
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "date",
base: 2,
expected: ISODate("1970-01-01T00:00:00.123Z")
expected: ISODate("1970-01-01T00:00:00.123Z"),
},
{
_id: 58,
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "long",
base: 2,
expected: NumberLong(123)
expected: NumberLong(123),
},
{_id: 59, input: NumberInt(1), target: "int", base: 2, expected: NumberInt(1)},
{_id: 60, input: NumberInt(1), target: "long", base: 2, expected: NumberLong(1)},
@ -122,7 +122,7 @@ const conversionTestDocs = [
input: NumberDecimal("1.9"),
target: "decimal",
base: 2,
expected: NumberDecimal("1.9")
expected: NumberDecimal("1.9"),
},
// Complementary examples of illegal conversions.
@ -152,28 +152,28 @@ const conversionTestDocs = [
input: NumberInt(2147483647),
target: "string",
base: 2,
expected: "1111111111111111111111111111111"
expected: "1111111111111111111111111111111",
},
{
_id: 81,
input: NumberInt(-2147483648),
target: "string",
base: 2,
expected: "-10000000000000000000000000000000"
expected: "-10000000000000000000000000000000",
},
{
_id: 82,
input: NumberLong("9223372036854775807"),
target: "string",
base: 2,
expected: "111111111111111111111111111111111111111111111111111111111111111"
expected: "111111111111111111111111111111111111111111111111111111111111111",
},
{
_id: 83,
input: NumberLong("-9223372036854775808"),
target: "string",
base: 2,
expected: "-1000000000000000000000000000000000000000000000000000000000000000"
expected: "-1000000000000000000000000000000000000000000000000000000000000000",
},
{_id: 84, input: NumberInt(2147483647), target: "string", base: 8, expected: "17777777777"},
@ -183,14 +183,14 @@ const conversionTestDocs = [
input: NumberLong("9223372036854775807"),
target: "string",
base: 8,
expected: "777777777777777777777"
expected: "777777777777777777777",
},
{
_id: 87,
input: NumberLong("-9223372036854775808"),
target: "string",
base: 8,
expected: "-1000000000000000000000"
expected: "-1000000000000000000000",
},
{_id: 88, input: NumberInt(2147483647), target: "string", base: 10, expected: "2147483647"},
@ -200,14 +200,14 @@ const conversionTestDocs = [
input: NumberLong("9223372036854775807"),
target: "string",
base: 10,
expected: "9223372036854775807"
expected: "9223372036854775807",
},
{
_id: 91,
input: NumberLong("-9223372036854775808"),
target: "string",
base: 10,
expected: "-9223372036854775808"
expected: "-9223372036854775808",
},
{_id: 92, input: NumberInt(2147483647), target: "string", base: 16, expected: "7FFFFFFF"},
@ -217,14 +217,14 @@ const conversionTestDocs = [
input: NumberLong("9223372036854775807"),
target: "string",
base: 16,
expected: "7FFFFFFFFFFFFFFF"
expected: "7FFFFFFFFFFFFFFF",
},
{
_id: 95,
input: NumberLong("-9223372036854775808"),
target: "string",
base: 16,
expected: "-8000000000000000"
expected: "-8000000000000000",
},
];
@ -259,38 +259,38 @@ const illegalConversionTestDocs = [
_id: 16,
input: "1000000000000000000000000000000000000000000000000000000000000000",
target: "double",
base: 2
base: 2,
},
{
_id: 17,
input: "1000000000000000000000000000000000000000000000000000000000000000",
target: "long",
base: 2
base: 2,
},
{
_id: 18,
input: "1000000000000000000000000000000000000000000000000000000000000000",
target: "decimal",
base: 2
base: 2,
},
{_id: 19, input: "-10000000000000000000000000000001", target: "int", base: 2},
{
_id: 20,
input: "-1000000000000000000000000000000000000000000000000000000000000001",
target: "double",
base: 2
base: 2,
},
{
_id: 21,
input: "-1000000000000000000000000000000000000000000000000000000000000001",
target: "long",
base: 2
base: 2,
},
{
_id: 22,
input: "-1000000000000000000000000000000000000000000000000000000000000001",
target: "decimal",
base: 2
base: 2,
},
{_id: 23, input: "20000000000", target: "int", base: 8},
@ -363,7 +363,7 @@ const invalidArgumentValueDocs = [
input: ObjectId("0123456789abcdef01234567"),
target: "objectId",
base: 2.1,
expectedCode: 3501300
expectedCode: 3501300,
},
{_id: 56, input: false, target: "bool", base: 3, expectedCode: 3501301},
{
@ -371,7 +371,7 @@ const invalidArgumentValueDocs = [
input: ISODate("1970-01-01T00:00:00.123Z"),
target: "date",
base: 2.1,
expectedCode: 3501300
expectedCode: 3501300,
},
{_id: 59, input: NumberInt(1), target: "int", base: 3, expectedCode: 3501301},
{_id: 62, input: NumberLong(1), target: "long", base: 2.1, expectedCode: 3501300},
@ -386,5 +386,5 @@ runConvertTests({
runRoundTripTests: true,
conversionTestDocs,
illegalConversionTestDocs,
invalidArgumentValueDocs
invalidArgumentValueDocs,
});

View File

@ -32,7 +32,7 @@ function setupCollection() {
assert(result["_id"].toString().startsWith("ObjectId("));
}
// All generated ObjectIds are unique.
assert.eq(new Set(resultArray.map(res => res["_id"].toString())).size, resultArray.length);
assert.eq(new Set(resultArray.map((res) => res["_id"].toString())).size, resultArray.length);
}
// Test with invalid arguments.
@ -40,31 +40,28 @@ setupCollection();
const failedToParseCode = 9;
{
// non-empty object
const error = assert.throws(
() => coll.aggregate([{$project: {_id: {$createObjectId: {"key": "value"}}}}]).toArray());
const error = assert.throws(() =>
coll.aggregate([{$project: {_id: {$createObjectId: {"key": "value"}}}}]).toArray(),
);
assert.commandFailedWithCode(error, failedToParseCode);
}
{
// null
const error =
assert.throws(() => coll.aggregate([{$project: {_id: {$createObjectId: null}}}]).toArray());
const error = assert.throws(() => coll.aggregate([{$project: {_id: {$createObjectId: null}}}]).toArray());
assert.commandFailedWithCode(error, failedToParseCode);
}
{
// array
const error = assert.throws(
() => coll.aggregate([{$project: {_id: {$createObjectId: ["argument"]}}}]).toArray());
const error = assert.throws(() => coll.aggregate([{$project: {_id: {$createObjectId: ["argument"]}}}]).toArray());
assert.commandFailedWithCode(error, failedToParseCode);
}
{
// object id
const error = assert.throws(
() => coll.aggregate([{$project: {_id: {$createObjectId: ObjectId()}}}]).toArray());
const error = assert.throws(() => coll.aggregate([{$project: {_id: {$createObjectId: ObjectId()}}}]).toArray());
assert.commandFailedWithCode(error, failedToParseCode);
}
{
// string
const error = assert.throws(
() => coll.aggregate([{$project: {_id: {$createObjectId: "argument"}}}]).toArray());
const error = assert.throws(() => coll.aggregate([{$project: {_id: {$createObjectId: "argument"}}}]).toArray());
assert.commandFailedWithCode(error, failedToParseCode);
}

View File

@ -28,7 +28,7 @@ function basicTest() {
const pipeline = [
{$addFields: {uuidField: {$createUUID: {}}}},
{$project: {uuidStrField: {$toString: "$uuidField"}, uuidField: 1}},
{$project: {_id: 0}}
{$project: {_id: 0}},
];
const resultArray = coll.aggregate(pipeline).toArray();
@ -56,21 +56,21 @@ function lookupTest() {
setupCollection();
const pipeline = [
{$lookup: {
from: collName,
let: {
docId: "$_id"
{
$lookup: {
from: collName,
let: {
docId: "$_id",
},
pipeline: [{$addFields: {uuid: {$createUUID: {}}}}],
as: "result",
},
pipeline: [
{$addFields: {uuid: {$createUUID: {}}}}
],
as: "result"
}},
},
];
const resultArray = coll.aggregate(pipeline).toArray();
assert.eq(resultArray.length, docCount);
const s = resultArray.flatMap(doc => doc.result).map(doc => doc.uuid);
const s = resultArray.flatMap((doc) => doc.result).map((doc) => doc.uuid);
const set = new Set(s);
assert.eq(set.size, docCount * docCount);
}

View File

@ -32,7 +32,7 @@ function basicTest() {
// Evaluate a $group with another $currentDate expression.
{$group: {_id: "$time1", time2: {$first: {$currentDate: {}}}}},
{$project: {time1: "$_id", time2: 1}},
{$project: {_id: 0}}
{$project: {_id: 0}},
];
const resultArray = coll.aggregate(pipeline).toArray();
@ -55,13 +55,15 @@ try {
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluation", {ms: 2}),
primaryNodeOnly: false,
});
failPoints.push(...FixtureHelpers.mapOnEachShardNode({
db: db.getSiblingDB("admin"),
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluationSBE", {ms: 2}),
primaryNodeOnly: false,
}));
failPoints.push(
...FixtureHelpers.mapOnEachShardNode({
db: db.getSiblingDB("admin"),
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluationSBE", {ms: 2}),
primaryNodeOnly: false,
}),
);
basicTest();
} finally {
failPoints.forEach(failPoint => failPoint.off());
failPoints.forEach((failPoint) => failPoint.off());
}

View File

@ -26,18 +26,16 @@ function runAndAssertResultOrErrorCode(date, result, errorCode) {
function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
executeAggregationTestCase(coll, {
pipeline: [{$project: {_id: 0, newDate: dateArithmeticsSpec}}],
inputDocuments: [
{_id: 1, date: ISODate("2020-12-31T12:10:05"), unit: "month", timezone: "Europe/Paris"}
],
inputDocuments: [{_id: 1, date: ISODate("2020-12-31T12:10:05"), unit: "month", timezone: "Europe/Paris"}],
expectedErrorCode: expectedErrorCode,
expectedResults: expectedResult
expectedResults: expectedResult,
});
}
(function testDateAddWithValidInputs() {
runAndAssert({$dateAdd: {startDate: ISODate("2020-11-30T12:10:05Z"), unit: "day", amount: 1}},
[{newDate: ISODate("2020-12-01T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: ISODate("2020-11-30T12:10:05Z"), unit: "day", amount: 1}}, [
{newDate: ISODate("2020-12-01T12:10:05Z")},
]);
// Test that adding with a null argument (non-existing field) results in null.
runAndAssert({$dateAdd: {startDate: "$dateSent", unit: "day", amount: 1}}, [{newDate: null}]);
@ -46,148 +44,179 @@ function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: null}}, [{newDate: null}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1, timezone: null}},
[{newDate: null}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1, timezone: null}}, [{newDate: null}]);
// Test combination of null and invalid arguments.
runAndAssertResultOrErrorCode({$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
[{newDate: null}],
ErrorCodes.FailedToParse);
runAndAssertResultOrErrorCode(
{$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
[{newDate: null}],
ErrorCodes.FailedToParse,
);
runAndAssert({$dateAdd: {startDate: "New year day", unit: "$timeunit", amount: 1}},
[{newDate: null}]);
runAndAssert({$dateAdd: {startDate: "New year day", unit: "$timeunit", amount: 1}}, [{newDate: null}]);
runAndAssertResultOrErrorCode(
{$dateAdd: {startDate: "$date", unit: "workday", amount: "$amount", timezone: "Unknown"}},
[{newDate: null}],
ErrorCodes.FailedToParse);
ErrorCodes.FailedToParse,
);
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1.5, timezone: null}},
[{newDate: null}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1.5, timezone: null}}, [{newDate: null}]);
// Tests when startDate and result date cross the DST time change in a timezone.
runAndAssert({
$dateAdd: {
startDate: ISODate("2020-10-24T18:10:00Z"),
unit: "hour",
amount: 24,
timezone: "Europe/Paris"
}
},
[{newDate: ISODate("2020-10-25T18:10:00Z")}]);
runAndAssert(
{
$dateAdd: {
startDate: ISODate("2020-10-24T18:10:00Z"),
unit: "hour",
amount: 24,
timezone: "Europe/Paris",
},
},
[{newDate: ISODate("2020-10-25T18:10:00Z")}],
);
// When adding units of day both the startDate and the result represent 20:10:00 in
// Europe/Paris. The two dates have different offsets from UTC due to the change in daylight
// savings time.
runAndAssert({
$dateAdd: {
startDate: ISODate("2020-10-24T18:10:00Z"),
unit: "day",
amount: 1,
timezone: "Europe/Paris"
}
},
[{newDate: ISODate("2020-10-25T19:10:00Z")}]);
runAndAssert(
{
$dateAdd: {
startDate: ISODate("2020-10-24T18:10:00Z"),
unit: "day",
amount: 1,
timezone: "Europe/Paris",
},
},
[{newDate: ISODate("2020-10-25T19:10:00Z")}],
);
// The following tests use start date from the document field and all valid values for the
// 'unit' argument.
runAndAssert({$dateAdd: {startDate: "$date", unit: "year", amount: -1}},
[{newDate: ISODate("2019-12-31T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "year", amount: -1}}, [
{newDate: ISODate("2019-12-31T12:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "quarter", amount: 1}},
[{newDate: ISODate("2021-03-31T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "quarter", amount: 1}}, [
{newDate: ISODate("2021-03-31T12:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2}},
[{newDate: ISODate("2021-02-28T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2}}, [
{newDate: ISODate("2021-02-28T12:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "week", amount: 1}},
[{newDate: ISODate("2021-01-07T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "week", amount: 1}}, [
{newDate: ISODate("2021-01-07T12:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: 1}},
[{newDate: ISODate("2021-01-01T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: 1}}, [
{newDate: ISODate("2021-01-01T12:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
[{newDate: ISODate("2020-12-31T14:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "hour", amount: 2}}, [
{newDate: ISODate("2020-12-31T14:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "minute", amount: -20}},
[{newDate: ISODate("2020-12-31T11:50:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "minute", amount: -20}}, [
{newDate: ISODate("2020-12-31T11:50:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "millisecond", amount: 1050}},
[{newDate: ISODate("2020-12-31T12:10:06.05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "millisecond", amount: 1050}}, [
{newDate: ISODate("2020-12-31T12:10:06.05Z")},
]);
// Tests using the document fields for unit and timezone arguments.
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1}},
[{newDate: ISODate("2021-01-31T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1}}, [
{newDate: ISODate("2021-01-31T12:10:05Z")},
]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2, timezone: "$timezone"}},
[{newDate: ISODate("2021-02-28T12:10:05Z")}]);
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2, timezone: "$timezone"}}, [
{newDate: ISODate("2021-02-28T12:10:05Z")},
]);
})();
(function testDateSubtractWithValidInputs() {
runAndAssert({$dateSubtract: {startDate: "$date", unit: "year", amount: 2}},
[{newDate: ISODate("2018-12-31T12:10:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "year", amount: 2}}, [
{newDate: ISODate("2018-12-31T12:10:05Z")},
]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "quarter", amount: 2}},
[{newDate: ISODate("2020-06-30T12:10:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "quarter", amount: 2}}, [
{newDate: ISODate("2020-06-30T12:10:05Z")},
]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "day", amount: 15}},
[{newDate: ISODate("2020-12-16T12:10:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "day", amount: 15}}, [
{newDate: ISODate("2020-12-16T12:10:05Z")},
]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "hour", amount: 48}},
[{newDate: ISODate("2020-12-29T12:10:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "hour", amount: 48}}, [
{newDate: ISODate("2020-12-29T12:10:05Z")},
]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "minute", amount: 15}},
[{newDate: ISODate("2020-12-31T11:55:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "minute", amount: 15}}, [
{newDate: ISODate("2020-12-31T11:55:05Z")},
]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "second", amount: 125}},
[{newDate: ISODate("2020-12-31T12:08:00Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "second", amount: 125}}, [
{newDate: ISODate("2020-12-31T12:08:00Z")},
]);
// Test last day adjustment in UTC.
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3}},
[{newDate: ISODate("2020-09-30T12:10:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3}}, [
{newDate: ISODate("2020-09-30T12:10:05Z")},
]);
// Test last day adjustment and crossing DST time change in a timezone.
runAndAssert(
{$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3, timezone: "$timezone"}},
[{newDate: ISODate("2020-09-30T11:10:05Z")}]);
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3, timezone: "$timezone"}}, [
{newDate: ISODate("2020-09-30T11:10:05Z")},
]);
// Test last day adjustment in the New York timezone.
runAndAssert({
$dateSubtract: {
startDate: ISODate("2021-01-31T03:00:00Z"),
unit: "month",
amount: 2,
timezone: "America/New_York"
}
},
[{newDate: ISODate("2020-12-01T03:00:00Z")}]);
runAndAssert(
{
$dateSubtract: {
startDate: ISODate("2021-01-31T03:00:00Z"),
unit: "month",
amount: 2,
timezone: "America/New_York",
},
},
[{newDate: ISODate("2020-12-01T03:00:00Z")}],
);
})();
// Test combinations of $dateAdd and $dateSubtract.
(function testDateArithmetics() {
runAndAssert({
$dateSubtract: {
startDate: {$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
unit: "hour",
amount: 2
}
},
[{newDate: ISODate("2020-12-31T12:10:05Z")}]);
runAndAssert(
{
$dateSubtract: {
startDate: {$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
unit: "hour",
amount: 2,
},
},
[{newDate: ISODate("2020-12-31T12:10:05Z")}],
);
assert.eq(
coll.aggregate([{
$project: {newDate: {$dateSubtract: {startDate: "$date", unit: "month", amount: 1}}}
}])
coll
.aggregate([
{
$project: {newDate: {$dateSubtract: {startDate: "$date", unit: "month", amount: 1}}},
},
])
.toArray(),
coll.aggregate([{
$project: {
newDate: {
$dateAdd:
{startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5}
}
}
}])
.toArray());
coll
.aggregate([
{
$project: {
newDate: {
$dateAdd: {startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5},
},
},
},
])
.toArray(),
);
})();
// Tests for error codes.
@ -202,34 +231,27 @@ function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", units: "day", amount: 1}}, 5166401);
// Invalid string type of startDate argument.
runAndAssertErrorCode({$dateSubtract: {startDate: "myBirthDate", unit: "year", amount: 10}},
5166403);
runAndAssertErrorCode({$dateSubtract: {startDate: "myBirthDate", unit: "year", amount: 10}}, 5166403);
// Invalid numeric type of unit argument.
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: 1, amount: 10}}, 5166404);
// Invalid value of unit argument.
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "epoch", amount: 10}},
ErrorCodes.FailedToParse);
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "epoch", amount: 10}}, ErrorCodes.FailedToParse);
// Invalid double type of amount argument.
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "year", amount: 1.001}},
5166405);
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "year", amount: 1.001}}, 5166405);
// Overflow error of dateAdd operation due to large amount.
runAndAssertErrorCode(
{$dateSubtract: {startDate: "$date", unit: "month", amount: 12 * 300000000}}, 5166406);
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "month", amount: 12 * 300000000}}, 5166406);
// Invalid 'amount' parameter error of dateAdd operation due to large amount.
runAndAssertErrorCode(
{$dateSubtract: {startDate: "$date", unit: "month", amount: -30000000000}}, 5976500);
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "month", amount: -30000000000}}, 5976500);
// Invalid 'amount' parameter error of dateSubtract operation: long long min value cannot be
// negated.
runAndAssertErrorCode(
{$dateSubtract: {startDate: "$date", unit: "day", amount: -9223372036854775808}}, 6045000);
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "day", amount: -9223372036854775808}}, 6045000);
// Invalid value of timezone argument.
runAndAssertErrorCode(
{$dateAdd: {startDate: "$date", unit: "year", amount: 1, timezone: "Unknown"}}, 40485);
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "year", amount: 1, timezone: "Unknown"}}, 40485);
})();

View File

@ -14,103 +14,117 @@ const coll = testDB.collection;
assert.commandWorked(testDB.dropDatabase());
const someDate = new Date("2020-11-01T18:23:36Z");
const aggregationPipelineWithDateDiff = [{
$project: {
_id: false,
date_diff: {
$dateDiff:
{startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"}
}
}
}];
const aggregationPipelineWithDateDiffAndStartOfWeek = [{
$project: {
_id: false,
date_diff: {
$dateDiff: {
startDate: "$startDate",
endDate: "$endDate",
unit: "$unit",
timezone: "$timeZone",
startOfWeek: "$startOfWeek"
}
}
}
}];
const aggregationPipelineWithDateDiff = [
{
$project: {
_id: false,
date_diff: {
$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"},
},
},
},
];
const aggregationPipelineWithDateDiffAndStartOfWeek = [
{
$project: {
_id: false,
date_diff: {
$dateDiff: {
startDate: "$startDate",
endDate: "$endDate",
unit: "$unit",
timezone: "$timeZone",
startOfWeek: "$startOfWeek",
},
},
},
},
];
const testCases = [
{
// Parameters are constants, timezone is not specified.
pipeline: [{
$project: {
_id: true,
date_diff: {
$dateDiff: {
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour"
}
}
}
}],
pipeline: [
{
$project: {
_id: true,
date_diff: {
$dateDiff: {
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour",
},
},
},
},
],
inputDocuments: [{_id: 1}],
expectedResults: [{_id: 1, date_diff: NumberLong("6")}]
expectedResults: [{_id: 1, date_diff: NumberLong("6")}],
},
{
// Parameters are field paths.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [{
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour",
timeZone: "America/New_York",
startOfWeek: "IGNORED" // Ignored when unit is not week.
}],
expectedResults: [{date_diff: NumberLong("6")}]
inputDocuments: [
{
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour",
timeZone: "America/New_York",
startOfWeek: "IGNORED", // Ignored when unit is not week.
},
],
expectedResults: [{date_diff: NumberLong("6")}],
},
{
// Parameters are field paths, 'timezone' is not specified.
pipeline: [{
$project: {
_id: false,
date_diff:
{$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}}
}
}],
inputDocuments: [{
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour"
}],
expectedResults: [{date_diff: NumberLong("6")}]
pipeline: [
{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}},
},
},
],
inputDocuments: [
{
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour",
},
],
expectedResults: [{date_diff: NumberLong("6")}],
},
{
// 'startDate' and 'endDate' are object ids.
pipeline: [{
$project: {
_id: false,
date_diff: {
$dateDiff: {
startDate: "$_id",
endDate: "$_id",
unit: "millisecond",
timezone: "America/New_York"
}
}
}
}],
pipeline: [
{
$project: {
_id: false,
date_diff: {
$dateDiff: {
startDate: "$_id",
endDate: "$_id",
unit: "millisecond",
timezone: "America/New_York",
},
},
},
},
],
inputDocuments: [{}],
expectedResults: [{date_diff: NumberLong("0")}]
expectedResults: [{date_diff: NumberLong("0")}],
},
{
// 'startDate' and 'endDate' are timestamps.
pipeline: [{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}}
}
}],
pipeline: [
{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}},
},
},
],
inputDocuments: [{ts: new Timestamp()}],
expectedResults: [{date_diff: NumberLong("0")}]
expectedResults: [{date_diff: NumberLong("0")}],
},
{
// Invalid 'startDate' type.
@ -189,46 +203,53 @@ const testCases = [
{
// Invalid 'timezone' value.
pipeline: aggregationPipelineWithDateDiff,
inputDocuments:
[{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
expectedErrorCode: 40485,
},
{
// Specified 'startOfWeek'.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [{
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
unit: "week",
timeZone: "GMT",
startOfWeek: "MONDAY"
}],
inputDocuments: [
{
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
unit: "week",
timeZone: "GMT",
startOfWeek: "MONDAY",
},
],
expectedResults: [{date_diff: NumberLong("1")}],
},
{
// Specified 'startOfWeek' and timezone.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [{
startDate: new Date("2021-01-17T05:00:00Z"), // Sunday in New York.
endDate: new Date("2021-01-17T04:59:00Z"), // Saturday in New York.
unit: "week",
timeZone: "America/New_York",
startOfWeek: "sunday"
}],
inputDocuments: [
{
startDate: new Date("2021-01-17T05:00:00Z"), // Sunday in New York.
endDate: new Date("2021-01-17T04:59:00Z"), // Saturday in New York.
unit: "week",
timeZone: "America/New_York",
startOfWeek: "sunday",
},
],
expectedResults: [{date_diff: NumberLong("-1")}],
},
{
// Unspecified 'startOfWeek' - defaults to Sunday.
pipeline: [{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}}
}
}],
inputDocuments: [{
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
}],
pipeline: [
{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}},
},
},
],
inputDocuments: [
{
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
},
],
expectedResults: [{date_diff: NumberLong("0")}],
},
{
@ -246,30 +267,28 @@ const testCases = [
{
// Invalid 'startOfWeek' type.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [
{startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}
],
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}],
expectedErrorCode: 5439015,
},
{
// Invalid 'startOfWeek' type, unit is not the week.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [
{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}
],
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}],
expectedResults: [{date_diff: NumberLong("0")}],
},
{
// Invalid 'startOfWeek' value.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [{
startDate: someDate,
endDate: someDate,
unit: "week",
timeZone: "GMT",
startOfWeek: "FRIDIE"
}],
inputDocuments: [
{
startDate: someDate,
endDate: someDate,
unit: "week",
timeZone: "GMT",
startOfWeek: "FRIDIE",
},
],
expectedErrorCode: 5439016,
}
},
];
testCases.forEach(testCase => executeAggregationTestCase(coll, testCase));
testCases.forEach((testCase) => executeAggregationTestCase(coll, testCase));

View File

@ -7,35 +7,33 @@ function test(date, testSynthetics) {
c.drop();
c.save({date: date});
var ISOfmt = (date.getUTCMilliseconds() == 0) ? 'ISODate("%Y-%m-%dT%H:%M:%SZ")'
: 'ISODate("%Y-%m-%dT%H:%M:%S.%LZ")';
var ISOfmt = date.getUTCMilliseconds() == 0 ? 'ISODate("%Y-%m-%dT%H:%M:%SZ")' : 'ISODate("%Y-%m-%dT%H:%M:%S.%LZ")';
// Can't use aggregate helper or assertErrorCode because we need to handle multiple error types
var res = c.runCommand('aggregate', {
pipeline: [{
$project: {
_id: 0,
year: {$year: '$date'},
month: {$month: '$date'},
dayOfMonth: {$dayOfMonth: '$date'},
hour: {$hour: '$date'},
minute: {$minute: '$date'},
second: {$second: '$date'}
var res = c.runCommand("aggregate", {
pipeline: [
{
$project: {
_id: 0,
year: {$year: "$date"},
month: {$month: "$date"},
dayOfMonth: {$dayOfMonth: "$date"},
hour: {$hour: "$date"},
minute: {$minute: "$date"},
second: {$second: "$date"},
// server-6666
,
millisecond: {$millisecond: '$date'}
// server-6666
millisecond: {$millisecond: "$date"},
// server-9289
,
millisecondPlusTen: {$millisecond: {$add: ['$date', 10]}}
// server-9289
millisecondPlusTen: {$millisecond: {$add: ["$date", 10]}},
// server-11118
,
format: {$dateToString: {format: ISOfmt, date: '$date'}}
}
}],
cursor: {}
// server-11118
format: {$dateToString: {format: ISOfmt, date: "$date"}},
},
},
],
cursor: {},
});
if (date.valueOf() < 0 && _isWindows() && res.code == 16422) {
@ -58,20 +56,22 @@ function test(date, testSynthetics) {
assert.eq(res.cursor.firstBatch[0].minute, date.getUTCMinutes(), "minute");
assert.eq(res.cursor.firstBatch[0].second, date.getUTCSeconds(), "second");
assert.eq(res.cursor.firstBatch[0].millisecond, date.getUTCMilliseconds(), "millisecond");
assert.eq(res.cursor.firstBatch[0].millisecondPlusTen,
(date.getUTCMilliseconds() + 10) % 1000,
"millisecondPlusTen");
assert.eq(
res.cursor.firstBatch[0].millisecondPlusTen,
(date.getUTCMilliseconds() + 10) % 1000,
"millisecondPlusTen",
);
assert.eq(res.cursor.firstBatch[0].format, date.tojson(), "format");
assert.eq(res.cursor.firstBatch[0], {
year: date.getUTCFullYear(),
month: date.getUTCMonth() + 1, // jan == 1
month: date.getUTCMonth() + 1, // jan == 1
dayOfMonth: date.getUTCDate(),
hour: date.getUTCHours(),
minute: date.getUTCMinutes(),
second: date.getUTCSeconds(),
millisecond: date.getUTCMilliseconds(),
millisecondPlusTen: ((date.getUTCMilliseconds() + 10) % 1000),
format: date.tojson()
millisecondPlusTen: (date.getUTCMilliseconds() + 10) % 1000,
format: date.tojson(),
});
if (testSynthetics) {
@ -79,39 +79,39 @@ function test(date, testSynthetics) {
res = c.aggregate({
$project: {
_id: 0,
week: {$week: '$date'},
dayOfWeek: {$dayOfWeek: '$date'},
dayOfYear: {$dayOfYear: '$date'},
format: {$dateToString: {format: '%U-%w-%j', date: '$date'}}
}
week: {$week: "$date"},
dayOfWeek: {$dayOfWeek: "$date"},
dayOfYear: {$dayOfYear: "$date"},
format: {$dateToString: {format: "%U-%w-%j", date: "$date"}},
},
});
assert.eq(res.toArray()[0], {week: 0, dayOfWeek: 7, dayOfYear: 2, format: '00-7-002'});
assert.eq(res.toArray()[0], {week: 0, dayOfWeek: 7, dayOfYear: 2, format: "00-7-002"});
}
}
// Basic test
test(ISODate('1960-01-02 03:04:05.006Z'), true);
test(ISODate("1960-01-02 03:04:05.006Z"), true);
// Testing special rounding rules for seconds
test(ISODate('1960-01-02 03:04:04.999Z'), false); // second = 4
test(ISODate('1960-01-02 03:04:05.000Z'), true); // second = 5
test(ISODate('1960-01-02 03:04:05.001Z'), true); // second = 5
test(ISODate('1960-01-02 03:04:05.999Z'), true); // second = 5
test(ISODate("1960-01-02 03:04:04.999Z"), false); // second = 4
test(ISODate("1960-01-02 03:04:05.000Z"), true); // second = 5
test(ISODate("1960-01-02 03:04:05.001Z"), true); // second = 5
test(ISODate("1960-01-02 03:04:05.999Z"), true); // second = 5
// Test date before 1900 (negative tm_year values from gmtime)
test(ISODate('1860-01-02 03:04:05.006Z'), false);
test(ISODate("1860-01-02 03:04:05.006Z"), false);
// Test with time_t == -1 and 0
test(new Date(-1000), false);
test(new Date(0), false);
// Testing dates between 1970 and 2000
test(ISODate('1970-01-01 00:00:00.000Z'), false);
test(ISODate('1970-01-01 00:00:00.999Z'), false);
test(ISODate('1980-05-20 12:53:64.834Z'), false);
test(ISODate('1999-12-31 00:00:00.000Z'), false);
test(ISODate('1999-12-31 23:59:59.999Z'), false);
test(ISODate("1970-01-01 00:00:00.000Z"), false);
test(ISODate("1970-01-01 00:00:00.999Z"), false);
test(ISODate("1980-05-20 12:53:64.834Z"), false);
test(ISODate("1999-12-31 00:00:00.000Z"), false);
test(ISODate("1999-12-31 23:59:59.999Z"), false);
// Test date > 2000 for completeness (using now)
test(new Date(), false);

View File

@ -13,36 +13,40 @@ import "jstests/libs/query/sbe_assert_error_override.js";
const coll = db.date_expressions_with_time_zones;
coll.drop();
assert.commandWorked(coll.insert([
// Three sales on 2017-06-16 in UTC.
{_id: 0, date: new ISODate("2017-06-16T00:00:00.000Z"), sales: 1},
{_id: 1, date: new ISODate("2017-06-16T12:02:21.013Z"), sales: 2},
// Six sales on 2017-06-17 in UTC.
{_id: 2, date: new ISODate("2017-06-17T00:00:00.000Z"), sales: 2},
{_id: 3, date: new ISODate("2017-06-17T12:02:21.013Z"), sales: 2},
{_id: 4, date: new ISODate("2017-06-17T15:00:33.101Z"), sales: 2},
]));
assert.commandWorked(
coll.insert([
// Three sales on 2017-06-16 in UTC.
{_id: 0, date: new ISODate("2017-06-16T00:00:00.000Z"), sales: 1},
{_id: 1, date: new ISODate("2017-06-16T12:02:21.013Z"), sales: 2},
// Six sales on 2017-06-17 in UTC.
{_id: 2, date: new ISODate("2017-06-17T00:00:00.000Z"), sales: 2},
{_id: 3, date: new ISODate("2017-06-17T12:02:21.013Z"), sales: 2},
{_id: 4, date: new ISODate("2017-06-17T15:00:33.101Z"), sales: 2},
]),
);
// Compute how many sales happened on each day, in UTC.
assert.eq(
[
{_id: {year: 2017, month: 6, day: 16}, totalSales: 3},
{_id: {year: 2017, month: 6, day: 17}, totalSales: 6}
{_id: {year: 2017, month: 6, day: 17}, totalSales: 6},
],
coll.aggregate([
coll
.aggregate([
{
$group: {
_id: {
year: {$year: "$date"},
month: {$month: "$date"},
day: {$dayOfMonth: "$date"}
day: {$dayOfMonth: "$date"},
},
totalSales: {$sum: "$sales"}
}
totalSales: {$sum: "$sales"},
},
},
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
])
.toArray());
.toArray(),
);
// Compute how many sales happened on each day, in New York. The sales made at midnight should
// move to the previous days.
@ -50,44 +54,48 @@ assert.eq(
[
{_id: {year: 2017, month: 6, day: 15}, totalSales: 1},
{_id: {year: 2017, month: 6, day: 16}, totalSales: 4},
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4}
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4},
],
coll.aggregate([
coll
.aggregate([
{
$group: {
_id: {
year: {$year: {date: "$date", timezone: "America/New_York"}},
month: {$month: {date: "$date", timezone: "America/New_York"}},
day: {$dayOfMonth: {date: "$date", timezone: "America/New_York"}}
day: {$dayOfMonth: {date: "$date", timezone: "America/New_York"}},
},
totalSales: {$sum: "$sales"}
}
totalSales: {$sum: "$sales"},
},
},
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
])
.toArray());
.toArray(),
);
// Compute how many sales happened on each day, in Sydney (+10 hours).
assert.eq(
[
{_id: {year: 2017, month: 6, day: 16}, totalSales: 3},
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4},
{_id: {year: 2017, month: 6, day: 18}, totalSales: 2}
{_id: {year: 2017, month: 6, day: 18}, totalSales: 2},
],
coll.aggregate([
coll
.aggregate([
{
$group: {
_id: {
year: {$year: {date: "$date", timezone: "Australia/Sydney"}},
month: {$month: {date: "$date", timezone: "Australia/Sydney"}},
day: {$dayOfMonth: {date: "$date", timezone: "Australia/Sydney"}}
day: {$dayOfMonth: {date: "$date", timezone: "Australia/Sydney"}},
},
totalSales: {$sum: "$sales"}
}
totalSales: {$sum: "$sales"},
},
},
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
])
.toArray());
.toArray(),
);
assert(coll.drop());
assert.commandWorked(coll.insert({}));
@ -101,36 +109,38 @@ function runDateTimeExpressionWithTimezone(exprName, tz) {
function testDateTimeExpression(exprName, expectedValues) {
assert(coll.drop());
assert.commandWorked(
coll.insert({date: ISODate("2017-01-16T01:02:03.456Z"), timezone: "America/Sao_Paulo"}));
assert.eq(expectedValues.idBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
assert.commandWorked(coll.insert({date: ISODate("2017-01-16T01:02:03.456Z"), timezone: "America/Sao_Paulo"}));
assert.eq(
expectedValues.idBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "America/Sao_Paulo").cursor.firstBatch[0].out);
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out,
);
assert.eq(
expectedValues.idBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "America/Sao_Paulo").cursor.firstBatch[0].out,
);
// Test expression with offset based timezone
assert(coll.drop());
assert.commandWorked(
coll.insert({date: ISODate("2017-01-01T01:02:03.456Z"), timezone: "-01:30"}));
assert.eq(expectedValues.offsetBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
assert.eq(expectedValues.offsetBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "-01:30").cursor.firstBatch[0].out);
assert.commandWorked(coll.insert({date: ISODate("2017-01-01T01:02:03.456Z"), timezone: "-01:30"}));
assert.eq(
expectedValues.offsetBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out,
);
assert.eq(
expectedValues.offsetBasedTzExpected,
runDateTimeExpressionWithTimezone(exprName, "-01:30").cursor.firstBatch[0].out,
);
// Test expression when document has no $timezone field
assert(coll.drop());
assert.commandWorked(coll.insert({date: ISODate("2017-01-16T01:02:03.456Z")}));
assert.eq(null,
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
assert.eq(expectedValues.noTzExpected,
runDateTimeExpressionWithTimezone(exprName).cursor.firstBatch[0].out);
assert.eq(null, runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
assert.eq(expectedValues.noTzExpected, runDateTimeExpressionWithTimezone(exprName).cursor.firstBatch[0].out);
// Test expression when document has no date field
assert(coll.drop());
assert.commandWorked(coll.insert({timezone: "America/Sao_Paulo"}));
assert.eq(null,
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
assert.eq(null, runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
// test with invalid timezone identifier
assert(coll.drop());
@ -146,36 +156,23 @@ function testDateTimeExpression(exprName, expectedValues) {
// test with invalid date type
assert(coll.drop());
assert.commandWorked(
coll.insert({date: "2017-06-16T00:00:00.000Z", timezone: "America/Sao_Paulo"}));
assert.commandWorked(coll.insert({date: "2017-06-16T00:00:00.000Z", timezone: "America/Sao_Paulo"}));
assert.commandFailedWithCode(runDateTimeExpressionWithTimezone(exprName, "$timezone"), 16006);
}
testDateTimeExpression("$dayOfWeek",
{idBasedTzExpected: 1, offsetBasedTzExpected: 7, noTzExpected: 2});
testDateTimeExpression("$dayOfMonth",
{idBasedTzExpected: 15, offsetBasedTzExpected: 31, noTzExpected: 16});
testDateTimeExpression("$dayOfYear",
{idBasedTzExpected: 15, offsetBasedTzExpected: 366, noTzExpected: 16});
testDateTimeExpression("$year",
{idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
testDateTimeExpression("$month",
{idBasedTzExpected: 1, offsetBasedTzExpected: 12, noTzExpected: 1});
testDateTimeExpression("$hour",
{idBasedTzExpected: 23, offsetBasedTzExpected: 23, noTzExpected: 1});
testDateTimeExpression("$minute",
{idBasedTzExpected: 2, offsetBasedTzExpected: 32, noTzExpected: 2});
testDateTimeExpression("$second",
{idBasedTzExpected: 3, offsetBasedTzExpected: 3, noTzExpected: 3});
testDateTimeExpression("$millisecond",
{idBasedTzExpected: 456, offsetBasedTzExpected: 456, noTzExpected: 456});
testDateTimeExpression("$dayOfWeek", {idBasedTzExpected: 1, offsetBasedTzExpected: 7, noTzExpected: 2});
testDateTimeExpression("$dayOfMonth", {idBasedTzExpected: 15, offsetBasedTzExpected: 31, noTzExpected: 16});
testDateTimeExpression("$dayOfYear", {idBasedTzExpected: 15, offsetBasedTzExpected: 366, noTzExpected: 16});
testDateTimeExpression("$year", {idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
testDateTimeExpression("$month", {idBasedTzExpected: 1, offsetBasedTzExpected: 12, noTzExpected: 1});
testDateTimeExpression("$hour", {idBasedTzExpected: 23, offsetBasedTzExpected: 23, noTzExpected: 1});
testDateTimeExpression("$minute", {idBasedTzExpected: 2, offsetBasedTzExpected: 32, noTzExpected: 2});
testDateTimeExpression("$second", {idBasedTzExpected: 3, offsetBasedTzExpected: 3, noTzExpected: 3});
testDateTimeExpression("$millisecond", {idBasedTzExpected: 456, offsetBasedTzExpected: 456, noTzExpected: 456});
testDateTimeExpression("$week", {idBasedTzExpected: 3, offsetBasedTzExpected: 52, noTzExpected: 3});
testDateTimeExpression("$isoWeekYear",
{idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
testDateTimeExpression("$isoDayOfWeek",
{idBasedTzExpected: 7, offsetBasedTzExpected: 6, noTzExpected: 1});
testDateTimeExpression("$isoWeek",
{idBasedTzExpected: 2, offsetBasedTzExpected: 52, noTzExpected: 3});
testDateTimeExpression("$isoWeekYear", {idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
testDateTimeExpression("$isoDayOfWeek", {idBasedTzExpected: 7, offsetBasedTzExpected: 6, noTzExpected: 1});
testDateTimeExpression("$isoWeek", {idBasedTzExpected: 2, offsetBasedTzExpected: 52, noTzExpected: 3});
// Make sure the data type returned by the date/time expressions is correct
function testDateTimeExpressionType(exprName, exprType) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More