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

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(
{
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'
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(
{
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'`,
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");
}
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,12 +1,11 @@
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("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({
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: [{
pipeline: [
{
$group: {
_id: "$word",
wordCount: {
$accumulator: {
init: function() {
init: function () {
return 0;
},
accumulateArgs: ["$val"],
accumulate: function(state, val) {
accumulate: function (state, val) {
return state + val;
},
merge: function(state1, state2) {
merge: function (state1, state2) {
return state1 + state2;
},
finalize: function(state) {
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: [{
res = assert.commandWorked(
db.runCommand(
Object.merge(command, {
pipeline: [
{
$group: {
_id: 1,
avgWordLen: {
$accumulator: {
init: function() {
init: function () {
return {count: 0, sum: 0};
},
accumulateArgs: [{$strLenCP: "$word"}],
accumulate: function({count, sum}, wordLen) {
accumulate: function ({count, sum}, wordLen) {
return {count: count + 1, sum: sum + wordLen};
},
merge: function(s1, s2) {
merge: function (s1, s2) {
return {count: s1.count + s2.count, sum: s1.sum + s2.sum};
},
finalize: function({count, sum}) {
finalize: function ({count, sum}) {
return sum / count;
},
lang: 'js',
}
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
init: function () {
return [];
},
accumulateArgs: ["$no_such_field"],
accumulate: function(state, value) {
accumulate: function (state, value) {
return state.concat([value]);
},
merge: function(s1, s2) {
merge: function (s1, s2) {
return s1.concat(s2);
},
lang: 'js',
}
}
}
}];
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {},
init: function () {},
initArgs: {$const: 5},
accumulateArgs: [],
accumulate: function() {},
merge: function() {},
lang: 'js',
}
}
}
}];
accumulate: function () {},
merge: function () {},
lang: "js",
},
},
},
},
];
assert.commandFailedWithCode(db.runCommand(command), 4544711);
// Test that initArgs is passed to init.
command.pipeline = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function(str1, str2) {
init: function (str1, str2) {
return "initial_state_set_from_" + str1 + "_and_" + str2;
},
initArgs: ["ABC", "DEF"],
accumulateArgs: [],
accumulate: function(state) {
accumulate: function (state) {
return state;
},
merge: function(s1, s2) {
merge: function (s1, s2) {
return s1;
},
lang: 'js',
}
}
}
}];
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
throw 'init should not be called';
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';
accumulate: function () {
throw "accumulate should not be called";
},
merge: function() {
throw 'merge should not be called';
merge: function () {
throw "merge should not be called";
},
lang: 'js',
}
}
}
}];
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 = [{
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) {
init: function (...args) {
return args.toString();
},
initArgs: "$a",
accumulateArgs: [],
accumulate: function(state) {
accumulate: function (state) {
return state;
},
merge: function(s1, s2) {
merge: function (s1, s2) {
return s1;
},
lang: 'js',
}
}
}
}];
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {},
init: function () {},
accumulateArgs: {$const: 5},
accumulate: function(state, value) {},
merge: function(s1, s2) {},
lang: 'js',
}
}
}
}];
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {},
init: function () {},
accumulateArgs: ["ABC", "DEF"],
accumulate: function(state, str1, str2) {
accumulate: function (state, str1, str2) {
return str1 + str2;
},
merge: function(s1, s2) {
merge: function (s1, s2) {
return s1 || s2;
},
lang: 'js',
}
}
}
}];
res = assert.commandWorked(db.runCommand(command));
expectedResults = [
{_id: 1, value: "ABCDEF"},
lang: "js",
},
},
},
},
];
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
init: function () {
return [];
},
accumulateArgs: "$a",
accumulate: function(state, ...values) {
accumulate: function (state, ...values) {
state.push(values);
state.sort();
return state;
},
merge: function(s1, s2) {
merge: function (s1, s2) {
return s1.concat(s2);
},
lang: 'js',
}
}
}
}];
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 = [{
command.pipeline = [
{
$group: {
_id: 1,
value: {
$accumulator: {
init: function() {
init: function () {
return null;
},
accumulateArgs: [
null,
"$no_such_field",
{$let: {vars: {not_an_object: 5}, in : "$not_an_object.field"}}
{$let: {vars: {not_an_object: 5}, in: "$not_an_object.field"}},
],
accumulate: function(state, ...values) {
accumulate: function (state, ...values) {
return {
len: values.length,
types: values.map(v => typeof v),
types: values.map((v) => typeof v),
values: values,
};
},
merge: function(s1, s2) {
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]}},
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);

View File

@ -9,12 +9,14 @@ function runExample(groupKey, accumulatorSpec, aggregateOptions = {}) {
const aggregateCmd = {
aggregate: coll.getName(),
cursor: {},
pipeline: [{
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",
res = runExample(
"$_id",
{
init: function() {
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) {
accumulate: function (state) {
return state;
},
accumulateArgs: [1],
merge: function(state1, state2) {
merge: function (state1, state2) {
return state1;
},
finalize: function(state) {
finalize: function (state) {
return state.length;
},
lang: 'js',
lang: "js",
},
{allowDiskUse: false});
{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([
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}}
{$group: {_id: "$groupBy", count: largeAccumulator}},
])
.toArray();
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
assert.sameMembers(res, [
{_id: 1, count: 1000000},
{_id: 2, count: 1000000},
]);
// With $bucket.
res =
coll.aggregate([
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}}
}
$bucket: {groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}},
},
])
.toArray();
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
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}]);

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([
const res = coll
.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}}
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}},
])
.toArray();
const expected = [{
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.");
"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([
const res = coll
.aggregate([
{$sort: {_id: 1}},
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}}
{$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}}}}}
}
}])
"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,13 +14,11 @@ 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"}}}}
])
const res = coll
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}])
.toArray();
const expected = [{
const expected = [
{
"_id": {"min": 0, "max": 9},
"docs": [
{"_id": 0, "n": 9},
@ -32,21 +30,17 @@ coll.insertMany(docs);
{"_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.");
{"_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"}}}}
])
const res = coll
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}])
.toArray();
const expected = [
{
@ -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,26 +60,26 @@ 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}}}}}
}
}])
"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,77 +9,88 @@ 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([
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([
let result = coll
.aggregate([
{$match: {_id: {$in: [0, 1, 2]}}},
{$sort: {_id: 1}},
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
{$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([
result = coll
.aggregate([
{$match: {_id: {$in: [0, 1, 2]}}},
{$sort: {_id: -1}},
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
{$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({
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([
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'}}}
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
])
.toArray();
assert.eq(result, [{
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"
]
}]);
"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([
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([
{_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'}}}
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
])
.toArray();
assert.eq(result, [{_id: null, allBooks: ["Smile :)", "Happy!"]}]);
@ -88,62 +99,74 @@ assert.eq(result, [{_id: null, allBooks: ["Smile :)", "Happy!"]}]);
const notArrays = [1, "string", {object: "object"}, null];
for (const notAnArray of notArrays) {
assert.commandWorked(coll.insert([{
assert.commandWorked(
coll.insert([
{
_id: "doesNotMatter",
author: "doesNotMatter",
publisher: "doesNotMatter",
books: notAnArray
}]));
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([
result = coll
.aggregate([
{$match: {_id: {$in: [0, 1, 3]}}},
{$sort: {_id: 1}},
{$group: {_id: '$publisher', booksByPublisher: {$concatArrays: '$books'}}},
{$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([
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]]}]);
{_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([
result = coll
.aggregate([
{$sort: {_id: 1}},
{
$bucket: {
groupBy: '$_id',
groupBy: "$_id",
boundaries: [0, 5, 10],
output: {nums: {$concatArrays: "$arr"}}
}
}
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.eq(result, [
{"_id": 0, "nums": [0, 1, 2, 3, 4]},
{"_id": 5, "nums": [5, 6, 7, 8, 9]},
]);
assert(coll.drop());

View File

@ -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"}}},
])
{
// 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"}}},
])
{
// 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"}}},
])
{
// 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));
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"}}},
])
{
// 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([
{
// 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}}
{$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));
const expectedResults = [{_id: "AZ", sales: 0}];
assert(
orderedArrayEq(expectedResults, actualResults),
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
);
}
{ // Test $last in $bucketAuto.
let actualResults =
coll.aggregate([
{
// 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}}
{$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));
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})
{
// 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})
{
// 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,8 +8,7 @@ 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 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.
@ -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,22 +66,20 @@ 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}}}},
])
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([
const firstNResultsWithInitExpr = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
@ -87,23 +87,23 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
sales: {
$firstN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
},
])
.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([
const firstNResultsWithInitExprAndVariableGroupId = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
@ -111,33 +111,31 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
sales: {
$firstN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
},
])
.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}}}},
])
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([
const lastNResultsWithInitExpr = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
@ -145,23 +143,23 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
sales: {
$lastN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
},
])
.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([
const lastNResultsWithInitExprAndVariableGroupId = coll
.aggregate([
{$sort: {_id: 1}},
{
$group: {
@ -169,20 +167,20 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
sales: {
$lastN: {
input: "$sales",
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
}
}
}
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
},
},
},
},
])
.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,66 +197,66 @@ 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([
let actualFirstNBucketAutoResults = coll
.aggregate([
{$sort: {state: 1, sales: 1}},
{
$bucketAuto: {
groupBy: '$state',
groupBy: "$state",
buckets: 10 * 1000,
output: {sales: {$firstN: {input: "$sales", n: n}}}
}
output: {sales: {$firstN: {input: "$sales", n: n}}},
},
{$project: {_id: "$_id.min", sales: 1}}
},
{$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([
let actualLastNBucketAutoResults = coll
.aggregate([
{$sort: {state: 1, sales: 1}},
{
$bucketAuto: {
groupBy: '$state',
groupBy: "$state",
buckets: 10 * 1000,
output: {sales: {$lastN: {input: "$sales", n: n}}}
}
output: {sales: {$lastN: {input: "$sales", n: n}}},
},
{$project: {_id: "$_id.min", sales: 1}}
},
{$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(
const indexedFirstNResults = coll
.aggregate(
[
{$sort: {_id: 1}},
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
{$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}},
{$sort: {_id: 1}},
],
{hint: idxSpec})
{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})
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: [{
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$group: {
_id: {'st': '$state'},
sales: {$firstN: {input: '$sales', n: 2, randomField: "randomArg"}}
}
}],
cursor: {}
}),
5787901);
_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 = [{
const pipeline = [
{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
}
}];
},
},
];
assertErrCodeAndErrMsgContains(coll, pipeline, 9961600, aggFunction);
}
function assertNoError(aggFunction, aggFunctionArgument) {
const pipeline = [{
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 = [{
const pipeline = [
{
$group: {
_id: "$groupKey",
agg: {[aggFunction]: aggFunctionArgument},
$doingMerge: true,
}
}];
},
},
];
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
}

View File

@ -17,23 +17,25 @@ function reduce(key, values) {
return Array.sum(values);
}
let groupPipe = [{
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
allowDiskUse: true, // Set allowDiskUse to true to force the expression to run on a shard in the
// passthrough suites, where javascript execution is supported.
};
@ -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);

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: [{
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 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,11 +79,8 @@ 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}}
])
const actualResults = coll
.aggregate([{$group: {_id: "$state", sales: {[nFunction]: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}])
.toArray();
assert.eq(expectedResults, actualResults);
@ -94,14 +90,14 @@ 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([
let actualBucketAutoResults = coll
.aggregate([
{
$bucketAuto: {
groupBy: '$state',
groupBy: "$state",
buckets: 10 * 1000,
output: {sales: {[nFunction]: {input: '$sales', n: n}}}
}
output: {sales: {[nFunction]: {input: "$sales", n: n}}},
},
},
{$project: {_id: "$_id.min", sales: 1}},
{$sort: {_id: 1}},
@ -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}}}
}])
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: [{
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$bucketAuto: {
groupBy: "$state",
buckets: 2,
output: {minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
}
}],
cursor: {}
}),
4544714);
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: [{
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$group: {
_id: {'st': '$state'},
minSales: {$minN: {input: '$sales', n: 2, randomField: "randomArg"}}
}
}],
cursor: {}
}),
5787901);
_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],
],
},
msg: "'p' should be able to use expressions that evaluate to an array"
input: "$x",
method: "approximate",
},
},
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,11 +10,17 @@ 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}}}}
let result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$nums"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
@ -24,12 +30,18 @@ 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}}}}
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$nums"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
@ -41,9 +53,10 @@ 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}}}}
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$nums"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
@ -52,15 +65,18 @@ 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([
assert.commandWorked(
coll.insert([
{_id: 0, vals: [["nested"], 1, 2]},
{_id: 1, vals: [3, 4, ["nested"]]},
{_id: 2, vals: [4, ["nested", "extra"]]}
]));
{_id: 2, vals: [4, ["nested", "extra"]]},
]),
);
result = coll.aggregate([
{$group: {_id: null, n: {$setUnion: '$vals'}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
result = coll
.aggregate([
{$group: {_id: null, n: {$setUnion: "$vals"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
])
.toArray();
@ -70,33 +86,47 @@ assert(coll.drop());
// $setUnion should deduplicate objects as well. Note that documents which differ in field order are
// considered unique.
assert.commandWorked(coll.insert([
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}}}}
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([
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([
{_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}}}}
{$group: {_id: null, allBooks: {$setUnion: "$books"}}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$allBooks", sortBy: 1}}}},
])
.toArray();
@ -105,12 +135,18 @@ 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([
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}}}}
{$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]}]);
@ -118,19 +154,32 @@ 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([
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([
{_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}}}}
{$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([
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"]}
]));
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]},
]),
);
result =
coll.aggregate([
{$group: {_id: '$publisher', booksByPublisher: {$setUnion: '$books'}}},
result = coll
.aggregate([
{$group: {_id: "$publisher", booksByPublisher: {$setUnion: "$books"}}},
{$sort: {_id: 1}},
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$booksByPublisher', sortBy: 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"}}}
}])
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]}]);
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

@ -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 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,15 +68,15 @@ function buildTopNBottomNSpec(op, sortSpec, outputSpec, nValue) {
* Helper that verifies that 'op' and 'sortSpec' produce 'expectedResults'.
*/
function assertExpected(op, sortSpec, expectedResults) {
const actual =
coll.aggregate([
const actual = coll
.aggregate([
{
$group: {
_id: "$state",
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)
}
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN),
},
{$sort: {_id: 1}}
},
{$sort: {_id: 1}},
])
.toArray();
assert.eq(expectedResults, actual);
@ -84,15 +87,14 @@ 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([
let actualBucketAutoResults = coll
.aggregate([
{
$bucketAuto: {
groupBy: '$state',
groupBy: "$state",
buckets: 10 * 1000,
output:
{associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)}
}
output: {associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)},
},
},
{$project: {_id: "$_id.min", associates: 1}},
{$sort: {_id: 1}},
@ -116,19 +118,18 @@ 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([
const combinedGroup = coll
.aggregate([
{
$group: {
_id: "$state",
bottomAsc: buildTopNBottomNSpec("$bottomN", {sales: 1}, "$associate", defaultN),
bottomDesc:
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)
}
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN),
},
{$sort: {_id: 1}}
},
{$sort: {_id: 1}},
])
.toArray();
@ -143,25 +144,24 @@ 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}}}
}
}])
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
},
},
])
.toArray();
// Verify that the 'CA' group has 10 results, while all others have only 4.
@ -181,12 +181,13 @@ 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({
const outputMissing = coll
.aggregate({
$group: {
_id: "",
bottom: {$bottom: {output: "$b", sortBy: {sales: 1}}},
top: {$top: {output: "$b", sortBy: {sales: 1}}}
}
top: {$top: {output: "$b", sortBy: {sales: 1}}},
},
})
.toArray();
assert.eq(null, outputMissing[0]["top"]);
@ -195,32 +196,39 @@ assert.eq(null, outputMissing[0]["bottom"]);
// Error cases.
// Cannot reference the group key in $bottomN when using $bucketAuto.
assert.commandFailedWithCode(coll.runCommand("aggregate", {
pipeline: [{
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$bucketAuto: {
groupBy: "$state",
buckets: 2,
output: {
bottomAssociates:
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
}
}
}],
cursor: {}
}),
4544714);
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: [{
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [
{
$group: {
_id: {'st': '$state'},
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}}
}
}],
cursor: {}
}),
5787903);
_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();
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),
assert.gte(
NumberDecimal(nVal),
NumberDecimal(arr.length),
nVal + " is not GTE array length of " + tojson(arr) + " for field " +
fieldName);
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", {
delProps.forEach((delProp) => delete spec[delProp]);
assert.commandFailedWithCode(
coll.runCommand("aggregate", {
pipeline: [{$group: {_id: {"st": "$state"}, bottomAssociates: {[op]: spec}}}],
cursor: {}
cursor: {},
}),
errCode);
errCode,
);
};
// Reject non-integral/negative values of n.
@ -326,9 +340,8 @@ 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}}}}})
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);
@ -336,16 +349,16 @@ assert.eq([2, 3, 4], embeddedResult[0].result);
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}}}}})
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}}}}})
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);
@ -354,54 +367,55 @@ 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({
const actualFullAscending = coll
.aggregate({
$group: {
_id: "",
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}}
}
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({
const actualAAscendingBDescending = coll
.aggregate({
$group: {
_id: "",
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}}
}
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"}}}])
const sortStageResult = coll
.aggregate([{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
.toArray()
.map(doc => doc["text"]);
.map((doc) => doc["text"]);
const testOperatorText = (op) => {
const opNResult =
coll.aggregate([
const opNResult = coll
.aggregate([
{$match: {$text: {$search: "apples pears"}}},
{
$group: {
_id: "",
result: {
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}}
}
}
}
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}},
},
},
},
])
.toArray();
assert.eq(opNResult.length, 1);
@ -417,9 +431,8 @@ 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}}}}}])
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 ','
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({
assert.commandFailed(
db.runCommand({
aggregate: t.getName(),
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: '$a'}}}}]
}));
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({
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'}
}
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({
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())
]
}));
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}}), [{
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [
{
_id: 0,
linkArray: [
{$ref: otherColl.getName(), $db: db.getName()},
{$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([
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"}}
{$project: {x: "$$ROOT.$ref"}},
])
.toArray(),
[{_id: 0, x: "hello world"}]);
[{_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(),
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}]}]));
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 = [{
const graphLookupPipeline = [
{
$graphLookup: {
from: graphLookupColl.getName(),
startWith: "$link.$id",
connectFromField: "link.$id",
connectToField: "_id",
as: "connectedDocuments"
}
as: "connectedDocuments",
},
{$sort: {_id: 1}}];
},
{$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")});

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());

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,7 +58,7 @@ function testLargeIn() {
function testLargeSwitch() {
jsTestLog("Testing large $switch");
const cases = range(150000)
.map(function(i) {
.map(function (i) {
return {case: {$gt: ["$a", i]}, then: i};
})
.reverse();
@ -72,21 +72,23 @@ function testLargeBucket() {
for (let i = 0; i < 100000; i++) {
boundaries.push(i);
}
runAgg([{
runAgg([
{
$bucket: {
groupBy: "$a",
boundaries: boundaries,
default: "default",
output: {"count": {$sum: 1}}
}
}]);
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;
@ -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
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 {
(function () {
const kMemLimit = 100;
let oldMemSettings = setHashGroupMemoryParameters(kMemLimit);
try {
testSpillingForVariousAccumulators();
} finally {
} 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 = [{
const pipeline = [
{
$lookup: {
from: foreignColl.getName(),
localField: localField,
foreignField: foreignField,
as: "matched"
}}];
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({
const oldMemSettings = assert.commandWorked(
db.adminCommand({
getParameter: 1,
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1
}))
.internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
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();

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({
assert.commandWorked(
db.runCommand({
explain: {aggregate: sourceColl.getName(), pipeline: [writingStage], cursor: {}},
verbosity: verbosity
}));
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([{
assert.commandWorked(
coll.insert([
{
_id: 0,
veryBigPositiveLong: NumberLong("9223372036854775806"),
veryBigPositiveDouble: 9223372036854775806,
veryBigPositiveDecimal: NumberDecimal("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,7 +9,8 @@ function getResultOfExpression(expr) {
return resultArray[0].computed;
}
assert.commandWorked(coll.insert({
assert.commandWorked(
coll.insert({
_id: 0,
decimalVal: NumberDecimal("819.5359123621083"),
doubleVal: 819.536,
@ -21,110 +22,115 @@ assert.commandWorked(coll.insert({
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();

View File

@ -5,7 +5,8 @@ import "jstests/libs/query/sbe_assert_error_override.js";
const coll = db.any_element_true;
coll.drop();
assert.commandWorked(coll.insert({
assert.commandWorked(
coll.insert({
_id: 0,
allTrue: [true, true],
someTrue: [true, false],
@ -15,8 +16,9 @@ assert.commandWorked(coll.insert({
undefinedInput: [undefined],
undefinedTrue: [undefined, true],
nullTrue: [null, true],
empty: []
}));
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}}])
const result = coll
.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
.toArray()
.map(r => r.res);
.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,27 +6,24 @@
* 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
@ -34,11 +31,11 @@ const $x = "$x"; // fieldpath to "block" constant folding
* @param {string} message error message
* @returns true if the explain output matches expectedOutput, and an assertion failure otherwise.
*/
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
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 {[op]: arr.map((elt) => buildExpressionFromArguments(elt, op))};
} else if (typeof arr === "string" || arr instanceof String) {
return arr;
} else {
return {$const: arr};
@ -54,85 +51,100 @@ function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
assert.eq(processedPipeline[0].$group._id, expected, message);
return true;
}
}
function assertConstantFoldingResults(input, addOutput, 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],
// Left-associative test cases.
assertConstantFoldingResults(
[1, 2, $x],
[3, $x],
[2, $x],
"Constants should fold left-to-right before the first non-constant.");
assertConstantFoldingResults(
"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.");
"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],
assertConstantFoldingResults(
[5, 2, $x, 3, 4],
[7, $x, 3, 4],
[10, $x, 3, 4],
"Constants should fold up until a non-constant.");
"Constants should fold up until a non-constant.",
);
assertConstantFoldingResults([$x, 1, 2, 3],
assertConstantFoldingResults(
[$x, 1, 2, 3],
[$x, 1, 2, 3],
"Non-constant at start of operand list blocks folding constants.");
[$x, 1, 2, 3],
"Non-constant at start of operand list blocks folding constants.",
);
assertConstantFoldingResults([[1, 2, $x], 3, 4, $x, 5],
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.");
"Nested operation folds as expected.",
);
assertConstantFoldingResults(
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.");
"Nested operation folds along with outer operation following left-associative rules.",
);
assertConstantFoldingResults(
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.");
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.",
);
assertConstantFoldingResults(
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.");
}());
"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) => {
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') {
} else if (typeof arr === "object") {
let out = {};
Object.keys(arr).forEach(k => {
Object.keys(arr).forEach((k) => {
out[k] = wrapLits(arr[k]);
});
return out;
} else if (typeof arr === 'string' || arr instanceof String) {
} else if (typeof arr === "string" || arr instanceof String) {
return arr;
} else {
return {$const: arr};
@ -141,77 +153,92 @@ const assertFoldedResult = (expr, 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]},
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.");
"Multiply inside add will fold as much as it can.",
);
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
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.");
"Add inside multiply will fold as much as it can.",
);
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
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.");
"Multiply without fieldpath will fold away and add will continue folding.",
);
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
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.");
"Add without fieldpath will fold away and multiply will continue folding.",
);
assertFoldedResult(
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.");
"Constant folding nested $multiply proceeds even after outer $add stops folding.",
);
assertFoldedResult(
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.");
}());
"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]}))
assertArrayEq({
actual: coll
.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
.toArray()
.map(idToString),
expected: [
"915242528741.9469524422272990976000",
"905721242210.0453137831269007622941",
]
});
expected: ["915242528741.9469524422272990976000", "905721242210.0453137831269007622941"],
});
// BF-24945
coll.drop();
coll.insert({x: 0, y: 4.1});
assert(numberDecimalsEqual(
// 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"]
}))
.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"]
});
}());
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

@ -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,7 +6,8 @@ import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
const coll = db.expression_binarySize;
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, x: ""},
{_id: 1, x: "abc"},
{_id: 2, x: "ab\0c"},
@ -15,12 +16,11 @@ assert.commandWorked(coll.insert([
{_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.

View File

@ -9,19 +9,22 @@ const collName = jsTestName();
const coll = db[collName];
coll.drop();
assert.commandWorked(coll.insert([
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"]}}}])
actual: coll
.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
.toArray()
.map(doc => doc.r),
expected: expectedResult
.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({
for (const operand of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
pipeline: [
{
$project: {
r: {[operator]: ["$a", operand]},
}
}]
},
},
],
}),
ErrorCodes.TypeMismatch);
ErrorCodes.TypeMismatch,
);
}
for (const argument of ["$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
assert.commandFailedWithCode(coll.runCommand({
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
pipeline: [
{
$project: {
r: {[operator]: argument},
}
}]
},
},
],
}),
ErrorCodes.TypeMismatch);
ErrorCodes.TypeMismatch,
);
}
}
assertArrayEq({
actual: coll.aggregate([
{$project: {r: {$bitNot: "$a"}}},
])
actual: coll
.aggregate([{$project: {r: {$bitNot: "$a"}}}])
.toArray()
.map(doc => doc.r),
expected: [-1, -2, -3, -4]
.map((doc) => doc.r),
expected: [-1, -2, -3, -4],
});
assert.commandFailedWithCode(coll.runCommand({
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
pipeline: [
{
$project: {
r: {$bitNot: 12.5},
}
}]
}),
ErrorCodes.TypeMismatch);
},
},
],
}),
ErrorCodes.TypeMismatch,
);
assert.commandFailedWithCode(coll.runCommand({
assert.commandFailedWithCode(
coll.runCommand({
aggregate: collName,
cursor: {},
pipeline: [{
pipeline: [
{
$project: {
r: {$bitNot: ["$a", "$b"]},
}
}]
}),
16020); // Error for incorrect number of arguments.
},
},
],
}),
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}}}}]),
assert.throws(
() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
[],
"$bsonSize requires a document input");
"$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,19 +64,56 @@ 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})
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})
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})
var results1 = coll
.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}], {collation: caseInsensitive})
.toArray();
var results2 = coll.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}],
{collation: caseInsensitive})
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,8 +163,8 @@ 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})
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);
@ -117,8 +172,8 @@ 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})
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);
@ -126,8 +181,8 @@ 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})
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);
@ -135,40 +190,44 @@ 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})
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,
testExpressionWithCollation(
coll,
{
$filter: {
input: {
$cond: {
if: {$eq: ["FOO", "foo"]},
then: ["a", "b", "A", "c", "C", "d"],
else: null
}
else: null,
},
},
as: "str",
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]}
}
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]},
},
},
["a", "A", "c", "C"],
caseInsensitive);
caseInsensitive,
);
// Test that $let's subexpressions respect the collation.
testExpressionWithCollation(coll,
testExpressionWithCollation(
coll,
{
$let: {
vars: {str: {$cond: [{$eq: ["A", "a"]}, "b", "c"]}},
in : {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]}
}
in: {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]},
},
},
"d",
caseInsensitive);
caseInsensitive,
);
// Test that $map's subexpressions respect the collation.
testExpressionWithCollation(
@ -177,17 +236,18 @@ testExpressionWithCollation(
$map: {
input: {$cond: [{$eq: ["A", "a"]}, ["aa", "a", "AA", "b"], null]},
as: "val",
in : {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]}
}
in: {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]},
},
},
[true, false, true, false],
caseInsensitive);
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})
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);
@ -200,20 +260,22 @@ testExpressionWithCollation(
$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}]
}
}
in: {
$cond: [{$eq: ["a", "A"]}, {sum: {$add: ["$$value.sum", "$$this"]}}, {sum: 0}],
},
},
},
{sum: 7},
caseInsensitive);
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(
results = coll
.aggregate(
[
{$sort: {_id: 1}},
{
@ -222,15 +284,16 @@ results = coll.aggregate(
$switch: {
branches: [
{case: {$eq: ["$a", "a"]}, then: "foo"},
{case: {$eq: ["$b", "b"]}, then: "bar"}
{case: {$eq: ["$b", "b"]}, then: "bar"},
],
default: "baz"
}
}
}
}
default: "baz",
},
},
},
},
],
{collation: caseInsensitive})
{collation: caseInsensitive},
)
.toArray();
assert.eq(3, results.length);
assert.eq("foo", results[0].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([{
results = coll
.aggregate(
[
{
$project: {
out: {
$zip: {
inputs: [
{$cond: [{$eq: ["A", "a"]}, "$evens", "$odds"]},
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]}
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]},
],
defaults: [0, {$cond: [{$eq: ["C", "c"]}, 5, 7]}],
useLongestLength: true
}
}
}
}],
{collation: caseInsensitive})
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,15 +24,9 @@ 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

@ -19,7 +19,8 @@ import {checkSbeFullyEnabled} from "jstests/libs/query/sbe_util.js";
const coll = db.projection_expr_concat_arrays;
coll.drop();
assert.commandWorked(coll.insertOne({
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"]]],
@ -33,13 +34,14 @@ assert.commandWorked(coll.insertOne({
str_val: "a string",
dbl_val: 2.0,
int_val: 1,
obj_val: {a: 1, b: "two"}
}));
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,7 +167,8 @@ runAndAssertThrows(["$int_arr", 32]);
assert(coll.drop());
// Test case where find returns multiple documents.
assert.commandWorked(coll.insertMany([
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: []},
@ -161,22 +176,29 @@ assert.commandWorked(coll.insertMany([
{arr1: [], arr2: []},
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: null},
{some_field: "foo"},
]));
runAndAssert(["$arr1", "$arr2"], [
]),
);
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"], [
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
]);
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([
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']}
]
}
}
{$eq: ["$noonSense", "am"]},
{$cond: [{$eq: ["$mealCombined", "yes"]}, "brunch", "breakfast"]},
{$cond: [{$eq: ["$mealCombined", "yes"]}, "linner", "dinner"]},
],
},
{$sort: {meal: 1}}
},
},
{$sort: {meal: 1}},
])
.map(doc => doc.meal));
.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 = [{
let pipeline = [
{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}}
}
}];
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 = [{
let pipeline = [
{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}}
}
}];
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 = [{
let pipeline = [
{
$project: {
_id: 0,
expected: 1,
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}}
}
}];
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: {
{
$lookup: {
from: collName,
let: {
docId: "$_id"
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({
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}},
runAndAssertResultOrErrorCode(
{$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
[{newDate: null}],
ErrorCodes.FailedToParse);
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({
runAndAssert(
{
$dateAdd: {
startDate: ISODate("2020-10-24T18:10:00Z"),
unit: "hour",
amount: 24,
timezone: "Europe/Paris"
}
timezone: "Europe/Paris",
},
[{newDate: ISODate("2020-10-25T18:10:00Z")}]);
},
[{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({
runAndAssert(
{
$dateAdd: {
startDate: ISODate("2020-10-24T18:10:00Z"),
unit: "day",
amount: 1,
timezone: "Europe/Paris"
}
timezone: "Europe/Paris",
},
[{newDate: ISODate("2020-10-25T19:10:00Z")}]);
},
[{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({
runAndAssert(
{
$dateSubtract: {
startDate: ISODate("2021-01-31T03:00:00Z"),
unit: "month",
amount: 2,
timezone: "America/New_York"
}
timezone: "America/New_York",
},
[{newDate: ISODate("2020-12-01T03:00:00Z")}]);
},
[{newDate: ISODate("2020-12-01T03:00:00Z")}],
);
})();
// Test combinations of $dateAdd and $dateSubtract.
(function testDateArithmetics() {
runAndAssert({
runAndAssert(
{
$dateSubtract: {
startDate: {$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
unit: "hour",
amount: 2
}
amount: 2,
},
[{newDate: ISODate("2020-12-31T12:10:05Z")}]);
},
[{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([{
coll
.aggregate([
{
$project: {
newDate: {
$dateAdd:
{startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5}
}
}
}])
.toArray());
$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,16 +14,18 @@ const coll = testDB.collection;
assert.commandWorked(testDB.dropDatabase());
const someDate = new Date("2020-11-01T18:23:36Z");
const aggregationPipelineWithDateDiff = [{
const aggregationPipelineWithDateDiff = [
{
$project: {
_id: false,
date_diff: {
$dateDiff:
{startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"}
}
}
}];
const aggregationPipelineWithDateDiffAndStartOfWeek = [{
$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"},
},
},
},
];
const aggregationPipelineWithDateDiffAndStartOfWeek = [
{
$project: {
_id: false,
date_diff: {
@ -32,60 +34,69 @@ const aggregationPipelineWithDateDiffAndStartOfWeek = [{
endDate: "$endDate",
unit: "$unit",
timezone: "$timeZone",
startOfWeek: "$startOfWeek"
}
}
}
}];
startOfWeek: "$startOfWeek",
},
},
},
},
];
const testCases = [
{
// Parameters are constants, timezone is not specified.
pipeline: [{
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"
}
}
}
}],
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: [{
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")}]
startOfWeek: "IGNORED", // Ignored when unit is not week.
},
],
expectedResults: [{date_diff: NumberLong("6")}],
},
{
// Parameters are field paths, 'timezone' is not specified.
pipeline: [{
pipeline: [
{
$project: {
_id: false,
date_diff:
{$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}}
}
}],
inputDocuments: [{
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")}]
unit: "hour",
},
],
expectedResults: [{date_diff: NumberLong("6")}],
},
{
// 'startDate' and 'endDate' are object ids.
pipeline: [{
pipeline: [
{
$project: {
_id: false,
date_diff: {
@ -93,24 +104,27 @@ const testCases = [
startDate: "$_id",
endDate: "$_id",
unit: "millisecond",
timezone: "America/New_York"
}
}
}
}],
timezone: "America/New_York",
},
},
},
},
],
inputDocuments: [{}],
expectedResults: [{date_diff: NumberLong("0")}]
expectedResults: [{date_diff: NumberLong("0")}],
},
{
// 'startDate' and 'endDate' are timestamps.
pipeline: [{
pipeline: [
{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}}
}
}],
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: [{
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"
}],
startOfWeek: "MONDAY",
},
],
expectedResults: [{date_diff: NumberLong("1")}],
},
{
// Specified 'startOfWeek' and timezone.
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [{
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"
}],
startOfWeek: "sunday",
},
],
expectedResults: [{date_diff: NumberLong("-1")}],
},
{
// Unspecified 'startOfWeek' - defaults to Sunday.
pipeline: [{
pipeline: [
{
$project: {
_id: false,
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}}
}
}],
inputDocuments: [{
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: [{
inputDocuments: [
{
startDate: someDate,
endDate: someDate,
unit: "week",
timeZone: "GMT",
startOfWeek: "FRIDIE"
}],
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: [{
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'}
year: {$year: "$date"},
month: {$month: "$date"},
dayOfMonth: {$dayOfMonth: "$date"},
hour: {$hour: "$date"},
minute: {$minute: "$date"},
second: {$second: "$date"},
// server-6666
,
millisecond: {$millisecond: '$date'}
millisecond: {$millisecond: "$date"},
// server-9289
,
millisecondPlusTen: {$millisecond: {$add: ['$date', 10]}}
millisecondPlusTen: {$millisecond: {$add: ["$date", 10]}},
// server-11118
,
format: {$dateToString: {format: ISOfmt, date: '$date'}}
}
}],
cursor: {}
format: {$dateToString: {format: ISOfmt, date: "$date"}},
},
},
],
cursor: {},
});
if (date.valueOf() < 0 && _isWindows() && res.code == 16422) {
@ -58,9 +56,11 @@ 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,
assert.eq(
res.cursor.firstBatch[0].millisecondPlusTen,
(date.getUTCMilliseconds() + 10) % 1000,
"millisecondPlusTen");
"millisecondPlusTen",
);
assert.eq(res.cursor.firstBatch[0].format, date.tojson(), "format");
assert.eq(res.cursor.firstBatch[0], {
year: date.getUTCFullYear(),
@ -70,8 +70,8 @@ function test(date, testSynthetics) {
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,7 +13,8 @@ import "jstests/libs/query/sbe_assert_error_override.js";
const coll = db.date_expressions_with_time_zones;
coll.drop();
assert.commandWorked(coll.insert([
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},
@ -21,28 +22,31 @@ assert.commandWorked(coll.insert([
{_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

View File

@ -1,10 +1,6 @@
import "jstests/libs/query/sbe_assert_error_override.js";
import {
anyEq,
assertErrCodeAndErrMsgContains,
assertErrorCode
} from "jstests/aggregation/extras/utils.js";
import {anyEq, assertErrCodeAndErrMsgContains, assertErrorCode} from "jstests/aggregation/extras/utils.js";
const coll = db.date_from_string;
@ -18,61 +14,63 @@ let testCases = [
{
expect: "2017-07-04T11:56:02Z",
inputString: "2017-07-04T11:56:02Z",
format: "%Y-%m-%dT%H:%M:%SZ"
format: "%Y-%m-%dT%H:%M:%SZ",
},
{
expect: "2017-07-04T11:56:02.813Z",
inputString: "2017-07-04T11:56:02.813Z",
format: "%Y-%m-%dT%H:%M:%S.%LZ"
format: "%Y-%m-%dT%H:%M:%S.%LZ",
},
{
expect: "2017-07-04T11:56:02.810Z",
inputString: "2017-07-04T11:56:02.81Z",
format: "%Y-%m-%dT%H:%M:%S.%LZ"
format: "%Y-%m-%dT%H:%M:%S.%LZ",
},
{
expect: "2017-07-04T11:56:02.800Z",
inputString: "2017-07-04T11:56:02.8Z",
format: "%Y-%m-%dT%H:%M:%S.%LZ"
format: "%Y-%m-%dT%H:%M:%S.%LZ",
},
{
expect: "2017-07-04T11:56:02Z",
inputString: "2017-07-04T11:56.02",
format: "%Y-%m-%dT%H:%M.%S"
format: "%Y-%m-%dT%H:%M.%S",
},
{
expect: "2017-07-04T11:56:02.813Z",
inputString: "2017-07-04T11:56.02.813",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
{
expect: "2017-07-04T11:56:02.810Z",
inputString: "2017-07-04T11:56.02.81",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
{
expect: "2017-07-04T11:56:02.800Z",
inputString: "2017-07-04T11:56.02.8",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
];
testCases.forEach(function(testCase) {
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}})
.toArray(),
tojson(testCase));
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}}).toArray(),
tojson(testCase),
);
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
}
}
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
@ -85,50 +83,55 @@ testCases = [
{
expect: "2017-07-04T10:56:02Z",
inputString: "2017-07-04T11:56.02",
format: "%Y-%m-%dT%H:%M.%S"
format: "%Y-%m-%dT%H:%M.%S",
},
{
expect: "2017-07-04T10:56:02.813Z",
inputString: "2017-07-04T11:56.02.813",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
{
expect: "2017-07-04T10:56:02.810Z",
inputString: "2017-07-04T11:56.02.81",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
{
expect: "2017-07-04T10:56:02.800Z",
inputString: "2017-07-04T11:56.02.8",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
];
testCases.forEach(function(testCase) {
assert.eq([{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll
.aggregate({
$project: {
date: {
$dateFromString:
{dateString: testCase.inputString, timezone: "Europe/London"}
}
}
$dateFromString: {dateString: testCase.inputString, timezone: "Europe/London"},
},
},
})
.toArray(),
tojson(testCase));
assert.eq([{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
tojson(testCase),
);
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: testCase.inputString,
timezone: "Europe/London",
format: testCase.format
}
}
}
format: testCase.format,
},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
@ -141,61 +144,68 @@ testCases = [
{
expect: "2017-07-04T10:56:02Z",
inputString: "2017-07-04T11:56.02",
format: "%Y-%m-%dT%H:%M.%S"
format: "%Y-%m-%dT%H:%M.%S",
},
{
expect: "2017-07-04T10:56:02.813Z",
inputString: "2017-07-04T11:56.02.813",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
{
expect: "2017-07-04T10:56:02.810Z",
inputString: "2017-07-04T11:56.02.81",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
{
expect: "2017-07-04T10:56:02.800Z",
inputString: "2017-07-04T11:56.02.8",
format: "%Y-%m-%dT%H:%M.%S.%L"
format: "%Y-%m-%dT%H:%M.%S.%L",
},
];
testCases.forEach(function(testCase) {
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {$dateFromString: {dateString: testCase.inputString, timezone: "+01:00"}}
}
date: {$dateFromString: {dateString: testCase.inputString, timezone: "+01:00"}},
},
})
.toArray(),
tojson(testCase));
assert.eq([{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
tojson(testCase),
);
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: testCase.inputString,
timezone: "+01:00",
format: testCase.format
}
}
}
format: testCase.format,
},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
/* Normal format tests from data. */
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, dateString: "2017-07-06T12:35:37Z", format: "%Y-%m-%dT%H:%M:%SZ"},
{_id: 1, dateString: "2017-07-06T12:35:37.513Z", format: "%Y-%m-%dT%H:%M:%S.%LZ"},
{_id: 2, dateString: "2017-07-06T12:35:37", format: "%Y-%m-%dT%H:%M:%S"},
{_id: 3, dateString: "2017-07-06T12:35:37.513", format: "%Y-%m-%dT%H:%M:%S.%L"},
{_id: 4, dateString: "1960-07-10T12:10:37.448", format: "%Y-%m-%dT%H:%M:%S.%L"},
]));
]),
);
let expectedResults = [
{"_id": 0, "date": ISODate("2017-07-06T12:35:37Z")},
@ -204,25 +214,30 @@ let expectedResults = [
{"_id": 3, "date": ISODate("2017-07-06T12:35:37.513Z")},
{"_id": 4, "date": ISODate("1960-07-10T12:10:37.448Z")},
];
assert.eq(expectedResults,
coll.aggregate([
assert.eq(
expectedResults,
coll
.aggregate([
{
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
// Repeat the test with an explicit format specifier string.
assert.eq(
expectedResults,
coll.aggregate([
coll
.aggregate([
{
$project: {date: {$dateFromString: {dateString: "$dateString", format: "$format"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
expectedResults = [
{"_id": 0, "date": new Date(1499344537000)},
@ -231,31 +246,37 @@ expectedResults = [
{"_id": 3, "date": new Date(1499344537513)},
{"_id": 4, "date": new Date(-299072962552)},
];
assert.eq(expectedResults,
coll.aggregate([
assert.eq(
expectedResults,
coll
.aggregate([
{
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
// Repeat the test with an explicit format specifier string.
assert.eq(
expectedResults,
coll.aggregate([
coll
.aggregate([
{
$project: {date: {$dateFromString: {dateString: "$dateString", format: "$format"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
/* --------------------------------------------------------------------------------------- */
/* Normal format tests from data, with time zone. */
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, dateString: "2017-07-06T12:35:37.513", timezone: "GMT"},
{_id: 1, dateString: "2017-07-06T12:35:37.513", timezone: "UTC"},
{_id: 2, dateString: "1960-07-10T12:35:37.513", timezone: "America/New_York"},
@ -263,7 +284,8 @@ assert.commandWorked(coll.insert([
{_id: 4, dateString: "2017-07-06T12:35:37.513", timezone: "America/Los_Angeles"},
{_id: 5, dateString: "2017-07-06T12:35:37.513", timezone: "Europe/Paris"},
{_id: 6, dateString: "2017-07-06T12:35:37.513", timezone: "+04:00"},
]));
]),
);
expectedResults = [
{"_id": 0, "date": ISODate("2017-07-06T12:35:37.513Z")},
@ -277,63 +299,66 @@ expectedResults = [
assert.eq(
expectedResults,
coll.aggregate([
coll
.aggregate([
{
$project:
{date: {$dateFromString: {dateString: "$dateString", timezone: "$timezone"}}},
$project: {date: {$dateFromString: {dateString: "$dateString", timezone: "$timezone"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
// Repeat the test with an explicit format specifier string.
assert.eq(expectedResults,
coll.aggregate([
assert.eq(
expectedResults,
coll
.aggregate([
{
$project: {
date: {
$dateFromString: {
dateString: "$dateString",
timezone: "$timezone",
format: "%Y-%m-%dT%H:%M:%S.%L"
}
}
format: "%Y-%m-%dT%H:%M:%S.%L",
},
},
{$sort: {_id: 1}}
},
},
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
/* --------------------------------------------------------------------------------------- */
/* dateString from data with timezone as constant */
coll.drop();
assert.commandWorked(coll.insert([
{_id: 0, dateString: "2017-07-06T12:35:37"},
]));
assert.commandWorked(coll.insert([{_id: 0, dateString: "2017-07-06T12:35:37"}]));
assert.eq(
[
{"_id": 0, "date": ISODate("2017-07-06T03:35:37Z")},
],
coll.aggregate([
[{"_id": 0, "date": ISODate("2017-07-06T03:35:37Z")}],
coll
.aggregate([
{
$project:
{date: {$dateFromString: {dateString: "$dateString", timezone: "Asia/Tokyo"}}},
$project: {date: {$dateFromString: {dateString: "$dateString", timezone: "Asia/Tokyo"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
/* --------------------------------------------------------------------------------------- */
/* dateString from constant with timezone from data */
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, timezone: "Europe/London"},
{_id: 1, timezone: "America/New_York"},
{_id: 2, timezone: "-05:00"},
]));
]),
);
assert.eq(
[
@ -341,18 +366,19 @@ assert.eq(
{"_id": 1, "date": ISODate("2017-07-19T22:52:35.199Z")},
{"_id": 2, "date": ISODate("2017-07-19T23:52:35.199Z")},
],
coll.aggregate([
coll
.aggregate([
{
$project: {
date: {
$dateFromString:
{dateString: "2017-07-19T18:52:35.199", timezone: "$timezone"}
}
$dateFromString: {dateString: "2017-07-19T18:52:35.199", timezone: "$timezone"},
},
},
{$sort: {_id: 1}}
},
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
/* --------------------------------------------------------------------------------------- */
/* BI format tests. */
@ -363,37 +389,39 @@ assert.commandWorked(coll.insert({_id: 0}));
let pipelines = [
{
expect: "2017-01-01T00:00:00Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-01-01 00:00:00"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-01-01 00:00:00"}}}},
},
{
expect: "2017-07-01T00:00:00Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-01 00:00:00"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-01 00:00:00"}}}},
},
{
expect: "2017-07-06T00:00:00Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06"}}}},
},
{
expect: "2017-07-06T00:00:00Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 00:00:00"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 00:00:00"}}}},
},
{
expect: "2017-07-06T11:00:00Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:00:00"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:00:00"}}}},
},
{
expect: "2017-07-06T11:36:00Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:00"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:00"}}}},
},
{
expect: "2017-07-06T11:36:54Z",
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:54"}}}}
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:54"}}}},
},
];
pipelines.forEach(function(pipeline) {
assert.eq([{_id: 0, date: ISODate(pipeline.expect)}],
pipelines.forEach(function (pipeline) {
assert.eq(
[{_id: 0, date: ISODate(pipeline.expect)}],
coll.aggregate(pipeline.pipeline).toArray(),
tojson(pipeline));
tojson(pipeline),
);
});
/* --------------------------------------------------------------------------------------- */
@ -424,31 +452,33 @@ testCases = [
{expect: "2017-07-14T15:02:44.771Z", inputString: "2017-07-14T12:02:44.771 P"},
{expect: "2017-07-14T12:02:44.771Z", inputString: "2017-07-14T12:02:44.771 Z"},
];
testCases.forEach(function(testCase) {
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}})
.toArray(),
tojson(testCase));
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}}).toArray(),
tojson(testCase),
);
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString:
{dateString: testCase.inputString, format: "%Y-%m-%dT%H:%M:%S.%L%z"}
}
}
$dateFromString: {dateString: testCase.inputString, format: "%Y-%m-%dT%H:%M:%S.%L%z"},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
/* BI format tests from data. */
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, dateString: "2017-01-01 00:00:00"},
{_id: 1, dateString: "2017-07-01 00:00:00"},
{_id: 2, dateString: "2017-07-06"},
@ -456,7 +486,8 @@ assert.commandWorked(coll.insert([
{_id: 4, dateString: "2017-07-06 11:00:00"},
{_id: 5, dateString: "2017-07-06 11:36:00"},
{_id: 6, dateString: "2017-07-06 11:36:54"},
]));
]),
);
assert.eq(
[
@ -466,21 +497,24 @@ assert.eq(
{"_id": 3, "date": ISODate("2017-07-06T00:00:00Z")},
{"_id": 4, "date": ISODate("2017-07-06T11:00:00Z")},
{"_id": 5, "date": ISODate("2017-07-06T11:36:00Z")},
{"_id": 6, "date": ISODate("2017-07-06T11:36:54Z")}
{"_id": 6, "date": ISODate("2017-07-06T11:36:54Z")},
],
coll.aggregate([
coll
.aggregate([
{
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
/* --------------------------------------------------------------------------------------- */
/* Wacky format tests from data. */
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, dateString: "July 4th, 2017"},
{_id: 1, dateString: "July 4th, 2017 12:39:30 BST"},
{_id: 2, dateString: "July 4th, 2017 11am"},
@ -490,7 +524,8 @@ assert.commandWorked(coll.insert([
{_id: 6, dateString: "2017-Jul-04 noon"},
{_id: 7, dateString: "2017-07-04 12:48:07 GMT+0545"},
{_id: 8, dateString: "2017-07-04 12:48:07 GMT-0200"},
]));
]),
);
assert.eq(
[
@ -504,13 +539,15 @@ assert.eq(
{"_id": 7, "date": ISODate("2017-07-04T07:03:07Z")},
{"_id": 8, "date": ISODate("2017-07-04T14:48:07Z")},
],
coll.aggregate([
coll
.aggregate([
{
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
])
.toArray());
.toArray(),
);
/* --------------------------------------------------------------------------------------- */
/* Tests formats that aren't supported with the normal $dateFromString parser. */
@ -527,24 +564,26 @@ testCases = [
{
inputString: "Day: 05 Month: 12 Year: 1988",
format: "Day: %d Month: %m Year: %Y",
expect: "1988-12-05T00:00:00Z"
expect: "1988-12-05T00:00:00Z",
},
{inputString: "Date: 1992/04/26", format: "Date: %Y/%m/%d", expect: "1992-04-26T00:00:00Z"},
{inputString: "4/26/1992:+0445", format: "%m/%d/%Y:%z", expect: "1992-04-25T19:15:00Z"},
{inputString: "4/26/1992:+285", format: "%m/%d/%Y:%Z", expect: "1992-04-25T19:15:00Z"},
];
testCases.forEach(function(testCase) {
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
}
}
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
@ -558,18 +597,20 @@ testCases = [
{inputString: "1.1.1", format: "%V.%u.%G", expect: "0001-01-01T00:00:00Z"},
{inputString: "2017, Day 5", format: "%Y, Day %j", expect: "2017-01-06T00:00:00Z"},
];
testCases.forEach(function(testCase) {
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
}
}
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
@ -579,18 +620,20 @@ testCases = [
{inputString: "2017, July 4", format: "%Y, %B %d", expect: "2017-07-04T00:00:00Z"},
{inputString: "oct 20 2020", format: "%b %d %Y", expect: "2020-10-20T00:00:00Z"},
];
testCases.forEach(function(testCase) {
testCases.forEach(function (testCase) {
assert.eq(
[{_id: 0, date: ISODate(testCase.expect)}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
}
}
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
},
},
})
.toArray(),
tojson(testCase));
tojson(testCase),
);
});
/* --------------------------------------------------------------------------------------- */
@ -598,20 +641,20 @@ testCases.forEach(function(testCase) {
coll.drop();
assert.commandWorked(coll.insert([
{_id: 0},
]));
assert.commandWorked(coll.insert([{_id: 0}]));
pipelines = [
[{'$project': {date: {$dateFromString: {dateString: "July 4th"}}}}],
[{'$project': {date: {$dateFromString: {dateString: "12:50:53"}}}}],
[{"$project": {date: {$dateFromString: {dateString: "July 4th"}}}}],
[{"$project": {date: {$dateFromString: {dateString: "12:50:53"}}}}],
];
pipelines.forEach(function(pipeline) {
assertErrCodeAndErrMsgContains(coll,
pipelines.forEach(function (pipeline) {
assertErrCodeAndErrMsgContains(
coll,
pipeline,
ErrorCodes.ConversionFailure,
"an incomplete date/time string has been found");
"an incomplete date/time string has been found",
);
});
/* --------------------------------------------------------------------------------------- */
@ -619,76 +662,84 @@ pipelines.forEach(function(pipeline) {
coll.drop();
assert.commandWorked(coll.insert([
{_id: 0},
]));
assert.commandWorked(coll.insert([{_id: 0}]));
pipelines = [
[{'$project': {date: {$dateFromString: {dateString: "2017, 12:50:53"}}}}],
[{'$project': {date: {$dateFromString: {dateString: "60.Monday1770/06:59"}}}}],
[{"$project": {date: {$dateFromString: {dateString: "2017, 12:50:53"}}}}],
[{"$project": {date: {$dateFromString: {dateString: "60.Monday1770/06:59"}}}}],
];
pipelines.forEach(function(pipeline) {
assertErrCodeAndErrMsgContains(
coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
pipelines.forEach(function (pipeline) {
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
});
/* --------------------------------------------------------------------------------------- */
/* NULL returns. */
coll.drop();
assert.commandWorked(coll.insert([
assert.commandWorked(
coll.insert([
{_id: 0, date: new ISODate("2017-06-19T15:13:25.713Z")},
{_id: 1, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: null},
{_id: 2, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: undefined},
]));
]),
);
pipelines = [
[{$project: {date: {$dateFromString: {dateString: "$tz"}}}}, {$sort: {_id: 1}}],
[
{
$project:
{date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", timezone: "$tz"}}}
$project: {date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", timezone: "$tz"}}},
},
{$sort: {_id: 1}}
{$sort: {_id: 1}},
],
];
pipelines.forEach(function(pipeline) {
assert.eq([{_id: 0, date: null}, {_id: 1, date: null}, {_id: 2, date: null}],
pipelines.forEach(function (pipeline) {
assert.eq(
[
{_id: 0, date: null},
{_id: 1, date: null},
{_id: 2, date: null},
],
coll.aggregate(pipeline).toArray(),
tojson(pipeline));
tojson(pipeline),
);
});
coll.drop();
assert.commandWorked(coll.insert([
{_id: 0},
{_id: 1, format: null},
{_id: 2, format: undefined},
]));
assert.commandWorked(coll.insert([{_id: 0}, {_id: 1, format: null}, {_id: 2, format: undefined}]));
assert(anyEq(
[{_id: 0, date: null}, {_id: 1, date: null}, {_id: 2, date: null}],
coll.aggregate({
$project:
{date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", format: "$format"}}}
assert(
anyEq(
[
{_id: 0, date: null},
{_id: 1, date: null},
{_id: 2, date: null},
],
coll
.aggregate({
$project: {date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", format: "$format"}}},
})
.toArray()));
.toArray(),
),
);
/* --------------------------------------------------------------------------------------- */
/* Parse errors. */
let pipeline = [{$project: {date: {$dateFromString: "no-object"}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, 40540, "$dateFromString only supports an object as an argument");
assertErrCodeAndErrMsgContains(coll, pipeline, 40540, "$dateFromString only supports an object as an argument");
pipeline = [{$project: {date: {$dateFromString: {"unknown": "$tz"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, 40541, "Unrecognized argument");
pipeline = [{$project: {date: {$dateFromString: {dateString: 5}}}}];
assertErrCodeAndErrMsgContains(coll,
assertErrCodeAndErrMsgContains(
coll,
pipeline,
ErrorCodes.ConversionFailure,
"$dateFromString requires that 'dateString' be a string");
"$dateFromString requires that 'dateString' be a string",
);
/* --------------------------------------------------------------------------------------- */
/* Passing in time zone with date/time string. */
@ -696,32 +747,30 @@ assertErrCodeAndErrMsgContains(coll,
pipeline = {
$project: {
date: {
$dateFromString:
{dateString: "2017-07-12T22:23:55 GMT+02:00", timezone: "Europe/Amsterdam"}
}
}
$dateFromString: {dateString: "2017-07-12T22:23:55 GMT+02:00", timezone: "Europe/Amsterdam"},
},
},
};
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
pipeline = {
$project: {
date: {$dateFromString: {dateString: "2017-07-12T22:23:55Z", timezone: "Europe/Amsterdam"}}
}
date: {$dateFromString: {dateString: "2017-07-12T22:23:55Z", timezone: "Europe/Amsterdam"}},
},
};
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
pipeline = {
$project: {
date: {
$dateFromString:
{dateString: "2017-07-12T22:23:55 America/New_York", timezone: "Europe/Amsterdam"}
}
}
$dateFromString: {dateString: "2017-07-12T22:23:55 America/New_York", timezone: "Europe/Amsterdam"},
},
},
};
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
pipeline = {
$project: {date: {$dateFromString: {dateString: "2017-07-12T22:23:55 Europe/Amsterdam"}}}
$project: {date: {$dateFromString: {dateString: "2017-07-12T22:23:55 Europe/Amsterdam"}}},
};
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
@ -737,67 +786,65 @@ assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Tr
// Test missing specifier prefix '%'.
pipeline = [{$project: {date: {$dateFromString: {dateString: "1992-26-04", format: "Y-d-m"}}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, ErrorCodes.ConversionFailure, "Format literal not found");
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Format literal not found");
pipeline = [{$project: {date: {$dateFromString: {dateString: "1992", format: "%n"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, 18536, "Invalid format character");
pipeline = [{
pipeline = [
{
$project: {
date: {
$dateFromString:
{dateString: "4/26/1992:+0445", format: "%m/%d/%Y:%z", timezone: "+0500"}
}
}
}];
$dateFromString: {dateString: "4/26/1992:+0445", format: "%m/%d/%Y:%z", timezone: "+0500"},
},
},
},
];
assertErrCodeAndErrMsgContains(
coll,
pipeline,
ErrorCodes.ConversionFailure,
"you cannot pass in a date/time string with GMT offset together with a timezone argument");
"you cannot pass in a date/time string with GMT offset together with a timezone argument",
);
pipeline = [{$project: {date: {$dateFromString: {dateString: "4/26/1992", format: 5}}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
assertErrCodeAndErrMsgContains(coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
pipeline = [{$project: {date: {$dateFromString: {dateString: "4/26/1992", format: {}}}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
assertErrCodeAndErrMsgContains(coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
pipeline = [{$project: {date: {$dateFromString: {dateString: "ISO Day 6", format: "ISO Day %u"}}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
pipeline =
[{$project: {date: {$dateFromString: {dateString: "ISO Week 52", format: "ISO Week %V"}}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
pipeline = [{$project: {date: {$dateFromString: {dateString: "ISO Week 52", format: "ISO Week %V"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
pipeline = [{
$project: {date: {$dateFromString: {dateString: "ISO Week 1, 2018", format: "ISO Week %V, %Y"}}}
}];
assertErrCodeAndErrMsgContains(coll,
pipeline = [
{
$project: {date: {$dateFromString: {dateString: "ISO Week 1, 2018", format: "ISO Week %V, %Y"}}},
},
];
assertErrCodeAndErrMsgContains(
coll,
pipeline,
ErrorCodes.ConversionFailure,
"Mixing of ISO dates with natural dates is not allowed");
"Mixing of ISO dates with natural dates is not allowed",
);
pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d/%G"}}}}];
assertErrCodeAndErrMsgContains(coll,
assertErrCodeAndErrMsgContains(
coll,
pipeline,
ErrorCodes.ConversionFailure,
"Mixing of ISO dates with natural dates is not allowed");
"Mixing of ISO dates with natural dates is not allowed",
);
pipeline =
[{$project: {date: {$dateFromString: {dateString: "Dece 31 2018", format: "%b %d %Y"}}}}];
assertErrCodeAndErrMsgContains(
coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
pipeline = [{$project: {date: {$dateFromString: {dateString: "Dece 31 2018", format: "%b %d %Y"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
// Test embedded null bytes in the 'dateString' and 'format' fields.
pipeline =
[{$project: {date: {$dateFromString: {dateString: "12/31\0/2018", format: "%m/%d/%Y"}}}}];
pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31\0/2018", format: "%m/%d/%Y"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Not enough data");
pipeline =
[{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d\0/%Y"}}}}];
pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d\0/%Y"}}}}];
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Trailing data");

View File

@ -12,165 +12,181 @@ coll.drop();
assert.commandWorked(coll.insert({_id: 0}));
// Test that the 'onError' value is returned when 'dateString' is not a valid date/time.
for (let inputDate of ["July 4th",
"12:50:53",
"2017",
"60.Monday1770/06:59",
"Not even close",
"July 4th, 10000"]) {
for (let inputDate of ["July 4th", "12:50:53", "2017", "60.Monday1770/06:59", "Not even close", "July 4th, 10000"]) {
assert.eq(
[{_id: 0, date: onErrorValue}],
coll.aggregate({
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}}
coll
.aggregate({
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}},
})
.toArray());
.toArray(),
);
}
// Test that the 'onError' value is returned when 'dateString' is not a string.
for (let inputDate of [5, {year: 2018, month: 2, day: 5}, ["2018-02-05"]]) {
assert.eq(
[{_id: 0, date: onErrorValue}],
coll.aggregate({
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}}
coll
.aggregate({
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}},
})
.toArray());
.toArray(),
);
}
// Test that the 'onError' value is ignored when 'dateString' is nullish.
for (let inputDate of [null, undefined, "$missing"]) {
assert.eq(
[{_id: 0, date: null}],
coll.aggregate({
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}}
coll
.aggregate({
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}},
})
.toArray());
.toArray(),
);
}
// Test that the 'onError' value is returned for unmatched format strings.
for (let inputFormat of ["%Y", "%Y-%m-%dT%H", "Y-m-d"]) {
assert.eq(
[{_id: 0, date: onErrorValue}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString:
{dateString: "2018-02-06", format: inputFormat, onError: onErrorValue}
}
}
$dateFromString: {dateString: "2018-02-06", format: inputFormat, onError: onErrorValue},
},
},
})
.toArray());
.toArray(),
);
}
// Test that null is returned when the 'timezone' or 'format' is nullish, regardless of the
// 'onError' value.
for (let nullishValue of [null, undefined, "$missing"]) {
assert.eq([{_id: 0, date: null}],
coll.aggregate({
assert.eq(
[{_id: 0, date: null}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: "2018-02-06T11:56:02Z",
format: nullishValue,
onError: onErrorValue
}
}
}
onError: onErrorValue,
},
},
},
})
.toArray());
assert.eq([{_id: 0, date: null}],
coll.aggregate({
.toArray(),
);
assert.eq(
[{_id: 0, date: null}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: "2018-02-06T11:56:02Z",
timezone: nullishValue,
onError: onErrorValue
}
}
}
onError: onErrorValue,
},
},
},
})
.toArray());
.toArray(),
);
}
// Test that onError is returned when the input is not a string and other parameters are
// nullish.
assert.eq(
[{_id: 0, date: onErrorValue}],
coll.aggregate({
$project:
{date: {$dateFromString: {dateString: 5, format: null, onError: onErrorValue}}}
coll
.aggregate({
$project: {date: {$dateFromString: {dateString: 5, format: null, onError: onErrorValue}}},
})
.toArray());
.toArray(),
);
assert.eq(
[{_id: 0, date: onErrorValue}],
coll.aggregate({
coll
.aggregate({
$project: {
date:
{$dateFromString: {dateString: 5, timezone: "$missing", onError: onErrorValue}}
}
date: {$dateFromString: {dateString: 5, timezone: "$missing", onError: onErrorValue}},
},
})
.toArray());
.toArray(),
);
// Test that onError is ignored when the input is an invalid string and other parameters are
// nullish.
assert.eq(
[{_id: 0, date: null}],
coll.aggregate({
coll
.aggregate({
$project: {
date: {
$dateFromString:
{dateString: "Invalid date string", format: null, onError: onErrorValue}
}
}
$dateFromString: {dateString: "Invalid date string", format: null, onError: onErrorValue},
},
},
})
.toArray());
assert.eq([{_id: 0, date: null}],
coll.aggregate({
.toArray(),
);
assert.eq(
[{_id: 0, date: null}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: "Invalid date string",
timezone: "$missing",
onError: onErrorValue
}
}
}
onError: onErrorValue,
},
},
},
})
.toArray());
.toArray(),
);
// Test that 'onError' can be any type, not just an ISODate.
for (let onError of [{}, 5, "Not a date", null, undefined]) {
assert.eq(
[{_id: 0, date: onError}],
coll.aggregate(
{$project: {date: {$dateFromString: {dateString: "invalid", onError: onError}}}})
.toArray());
coll.aggregate({$project: {date: {$dateFromString: {dateString: "invalid", onError: onError}}}}).toArray(),
);
}
// Test that a missing 'onError' value results in no output field when used within a $project
// stage.
assert.eq(
[{_id: 0}],
coll.aggregate(
{$project: {date: {$dateFromString: {dateString: "invalid", onError: "$missing"}}}})
.toArray());
coll.aggregate({$project: {date: {$dateFromString: {dateString: "invalid", onError: "$missing"}}}}).toArray(),
);
// Test that 'onError' is ignored when the 'format' is invalid.
let res = coll.runCommand("aggregate", {
pipeline: [{
$project:
{date: {$dateFromString: {dateString: "4/26/1992", format: 5, onError: onErrorValue}}}
}],
cursor: {}
pipeline: [
{
$project: {date: {$dateFromString: {dateString: "4/26/1992", format: 5, onError: onErrorValue}}},
},
],
cursor: {},
});
assert.commandFailedWithCode(res, 40684);
assertErrCodeAndErrMsgContains(
coll,
[{
[
{
$project: {
date: {$dateFromString: {dateString: "4/26/1992", format: "%n", onError: onErrorValue}}
}
}],
date: {$dateFromString: {dateString: "4/26/1992", format: "%n", onError: onErrorValue}},
},
},
],
18536,
"Invalid format character '%n' in format string");
"Invalid format character '%n' in format string",
);

View File

@ -11,52 +11,55 @@ assert.commandWorked(coll.insert({_id: 0}));
for (let inputDate of [null, undefined, "$missing"]) {
assert.eq(
[{_id: 0, date: onNullValue}],
coll.aggregate(
{$project: {date: {$dateFromString: {dateString: inputDate, onNull: onNullValue}}}})
.toArray());
coll.aggregate({$project: {date: {$dateFromString: {dateString: inputDate, onNull: onNullValue}}}}).toArray(),
);
}
// Test that null is returned when the 'timezone' or 'format' is nullish, regardless of the
// 'onNull' value.
for (let nullishValue of [null, undefined, "$missing"]) {
assert.eq([{_id: 0, date: null}],
coll.aggregate({
assert.eq(
[{_id: 0, date: null}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: "2018-02-06T11:56:02Z",
format: nullishValue,
onNull: onNullValue
}
}
}
onNull: onNullValue,
},
},
},
})
.toArray());
assert.eq([{_id: 0, date: null}],
coll.aggregate({
.toArray(),
);
assert.eq(
[{_id: 0, date: null}],
coll
.aggregate({
$project: {
date: {
$dateFromString: {
dateString: "2018-02-06T11:56:02Z",
timezone: nullishValue,
onNull: onNullValue
}
}
}
onNull: onNullValue,
},
},
},
})
.toArray());
.toArray(),
);
}
// Test that 'onNull' can be any type, not just an ISODate.
for (let onNull of [{}, 5, "Not a date", null, undefined]) {
assert.eq(
[{_id: 0, date: onNull}],
coll.aggregate(
{$project: {date: {$dateFromString: {dateString: "$missing", onNull: onNull}}}})
.toArray());
coll.aggregate({$project: {date: {$dateFromString: {dateString: "$missing", onNull: onNull}}}}).toArray(),
);
}
assert.eq(
[{_id: 0}],
coll.aggregate(
{$project: {date: {$dateFromString: {dateString: "$missing", onNull: "$missing"}}}})
.toArray());
coll.aggregate({$project: {date: {$dateFromString: {dateString: "$missing", onNull: "$missing"}}}}).toArray(),
);

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