SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
This commit is contained in:
parent
601555914b
commit
591928c619
@ -1,3 +1,6 @@
|
||||
# SERVER-108478 Prettier JS format
|
||||
1adc32cdd5d6f7b8a9283d328bf7aa7d32caba6b
|
||||
|
||||
# SERVER-72197 Clang format v12.0.1
|
||||
f63255ee677ecae5896d6f35dd712ed60ae8c39a
|
||||
ff917ea96cf91f76bbd73b0d88d626a8523c5ec0
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
!*.yml
|
||||
!*.yaml
|
||||
!*.idl
|
||||
!*.js
|
||||
|
||||
# Opt in specific JS file paths
|
||||
!*.mjs
|
||||
@ -45,4 +46,4 @@ build
|
||||
bazel-*
|
||||
|
||||
# Streams specific
|
||||
src/mongo/db/modules/enterprise/src/streams/third_party/mongocxx/dist
|
||||
src/mongo/db/modules/enterprise/src/streams/third_party/mongocxx/dist
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
{
|
||||
"files": ["*.js", "*.mjs"],
|
||||
"options": {
|
||||
"tabWidth": 4
|
||||
"tabWidth": 4,
|
||||
"printWidth": 120,
|
||||
"quoteProps": "preserve"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -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]": {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
const stopList = [
|
||||
"print",
|
||||
"printjson",
|
||||
"printjsononeline",
|
||||
];
|
||||
const stopList = ["print", "printjson", "printjsononeline"];
|
||||
|
||||
export default {
|
||||
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
@ -16,20 +11,18 @@ export default {
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression: function(node) {
|
||||
if (node.callee.type == "Identifier" &&
|
||||
stopList.some(fn => fn == node.callee.name)) {
|
||||
context.report(
|
||||
{
|
||||
node,
|
||||
message: `Direct use of '${
|
||||
node.callee
|
||||
.name}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn'
|
||||
CallExpression: function (node) {
|
||||
if (node.callee.type == "Identifier" && stopList.some((fn) => fn == node.callee.name)) {
|
||||
context.report({
|
||||
node,
|
||||
message: `Direct use of '${
|
||||
node.callee.name
|
||||
}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn'
|
||||
|
||||
More about rules configuration: https://eslint.org/docs/latest/use/configure/rules`,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
const print_fns = [
|
||||
'jsTestLog',
|
||||
'jsTest.log',
|
||||
'jsTest.log.info',
|
||||
'jsTest.log.debug',
|
||||
'jsTest.log.warning',
|
||||
'jsTest.log.error',
|
||||
'print',
|
||||
"jsTestLog",
|
||||
"jsTest.log",
|
||||
"jsTest.log.info",
|
||||
"jsTest.log.debug",
|
||||
"jsTest.log.warning",
|
||||
"jsTest.log.error",
|
||||
"print",
|
||||
];
|
||||
|
||||
function flattenMemberExpressionName(expr) {
|
||||
@ -19,7 +19,6 @@ function flattenMemberExpressionName(expr) {
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
@ -30,43 +29,31 @@ export default {
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression: function(node) {
|
||||
CallExpression: function (node) {
|
||||
if (node.callee.type == "MemberExpression") {
|
||||
node.callee.name = flattenMemberExpressionName(node.callee);
|
||||
} else if (node.callee.type != "Identifier") return;
|
||||
|
||||
} else if (node.callee.type != "Identifier")
|
||||
return;
|
||||
if (print_fns.every((name) => name != node.callee.name)) return;
|
||||
|
||||
if (print_fns.every((name) => name != node.callee.name))
|
||||
return;
|
||||
node.arguments.forEach((arg) => {
|
||||
if (arg.type != "CallExpression") return;
|
||||
|
||||
node.arguments.forEach(arg => {
|
||||
if (arg.type != "CallExpression")
|
||||
return;
|
||||
if (arg.callee.type != "Identifier") return;
|
||||
|
||||
if (arg.callee.type != "Identifier")
|
||||
return;
|
||||
if (arg.callee.name != "tojson" && arg.callee.name != "tojsononeline") return;
|
||||
|
||||
if (arg.callee.name != "tojson" && arg.callee.name != "tojsononeline")
|
||||
return;
|
||||
|
||||
context.report(
|
||||
{
|
||||
node,
|
||||
message: `Calling ${arg.callee.name}() as a parameter of ${
|
||||
node.callee
|
||||
.name}(). Consider using toJsonForLog() instead or disable this rule by adding '// eslint-disable-next-line mongodb/no-printing-tojson'`,
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange(
|
||||
[
|
||||
arg.callee.start,
|
||||
arg.callee.end,
|
||||
],
|
||||
"toJsonForLog");
|
||||
}
|
||||
});
|
||||
context.report({
|
||||
node,
|
||||
message: `Calling ${arg.callee.name}() as a parameter of ${
|
||||
node.callee.name
|
||||
}(). Consider using toJsonForLog() instead or disable this rule by adding '// eslint-disable-next-line mongodb/no-printing-tojson'`,
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange([arg.callee.start, arg.callee.end], "toJsonForLog");
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -1 +1 @@
|
||||
assert.eq(true, false)
|
||||
assert.eq(true, false);
|
||||
|
||||
@ -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"]));
|
||||
})();
|
||||
|
||||
@ -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"]));
|
||||
})();
|
||||
|
||||
@ -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"));
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import {describe, it} from "jstests/libs/mochalite.js";
|
||||
|
||||
describe("fruits", () => {
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
|
||||
import {describe, it} from "jstests/libs/mochalite.js";
|
||||
|
||||
const pass = () => assert(true, 'pass');
|
||||
const fail = () => assert(false, 'fail');
|
||||
const pass = () => assert(true, "pass");
|
||||
const fail = () => assert(false, "fail");
|
||||
|
||||
it("test1", pass);
|
||||
it("test2", pass);
|
||||
describe("describe", function() {
|
||||
describe("describe", function () {
|
||||
it("test3", pass);
|
||||
it("test4", fail); // This test will fail
|
||||
it("test4", fail); // This test will fail
|
||||
it("test5", pass);
|
||||
});
|
||||
|
||||
@ -1 +1 @@
|
||||
(function() {})();
|
||||
(function () {})();
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
* @tags: [featureFlagToaster]
|
||||
*/
|
||||
|
||||
(function() {})();
|
||||
(function () {})();
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
* @tags: [featureFlagFryer]
|
||||
*/
|
||||
|
||||
(function() {})();
|
||||
(function () {})();
|
||||
|
||||
@ -1 +1 @@
|
||||
(function() {})();
|
||||
(function () {})();
|
||||
|
||||
@ -4,11 +4,13 @@ const adminDB = db.getSiblingDB("admin");
|
||||
|
||||
assert.commandWorked(t.insert({_id: 1, x: 1}));
|
||||
|
||||
assert.commandWorked(adminDB.runCommand({
|
||||
configureFailPoint: "failCommand",
|
||||
mode: "alwaysOn",
|
||||
data: {
|
||||
errorCode: ErrorCodes.InternalError,
|
||||
failCommands: ["validate"],
|
||||
}
|
||||
}));
|
||||
assert.commandWorked(
|
||||
adminDB.runCommand({
|
||||
configureFailPoint: "failCommand",
|
||||
mode: "alwaysOn",
|
||||
data: {
|
||||
errorCode: ErrorCodes.InternalError,
|
||||
failCommands: ["validate"],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@ -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"}],
|
||||
|
||||
@ -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:
|
||||
[
|
||||
|
||||
@ -12,30 +12,32 @@ for (const word of ["hello", "world", "world", "hello", "hi"]) {
|
||||
}
|
||||
|
||||
const command = {
|
||||
aggregate: 'accumulator_js',
|
||||
aggregate: "accumulator_js",
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCount: {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
return 0;
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCount: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
return 0;
|
||||
},
|
||||
accumulateArgs: ["$val"],
|
||||
accumulate: function (state, val) {
|
||||
return state + val;
|
||||
},
|
||||
merge: function (state1, state2) {
|
||||
return state1 + state2;
|
||||
},
|
||||
finalize: function (state) {
|
||||
return state;
|
||||
},
|
||||
},
|
||||
accumulateArgs: ["$val"],
|
||||
accumulate: function(state, val) {
|
||||
return state + val;
|
||||
},
|
||||
merge: function(state1, state2) {
|
||||
return state1 + state2;
|
||||
},
|
||||
finalize: function(state) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let expectedResults = [
|
||||
@ -65,31 +67,37 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
// Test a finalizer other than the identity function. Finalizers are useful when the intermediate
|
||||
// state needs to be a different format from the final result.
|
||||
res = assert.commandWorked(db.runCommand(Object.merge(command, {
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
avgWordLen: {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
return {count: 0, sum: 0};
|
||||
res = assert.commandWorked(
|
||||
db.runCommand(
|
||||
Object.merge(command, {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
avgWordLen: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
return {count: 0, sum: 0};
|
||||
},
|
||||
accumulateArgs: [{$strLenCP: "$word"}],
|
||||
accumulate: function ({count, sum}, wordLen) {
|
||||
return {count: count + 1, sum: sum + wordLen};
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return {count: s1.count + s2.count, sum: s1.sum + s2.sum};
|
||||
},
|
||||
finalize: function ({count, sum}) {
|
||||
return sum / count;
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
accumulateArgs: [{$strLenCP: "$word"}],
|
||||
accumulate: function({count, sum}, wordLen) {
|
||||
return {count: count + 1, sum: sum + wordLen};
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return {count: s1.count + s2.count, sum: s1.sum + s2.sum};
|
||||
},
|
||||
finalize: function({count, sum}) {
|
||||
return sum / count;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
},
|
||||
}
|
||||
}],
|
||||
})));
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, avgWordLen: 22 / 5}]), res.cursor);
|
||||
|
||||
// Test that a null word is considered a valid value.
|
||||
@ -102,204 +110,230 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
// This is similar to how most other agg operators work.
|
||||
assert(db.accumulator_js.drop());
|
||||
assert.commandWorked(db.accumulator_js.insert({sentinel: 1}));
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
return [];
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
return [];
|
||||
},
|
||||
accumulateArgs: ["$no_such_field"],
|
||||
accumulate: function (state, value) {
|
||||
return state.concat([value]);
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return s1.concat(s2);
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
accumulateArgs: ["$no_such_field"],
|
||||
accumulate: function(state, value) {
|
||||
return state.concat([value]);
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return s1.concat(s2);
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: [null]}]), res.cursor);
|
||||
|
||||
// Test that initArgs must evaluate to an array.
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {},
|
||||
initArgs: {$const: 5},
|
||||
accumulateArgs: [],
|
||||
accumulate: function() {},
|
||||
merge: function() {},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {},
|
||||
initArgs: {$const: 5},
|
||||
accumulateArgs: [],
|
||||
accumulate: function () {},
|
||||
merge: function () {},
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.commandFailedWithCode(db.runCommand(command), 4544711);
|
||||
|
||||
// Test that initArgs is passed to init.
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function(str1, str2) {
|
||||
return "initial_state_set_from_" + str1 + "_and_" + str2;
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function (str1, str2) {
|
||||
return "initial_state_set_from_" + str1 + "_and_" + str2;
|
||||
},
|
||||
initArgs: ["ABC", "DEF"],
|
||||
accumulateArgs: [],
|
||||
accumulate: function (state) {
|
||||
return state;
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return s1;
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
initArgs: ["ABC", "DEF"],
|
||||
accumulateArgs: [],
|
||||
accumulate: function(state) {
|
||||
return state;
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return s1;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: "initial_state_set_from_ABC_and_DEF"}]),
|
||||
res.cursor);
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: "initial_state_set_from_ABC_and_DEF"}]), res.cursor);
|
||||
|
||||
// Test that when initArgs errors, we fail gracefully, and don't call init.
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
throw 'init should not be called';
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
throw "init should not be called";
|
||||
},
|
||||
// Use $cond to thwart constant folding, to ensure we are testing evaluate rather
|
||||
// than optimize.
|
||||
initArgs: {$add: {$cond: ["$foo", "", ""]}},
|
||||
accumulateArgs: [],
|
||||
accumulate: function () {
|
||||
throw "accumulate should not be called";
|
||||
},
|
||||
merge: function () {
|
||||
throw "merge should not be called";
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
// Use $cond to thwart constant folding, to ensure we are testing evaluate rather
|
||||
// than optimize.
|
||||
initArgs: {$add: {$cond: ["$foo", "", ""]}},
|
||||
accumulateArgs: [],
|
||||
accumulate: function() {
|
||||
throw 'accumulate should not be called';
|
||||
},
|
||||
merge: function() {
|
||||
throw 'merge should not be called';
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
// ErrorCodes.TypeMismatch means "$add only supports numeric or date types". Code 16554 represented
|
||||
// a type mismatch before 6.1 for this specific check.
|
||||
assert.commandFailedWithCode(db.runCommand(command), [16554, ErrorCodes.TypeMismatch]);
|
||||
|
||||
// Test that initArgs can have a different length per group.
|
||||
assert(db.accumulator_js.drop());
|
||||
assert.commandWorked(db.accumulator_js.insert([
|
||||
{_id: 1, a: ['A', 'B', 'C']},
|
||||
{_id: 2, a: ['A', 'B', 'C']},
|
||||
{_id: 3, a: ['X', 'Y']},
|
||||
{_id: 4, a: ['X', 'Y']},
|
||||
]));
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: {a: "$a"},
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function(...args) {
|
||||
return args.toString();
|
||||
assert.commandWorked(
|
||||
db.accumulator_js.insert([
|
||||
{_id: 1, a: ["A", "B", "C"]},
|
||||
{_id: 2, a: ["A", "B", "C"]},
|
||||
{_id: 3, a: ["X", "Y"]},
|
||||
{_id: 4, a: ["X", "Y"]},
|
||||
]),
|
||||
);
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: {a: "$a"},
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function (...args) {
|
||||
return args.toString();
|
||||
},
|
||||
initArgs: "$a",
|
||||
accumulateArgs: [],
|
||||
accumulate: function (state) {
|
||||
return state;
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return s1;
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
initArgs: "$a",
|
||||
accumulateArgs: [],
|
||||
accumulate: function(state) {
|
||||
return state;
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return s1;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
assert(
|
||||
resultsEq(res.cursor.firstBatch,
|
||||
[{_id: {a: ['A', 'B', 'C']}, value: "A,B,C"}, {_id: {a: ['X', 'Y']}, value: "X,Y"}]),
|
||||
res.cursor);
|
||||
resultsEq(res.cursor.firstBatch, [
|
||||
{_id: {a: ["A", "B", "C"]}, value: "A,B,C"},
|
||||
{_id: {a: ["X", "Y"]}, value: "X,Y"},
|
||||
]),
|
||||
res.cursor,
|
||||
);
|
||||
|
||||
// Test that accumulateArgs must evaluate to an array.
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {},
|
||||
accumulateArgs: {$const: 5},
|
||||
accumulate: function(state, value) {},
|
||||
merge: function(s1, s2) {},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {},
|
||||
accumulateArgs: {$const: 5},
|
||||
accumulate: function (state, value) {},
|
||||
merge: function (s1, s2) {},
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.commandFailedWithCode(db.runCommand(command), 4544712);
|
||||
|
||||
// Test that accumulateArgs can have more than one element.
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {},
|
||||
accumulateArgs: ["ABC", "DEF"],
|
||||
accumulate: function(state, str1, str2) {
|
||||
return str1 + str2;
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {},
|
||||
accumulateArgs: ["ABC", "DEF"],
|
||||
accumulate: function (state, str1, str2) {
|
||||
return str1 + str2;
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return s1 || s2;
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return s1 || s2;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [
|
||||
{_id: 1, value: "ABCDEF"},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [{_id: 1, value: "ABCDEF"}];
|
||||
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
// Test that accumulateArgs can have a different length per document.
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
return [];
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
return [];
|
||||
},
|
||||
accumulateArgs: "$a",
|
||||
accumulate: function (state, ...values) {
|
||||
state.push(values);
|
||||
state.sort();
|
||||
return state;
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return s1.concat(s2);
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
accumulateArgs: "$a",
|
||||
accumulate: function(state, ...values) {
|
||||
state.push(values);
|
||||
state.sort();
|
||||
return state;
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return s1.concat(s2);
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [
|
||||
{_id: 1, value: [['A', 'B', 'C'], ['A', 'B', 'C'], ['X', 'Y'], ['X', 'Y']]},
|
||||
{
|
||||
_id: 1,
|
||||
value: [
|
||||
["A", "B", "C"],
|
||||
["A", "B", "C"],
|
||||
["X", "Y"],
|
||||
["X", "Y"],
|
||||
],
|
||||
},
|
||||
];
|
||||
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
@ -309,36 +343,36 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
// to JS as usual.
|
||||
assert(db.accumulator_js.drop());
|
||||
assert.commandWorked(db.accumulator_js.insert({}));
|
||||
command.pipeline = [{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
return null;
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
return null;
|
||||
},
|
||||
accumulateArgs: [
|
||||
null,
|
||||
"$no_such_field",
|
||||
{$let: {vars: {not_an_object: 5}, in: "$not_an_object.field"}},
|
||||
],
|
||||
accumulate: function (state, ...values) {
|
||||
return {
|
||||
len: values.length,
|
||||
types: values.map((v) => typeof v),
|
||||
values: values,
|
||||
};
|
||||
},
|
||||
merge: function (s1, s2) {
|
||||
return s1 || s2;
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
accumulateArgs: [
|
||||
null,
|
||||
"$no_such_field",
|
||||
{$let: {vars: {not_an_object: 5}, in : "$not_an_object.field"}}
|
||||
],
|
||||
accumulate: function(state, ...values) {
|
||||
return {
|
||||
len: values.length,
|
||||
types: values.map(v => typeof v),
|
||||
values: values,
|
||||
};
|
||||
},
|
||||
merge: function(s1, s2) {
|
||||
return s1 || s2;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [
|
||||
{_id: 1, value: {len: 3, types: ['object', 'object', 'object'], values: [null, null, null]}},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [{_id: 1, value: {len: 3, types: ["object", "object", "object"], values: [null, null, null]}}];
|
||||
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
@ -9,12 +9,14 @@ function runExample(groupKey, accumulatorSpec, aggregateOptions = {}) {
|
||||
const aggregateCmd = {
|
||||
aggregate: coll.getName(),
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: groupKey,
|
||||
accumulatedField: {$accumulator: accumulatorSpec},
|
||||
}
|
||||
}]
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: groupKey,
|
||||
accumulatedField: {$accumulator: accumulatorSpec},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
return coll.runCommand(Object.assign(aggregateCmd, aggregateOptions));
|
||||
}
|
||||
@ -23,20 +25,20 @@ function runExample(groupKey, accumulatorSpec, aggregateOptions = {}) {
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({}));
|
||||
let res = runExample(1, {
|
||||
init: function() {
|
||||
init: function () {
|
||||
return "a".repeat(20 * 1024 * 1024);
|
||||
},
|
||||
accumulate: function() {
|
||||
throw 'accumulate should not be called';
|
||||
accumulate: function () {
|
||||
throw "accumulate should not be called";
|
||||
},
|
||||
accumulateArgs: [],
|
||||
merge: function() {
|
||||
throw 'merge should not be called';
|
||||
merge: function () {
|
||||
throw "merge should not be called";
|
||||
},
|
||||
finalize: function() {
|
||||
throw 'finalize should not be called';
|
||||
finalize: function () {
|
||||
throw "finalize should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
});
|
||||
assert.commandFailedWithCode(res, [16493, 10334]);
|
||||
|
||||
@ -44,21 +46,21 @@ assert.commandFailedWithCode(res, [16493, 10334]);
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({}));
|
||||
res = runExample(1, {
|
||||
init: function() {
|
||||
init: function () {
|
||||
const str = "a".repeat(1 * 1024 * 1024);
|
||||
return Array.from({length: 20}, () => str);
|
||||
},
|
||||
accumulate: function() {
|
||||
throw 'accumulate should not be called';
|
||||
accumulate: function () {
|
||||
throw "accumulate should not be called";
|
||||
},
|
||||
accumulateArgs: [],
|
||||
merge: function() {
|
||||
throw 'merge should not be called';
|
||||
merge: function () {
|
||||
throw "merge should not be called";
|
||||
},
|
||||
finalize: function() {
|
||||
throw 'finalize should not be called';
|
||||
finalize: function () {
|
||||
throw "finalize should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
});
|
||||
assert.commandFailedWithCode(res, [17260, 10334]);
|
||||
|
||||
@ -68,21 +70,21 @@ const oneMBString = "a".repeat(1 * 1024 * 1024);
|
||||
const tenMBArray = Array.from({length: 10}, () => oneMBString);
|
||||
assert.commandWorked(coll.insert([{arr: tenMBArray}, {arr: tenMBArray}]));
|
||||
res = runExample(1, {
|
||||
init: function() {
|
||||
init: function () {
|
||||
return [];
|
||||
},
|
||||
accumulate: function(state, input) {
|
||||
accumulate: function (state, input) {
|
||||
state.push(input);
|
||||
return state;
|
||||
},
|
||||
accumulateArgs: ["$arr"],
|
||||
merge: function(state1, state2) {
|
||||
merge: function (state1, state2) {
|
||||
return state1.concat(state2);
|
||||
},
|
||||
finalize: function() {
|
||||
throw 'finalize should not be called';
|
||||
finalize: function () {
|
||||
throw "finalize should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
});
|
||||
assert.commandFailedWithCode(res, [4545000]);
|
||||
|
||||
@ -91,30 +93,34 @@ assert(coll.drop());
|
||||
assert.commandWorked(coll.insert(Array.from({length: 200}, (_, i) => ({_id: i}))));
|
||||
// By grouping on _id, each group contains only 1 document. This means it creates many
|
||||
// AccumulatorState instances.
|
||||
res = runExample("$_id",
|
||||
{
|
||||
init: function() {
|
||||
// Each accumulator state is big enough to be expensive, but not big enough
|
||||
// to hit the BSON size limit.
|
||||
return "a".repeat(1 * 1024 * 1024);
|
||||
},
|
||||
accumulate: function(state) {
|
||||
return state;
|
||||
},
|
||||
accumulateArgs: [1],
|
||||
merge: function(state1, state2) {
|
||||
return state1;
|
||||
},
|
||||
finalize: function(state) {
|
||||
return state.length;
|
||||
},
|
||||
lang: 'js',
|
||||
},
|
||||
{allowDiskUse: false});
|
||||
res = runExample(
|
||||
"$_id",
|
||||
{
|
||||
init: function () {
|
||||
// Each accumulator state is big enough to be expensive, but not big enough
|
||||
// to hit the BSON size limit.
|
||||
return "a".repeat(1 * 1024 * 1024);
|
||||
},
|
||||
accumulate: function (state) {
|
||||
return state;
|
||||
},
|
||||
accumulateArgs: [1],
|
||||
merge: function (state1, state2) {
|
||||
return state1;
|
||||
},
|
||||
finalize: function (state) {
|
||||
return state.length;
|
||||
},
|
||||
lang: "js",
|
||||
},
|
||||
{allowDiskUse: false},
|
||||
);
|
||||
// If featureFlagShardFilteringDistinctScan is on, we will push this $group down to shards on
|
||||
// sharded collection passthrough suites, and may run out of space during JS execution of init().
|
||||
assert.commandFailedWithCode(
|
||||
res, [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.JSInterpreterFailure]);
|
||||
assert.commandFailedWithCode(res, [
|
||||
ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
ErrorCodes.JSInterpreterFailure,
|
||||
]);
|
||||
|
||||
// Verify that having large number of documents doesn't cause the $accumulator to run out of memory.
|
||||
coll.drop();
|
||||
@ -122,38 +128,44 @@ assert.commandWorked(coll.insert({groupBy: 1, largeField: "a".repeat(1000)}));
|
||||
assert.commandWorked(coll.insert({groupBy: 2, largeField: "a".repeat(1000)}));
|
||||
const largeAccumulator = {
|
||||
$accumulator: {
|
||||
init: function() {
|
||||
init: function () {
|
||||
return "";
|
||||
},
|
||||
accumulateArgs: [{fieldName: "$a"}],
|
||||
accumulate: function(state, args) {
|
||||
accumulate: function (state, args) {
|
||||
return state + "a";
|
||||
},
|
||||
merge: function(state1, state2) {
|
||||
merge: function (state1, state2) {
|
||||
return state1 + state2;
|
||||
},
|
||||
finalize: function(state) {
|
||||
finalize: function (state) {
|
||||
return state.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
res = coll.aggregate([
|
||||
{$addFields: {a: {$range: [0, 1000000]}}},
|
||||
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
|
||||
{$group: {_id: "$groupBy", count: largeAccumulator}}
|
||||
])
|
||||
.toArray();
|
||||
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
|
||||
res = coll
|
||||
.aggregate([
|
||||
{$addFields: {a: {$range: [0, 1000000]}}},
|
||||
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
|
||||
{$group: {_id: "$groupBy", count: largeAccumulator}},
|
||||
])
|
||||
.toArray();
|
||||
assert.sameMembers(res, [
|
||||
{_id: 1, count: 1000000},
|
||||
{_id: 2, count: 1000000},
|
||||
]);
|
||||
|
||||
// With $bucket.
|
||||
res =
|
||||
coll.aggregate([
|
||||
{$addFields: {a: {$range: [0, 1000000]}}},
|
||||
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
|
||||
{
|
||||
$bucket:
|
||||
{groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}}
|
||||
}
|
||||
])
|
||||
.toArray();
|
||||
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
|
||||
res = coll
|
||||
.aggregate([
|
||||
{$addFields: {a: {$range: [0, 1000000]}}},
|
||||
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
|
||||
{
|
||||
$bucket: {groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.sameMembers(res, [
|
||||
{_id: 1, count: 1000000},
|
||||
{_id: 2, count: 1000000},
|
||||
]);
|
||||
|
||||
@ -5,30 +5,30 @@ var coll = db.accumulate_avg_sum_null;
|
||||
coll.drop();
|
||||
|
||||
// Null cases.
|
||||
assert.commandWorked(coll.insert({a: 1, b: 2, c: 'string', d: null}));
|
||||
assert.commandWorked(coll.insert({a: 1, b: 2, c: "string", d: null}));
|
||||
|
||||
// Missing field.
|
||||
var pipeline = [{$group: {_id: '$a', avg: {$avg: '$missing'}}}];
|
||||
var pipeline = [{$group: {_id: "$a", avg: {$avg: "$missing"}}}];
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
|
||||
|
||||
// Non-numeric field.
|
||||
pipeline = [{$group: {_id: '$a', avg: {$avg: '$c'}}}];
|
||||
pipeline = [{$group: {_id: "$a", avg: {$avg: "$c"}}}];
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
|
||||
|
||||
// Field with value of null.
|
||||
pipeline = [{$group: {_id: '$a', avg: {$avg: '$d'}}}];
|
||||
pipeline = [{$group: {_id: "$a", avg: {$avg: "$d"}}}];
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
|
||||
|
||||
// All three.
|
||||
coll.insert({a: 1, d: 'string'});
|
||||
coll.insert({a: 1, d: "string"});
|
||||
coll.insert({a: 1});
|
||||
pipeline = [{$group: {_id: '$a', avg: {$avg: '$d'}}}];
|
||||
pipeline = [{$group: {_id: "$a", avg: {$avg: "$d"}}}];
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: null}]);
|
||||
|
||||
// Non-null cases.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({a: 1, b: 2}));
|
||||
pipeline = [{$group: {_id: '$a', avg: {$avg: '$b'}}}];
|
||||
pipeline = [{$group: {_id: "$a", avg: {$avg: "$b"}}}];
|
||||
|
||||
// One field.
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 2}]);
|
||||
@ -43,6 +43,6 @@ assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
|
||||
|
||||
// Missing, null, or non-numeric fields should not error or affect the average.
|
||||
assert.commandWorked(coll.insert({a: 1}));
|
||||
assert.commandWorked(coll.insert({a: 1, b: 'string'}));
|
||||
assert.commandWorked(coll.insert({a: 1, b: "string"}));
|
||||
assert.commandWorked(coll.insert({a: 1, b: null}));
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
|
||||
assert.eq(coll.aggregate(pipeline).toArray(), [{_id: 1, avg: 0}]);
|
||||
|
||||
@ -15,55 +15,50 @@ coll.insertMany(docs);
|
||||
{
|
||||
// One bucket, all the documents coming out of the $bucketAuto stage should be in the same
|
||||
// order as they came in (i.e. sorted by _id).
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}}
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{
|
||||
"_id": {"min": 0, "max": 9},
|
||||
"nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42, 5, 42, 6, 42, 7, 42, 8, 42, 9, 42]
|
||||
}];
|
||||
assert.eq(
|
||||
res,
|
||||
expected,
|
||||
"$bucketAuto with 1 bucket & $concatArrays does not obey sort order of incoming docs.");
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [
|
||||
{
|
||||
"_id": {"min": 0, "max": 9},
|
||||
"nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42, 5, 42, 6, 42, 7, 42, 8, 42, 9, 42],
|
||||
},
|
||||
];
|
||||
assert.eq(res, expected, "$bucketAuto with 1 bucket & $concatArrays does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Two buckets, the documents in each bucket should be sorted by _id as well.
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}}
|
||||
])
|
||||
.toArray();
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [
|
||||
{"_id": {"min": 0, "max": 5}, "nums": [5, 42, 6, 42, 7, 42, 8, 42, 9, 42]},
|
||||
{"_id": {"min": 5, "max": 9}, "nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42]}
|
||||
{"_id": {"min": 5, "max": 9}, "nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42]},
|
||||
];
|
||||
assert.eq(
|
||||
res,
|
||||
expected,
|
||||
"$bucketAuto with 2 buckets & $concatArrays does not obey sort order of incoming docs.");
|
||||
assert.eq(res, expected, "$bucketAuto with 2 buckets & $concatArrays does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure that if we try to $concatArrays something that evaluates to 'missing', no value gets
|
||||
// appended to the resulting array.
|
||||
const res =
|
||||
coll.aggregate([{
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{
|
||||
"$bucketAuto": {
|
||||
"groupBy": "$a",
|
||||
"buckets": 1,
|
||||
"output":
|
||||
{"array": {"$concatArrays": {$getField: {"field": "b", "input": {a: 0}}}}}
|
||||
}
|
||||
}])
|
||||
.toArray();
|
||||
"output": {"array": {"$concatArrays": {$getField: {"field": "b", "input": {a: 0}}}}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{"_id": {"min": null, "max": null}, "array": []}];
|
||||
assert.eq(res,
|
||||
expected,
|
||||
"$bucketAuto with $concatArrays does not append any missing values as null.");
|
||||
assert.eq(res, expected, "$bucketAuto with $concatArrays does not append any missing values as null.");
|
||||
}
|
||||
|
||||
@ -14,40 +14,34 @@ coll.insertMany(docs);
|
||||
{
|
||||
// One bucket, all the documents coming out of the $bucketAuto stage should be in the same
|
||||
// order as they came in (i.e. sorted by _id).
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{
|
||||
"_id": {"min": 0, "max": 9},
|
||||
"docs": [
|
||||
{"_id": 0, "n": 9},
|
||||
{"_id": 1, "n": 8},
|
||||
{"_id": 2, "n": 7},
|
||||
{"_id": 3, "n": 6},
|
||||
{"_id": 4, "n": 5},
|
||||
{"_id": 5, "n": 4},
|
||||
{"_id": 6, "n": 3},
|
||||
{"_id": 7, "n": 2},
|
||||
{"_id": 8, "n": 1},
|
||||
{"_id": 9, "n": 0}
|
||||
]
|
||||
}];
|
||||
assert.eq(res,
|
||||
expected,
|
||||
"$bucketAuto with 1 bucket & $push does not obey sort order of incoming docs.");
|
||||
const res = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}])
|
||||
.toArray();
|
||||
const expected = [
|
||||
{
|
||||
"_id": {"min": 0, "max": 9},
|
||||
"docs": [
|
||||
{"_id": 0, "n": 9},
|
||||
{"_id": 1, "n": 8},
|
||||
{"_id": 2, "n": 7},
|
||||
{"_id": 3, "n": 6},
|
||||
{"_id": 4, "n": 5},
|
||||
{"_id": 5, "n": 4},
|
||||
{"_id": 6, "n": 3},
|
||||
{"_id": 7, "n": 2},
|
||||
{"_id": 8, "n": 1},
|
||||
{"_id": 9, "n": 0},
|
||||
],
|
||||
},
|
||||
];
|
||||
assert.eq(res, expected, "$bucketAuto with 1 bucket & $push does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Two buckets, the documents in each bucket should be sorted by _id as well.
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}
|
||||
])
|
||||
.toArray();
|
||||
const res = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}])
|
||||
.toArray();
|
||||
const expected = [
|
||||
{
|
||||
"_id": {"min": 0, "max": 5},
|
||||
@ -56,8 +50,8 @@ coll.insertMany(docs);
|
||||
{"_id": 6, "n": 3},
|
||||
{"_id": 7, "n": 2},
|
||||
{"_id": 8, "n": 1},
|
||||
{"_id": 9, "n": 0}
|
||||
]
|
||||
{"_id": 9, "n": 0},
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id": {"min": 5, "max": 9},
|
||||
@ -66,27 +60,27 @@ coll.insertMany(docs);
|
||||
{"_id": 1, "n": 8},
|
||||
{"_id": 2, "n": 7},
|
||||
{"_id": 3, "n": 6},
|
||||
{"_id": 4, "n": 5}
|
||||
]
|
||||
}
|
||||
{"_id": 4, "n": 5},
|
||||
],
|
||||
},
|
||||
];
|
||||
assert.eq(res,
|
||||
expected,
|
||||
"$bucketAuto with 2 buckets & $push does not obey sort order of incoming docs.");
|
||||
assert.eq(res, expected, "$bucketAuto with 2 buckets & $push does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure that if we try to $push something that evaluates to 'missing', no value gets pushed to
|
||||
// the resulting array.
|
||||
const res =
|
||||
coll.aggregate([{
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{
|
||||
"$bucketAuto": {
|
||||
"groupBy": "$a",
|
||||
"buckets": 1,
|
||||
"output": {"array": {"$push": {$getField: {"field": "b", "input": {a: 0}}}}}
|
||||
}
|
||||
}])
|
||||
.toArray();
|
||||
"output": {"array": {"$push": {$getField: {"field": "b", "input": {a: 0}}}}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{"_id": {"min": null, "max": null}, "array": []}];
|
||||
assert.eq(res, expected, "$bucketAuto with $push does not push any missing values as null.");
|
||||
}
|
||||
|
||||
@ -9,141 +9,164 @@ assert(coll.drop());
|
||||
|
||||
// Test that $concatArrays correctly concatenates arrays, preserves sort order and preserves order
|
||||
// of the elements inside the concatenated arrays.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, author: "Adrian", publisher: "Pub1", books: ["Adrian Book 0", "Adrian Book 1"]},
|
||||
{_id: 1, author: "Militsa", publisher: "Pub1", books: ["Militsa Book 0"]},
|
||||
{_id: 2, author: "Hana", publisher: "Pub1", books: ["Hana Book 0", "Hana Book 1"]},
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, author: "Adrian", publisher: "Pub1", books: ["Adrian Book 0", "Adrian Book 1"]},
|
||||
{_id: 1, author: "Militsa", publisher: "Pub1", books: ["Militsa Book 0"]},
|
||||
{_id: 2, author: "Hana", publisher: "Pub1", books: ["Hana Book 0", "Hana Book 1"]},
|
||||
]),
|
||||
);
|
||||
|
||||
let result = coll.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 2]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
])
|
||||
.toArray();
|
||||
let result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 2]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(
|
||||
result, [{
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
allBooks:
|
||||
["Adrian Book 0", "Adrian Book 1", "Militsa Book 0", "Hana Book 0", "Hana Book 1"]
|
||||
}]);
|
||||
allBooks: ["Adrian Book 0", "Adrian Book 1", "Militsa Book 0", "Hana Book 0", "Hana Book 1"],
|
||||
},
|
||||
]);
|
||||
|
||||
// Redo the aggregation with the reverse sort to ensure sort order is really preserverd and we're
|
||||
// not just looking at the docs in the order they are found in the collection.
|
||||
result = coll.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 2]}}},
|
||||
{$sort: {_id: -1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 2]}}},
|
||||
{$sort: {_id: -1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(
|
||||
result, [{
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
allBooks:
|
||||
["Hana Book 0", "Hana Book 1", "Militsa Book 0", "Adrian Book 0", "Adrian Book 1"]
|
||||
}]);
|
||||
allBooks: ["Hana Book 0", "Hana Book 1", "Militsa Book 0", "Adrian Book 0", "Adrian Book 1"],
|
||||
},
|
||||
]);
|
||||
|
||||
// $concatArrays should concatenate arrays, and any nested arrays should remain array values.
|
||||
assert.commandWorked(coll.insert({
|
||||
_id: 3,
|
||||
author: "Ben",
|
||||
publisher: "Pub2",
|
||||
books:
|
||||
[["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"]
|
||||
}));
|
||||
result = coll.aggregate([
|
||||
{$match: {_id: {$in: [1, 3]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{
|
||||
_id: null,
|
||||
allBooks: [
|
||||
"Militsa Book 0",
|
||||
["Ben Series 0 Book 0", "Ben Series 0 Book 1"],
|
||||
["Ben Series 1 Book 0"],
|
||||
"Ben Book 4"
|
||||
]
|
||||
}]);
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 3,
|
||||
author: "Ben",
|
||||
publisher: "Pub2",
|
||||
books: [["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"],
|
||||
}),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [1, 3]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
allBooks: [
|
||||
"Militsa Book 0",
|
||||
["Ben Series 0 Book 0", "Ben Series 0 Book 1"],
|
||||
["Ben Series 1 Book 0"],
|
||||
"Ben Book 4",
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// $concatArrays should 'skip over' documents that do not have field.
|
||||
// Importantly, do not create a null element for documents that do not have the referenced field.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 4, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
|
||||
{_id: 5, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 6, author: "Matt", publisher: "Pub3", books: ["Happy!"]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{$match: {_id: {$in: [4, 5, 6]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
])
|
||||
.toArray();
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 4, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
|
||||
{_id: 5, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 6, author: "Matt", publisher: "Pub3", books: ["Happy!"]},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [4, 5, 6]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{_id: null, allBooks: ["Smile :)", "Happy!"]}]);
|
||||
|
||||
// Test for errors on non-array types ($concatArrays only supports arrays).
|
||||
const notArrays = [1, "string", {object: "object"}, null];
|
||||
|
||||
for (const notAnArray of notArrays) {
|
||||
assert.commandWorked(coll.insert([{
|
||||
_id: "doesNotMatter",
|
||||
author: "doesNotMatter",
|
||||
publisher: "doesNotMatter",
|
||||
books: notAnArray
|
||||
}]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{
|
||||
_id: "doesNotMatter",
|
||||
author: "doesNotMatter",
|
||||
publisher: "doesNotMatter",
|
||||
books: notAnArray,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
assertErrorCode(coll,
|
||||
[{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}],
|
||||
ErrorCodes.TypeMismatch);
|
||||
assertErrorCode(coll, [{$group: {_id: null, allBooks: {$concatArrays: "$books"}}}], ErrorCodes.TypeMismatch);
|
||||
|
||||
assert.commandWorked(coll.deleteOne({_id: "doesNotMatter"}));
|
||||
}
|
||||
|
||||
// Basic test of $concatArrays with grouping.
|
||||
result = coll.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 3]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$publisher', booksByPublisher: {$concatArrays: '$books'}}},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 3]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: "$publisher", booksByPublisher: {$concatArrays: "$books"}}},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [
|
||||
{_id: "Pub1", booksByPublisher: ["Adrian Book 0", "Adrian Book 1", "Militsa Book 0"]},
|
||||
{
|
||||
_id: "Pub2",
|
||||
booksByPublisher:
|
||||
[["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"]
|
||||
booksByPublisher: [["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"],
|
||||
},
|
||||
]);
|
||||
|
||||
// $concatArrays dotted field.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert(
|
||||
[{_id: 1, a: {b: [1, 2, 3]}}, {_id: 2, a: {b: [4, 5, 6]}}, {_id: 3, a: {b: [7, 8, 9]}}]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$concatArrays: '$a.b'}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: {b: [1, 2, 3]}},
|
||||
{_id: 2, a: {b: [4, 5, 6]}},
|
||||
{_id: 3, a: {b: [7, 8, 9]}},
|
||||
]),
|
||||
);
|
||||
result = coll.aggregate([{$sort: {_id: 1}}, {$group: {_id: null, nums: {$concatArrays: "$a.b"}}}]).toArray();
|
||||
assert.eq(result, [{_id: null, nums: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
|
||||
assert(coll.drop());
|
||||
|
||||
// $concatArrays dotted field, array halfway on path.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
|
||||
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$concatArrays: '$a.b'}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{_id: null, nums: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [7, 8, 9]]}]);
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
|
||||
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]},
|
||||
]),
|
||||
);
|
||||
result = coll.aggregate([{$sort: {_id: 1}}, {$group: {_id: null, nums: {$concatArrays: "$a.b"}}}]).toArray();
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
nums: [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[10, 11, 12],
|
||||
[7, 8, 9],
|
||||
],
|
||||
},
|
||||
]);
|
||||
assert(coll.drop());
|
||||
|
||||
// Basic correctness test for $concatArrays used in $bucket (see bucketAuto_concatArrays.js for
|
||||
@ -157,16 +180,20 @@ for (let i = 0; i < 10; i++) {
|
||||
}
|
||||
coll.insertMany(docs);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$bucket: {
|
||||
groupBy: '$_id',
|
||||
boundaries: [0, 5, 10],
|
||||
output: {nums: {$concatArrays: "$arr"}}
|
||||
}
|
||||
}
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{"_id": 0, "nums": [0, 1, 2, 3, 4]}, {"_id": 5, "nums": [5, 6, 7, 8, 9]}]);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$bucket: {
|
||||
groupBy: "$_id",
|
||||
boundaries: [0, 5, 10],
|
||||
output: {nums: {$concatArrays: "$arr"}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [
|
||||
{"_id": 0, "nums": [0, 1, 2, 3, 4]},
|
||||
{"_id": 5, "nums": [5, 6, 7, 8, 9]},
|
||||
]);
|
||||
assert(coll.drop());
|
||||
|
||||
@ -9,7 +9,7 @@ coll.drop();
|
||||
const sharded = FixtureHelpers.isSharded(coll);
|
||||
|
||||
const memoryLimitMB = sharded ? 200 : 100;
|
||||
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
|
||||
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
|
||||
|
||||
const bulk = coll.initializeUnorderedBulkOp();
|
||||
for (let i = 0; i < memoryLimitMB + 10; i++) {
|
||||
@ -21,18 +21,18 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
|
||||
|
||||
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
|
||||
// so may hit the group error code before we hit ExceededMemoryLimit.
|
||||
const pipeline =
|
||||
[{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$concatArrays: '$bigArr'}}}];
|
||||
const expectedCodes =
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
|
||||
const pipeline = [{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$concatArrays: "$bigArr"}}}];
|
||||
const expectedCodes = [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
|
||||
|
||||
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
|
||||
assert.commandFailedWithCode(
|
||||
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
|
||||
expectedCodes);
|
||||
expectedCodes,
|
||||
);
|
||||
|
||||
// The $concatArrays accumulator does not support spilling to disk, so ensure that it will fail even
|
||||
// when disk use is allowed.
|
||||
assert.commandFailedWithCode(
|
||||
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}),
|
||||
expectedCodes);
|
||||
expectedCodes,
|
||||
);
|
||||
|
||||
@ -14,12 +14,15 @@ let docId = 0;
|
||||
const kMaxSales = 20;
|
||||
let expectedFirstSales = [];
|
||||
let expectedLastSales = [];
|
||||
for (const states
|
||||
of [{state: 'AZ', sales: 3}, {state: 'CA', sales: 2}, {state: 'NY', sales: kMaxSales}]) {
|
||||
for (const states of [
|
||||
{state: "AZ", sales: 3},
|
||||
{state: "CA", sales: 2},
|
||||
{state: "NY", sales: kMaxSales},
|
||||
]) {
|
||||
let firstSales;
|
||||
let lastSales;
|
||||
const state = states['state'];
|
||||
const sales = states['sales'];
|
||||
const state = states["state"];
|
||||
const sales = states["sales"];
|
||||
for (let i = 0; i < kMaxSales && i < sales; ++i) {
|
||||
const salesAmt = i * 10;
|
||||
docs.push({_id: docId++, state: state, sales: salesAmt, stateObj: {"st": state}, n: 3});
|
||||
@ -36,51 +39,53 @@ for (const states
|
||||
|
||||
assert.commandWorked(coll.insert(docs));
|
||||
|
||||
{ // Test $first when grouping by a string.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$first: "$sales"}}},
|
||||
])
|
||||
.toArray();
|
||||
{
|
||||
// Test $first when grouping by a string.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$first: "$sales"}}}])
|
||||
.toArray();
|
||||
|
||||
assert(arrayEq(expectedFirstSales, actualResults),
|
||||
() => "expected " + tojson(expectedFirstSales) + " actual " + tojson(actualResults));
|
||||
assert(
|
||||
arrayEq(expectedFirstSales, actualResults),
|
||||
() => "expected " + tojson(expectedFirstSales) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $first when grouping by an object.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: "$stateObj", sales: {$first: "$sales"}}},
|
||||
])
|
||||
.toArray();
|
||||
{
|
||||
// Test $first when grouping by an object.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$stateObj", sales: {$first: "$sales"}}}])
|
||||
.toArray();
|
||||
|
||||
const expectedResult =
|
||||
expectedFirstSales.map(doc => ({'_id': {'st': doc['_id']}, sales: doc['sales']}));
|
||||
assert(arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults));
|
||||
const expectedResult = expectedFirstSales.map((doc) => ({"_id": {"st": doc["_id"]}, sales: doc["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $last when grouping by a string.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$last: "$sales"}}},
|
||||
])
|
||||
.toArray();
|
||||
assert(arrayEq(expectedLastSales, actualResults),
|
||||
() => "expected " + tojson(expectedLastSales) + " actual " + tojson(actualResults));
|
||||
{
|
||||
// Test $last when grouping by a string.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$last: "$sales"}}}])
|
||||
.toArray();
|
||||
assert(
|
||||
arrayEq(expectedLastSales, actualResults),
|
||||
() => "expected " + tojson(expectedLastSales) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $last when grouping by an object.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: "$stateObj", sales: {$last: "$sales"}}},
|
||||
])
|
||||
.toArray();
|
||||
{
|
||||
// Test $last when grouping by an object.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$stateObj", sales: {$last: "$sales"}}}])
|
||||
.toArray();
|
||||
|
||||
const expectedResult =
|
||||
expectedLastSales.map(doc => ({'_id': {'st': doc['_id']}, sales: doc['sales']}));
|
||||
assert(arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults));
|
||||
const expectedResult = expectedLastSales.map((doc) => ({"_id": {"st": doc["_id"]}, sales: doc["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
// Basic correctness test for $first/$last used in $bucketAuto. Though $bucketAuto uses
|
||||
@ -90,59 +95,61 @@ assert.commandWorked(coll.insert(docs));
|
||||
// buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
|
||||
{ // Test $first in $bucketAuto.
|
||||
let actualResults =
|
||||
coll.aggregate([
|
||||
{$sort: {sales: 1}},
|
||||
{$bucketAuto: {groupBy: '$state', buckets: 1, output: {sales: {$first: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
])
|
||||
.toArray();
|
||||
const expectedResults = [{_id: 'AZ', sales: 0}];
|
||||
assert(orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults));
|
||||
{
|
||||
// Test $first in $bucketAuto.
|
||||
let actualResults = coll
|
||||
.aggregate([
|
||||
{$sort: {sales: 1}},
|
||||
{$bucketAuto: {groupBy: "$state", buckets: 1, output: {sales: {$first: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
const expectedResults = [{_id: "AZ", sales: 0}];
|
||||
assert(
|
||||
orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $last in $bucketAuto.
|
||||
let actualResults =
|
||||
coll.aggregate([
|
||||
{$sort: {sales: 1}},
|
||||
{$bucketAuto: {groupBy: '$state', buckets: 1, output: {sales: {$last: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
])
|
||||
.toArray();
|
||||
const expectedResults = [{_id: 'AZ', sales: 190}];
|
||||
assert(orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults));
|
||||
{
|
||||
// Test $last in $bucketAuto.
|
||||
let actualResults = coll
|
||||
.aggregate([
|
||||
{$sort: {sales: 1}},
|
||||
{$bucketAuto: {groupBy: "$state", buckets: 1, output: {sales: {$last: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
const expectedResults = [{_id: "AZ", sales: 190}];
|
||||
assert(
|
||||
orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that an index on {_id: 1, sales: -1} will produce the expected results.
|
||||
const idxSpec = {
|
||||
_id: 1,
|
||||
sales: -1
|
||||
sales: -1,
|
||||
};
|
||||
assert.commandWorked(coll.createIndex(idxSpec));
|
||||
|
||||
{ // Test $first with index.
|
||||
const actualResults = coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$first: "$sales"}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
.toArray();
|
||||
{
|
||||
// Test $first with index.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$first: "$sales"}}}, {$sort: {_id: 1}}], {
|
||||
hint: idxSpec,
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(expectedFirstSales, actualResults);
|
||||
}
|
||||
|
||||
{ // Test $last with index.
|
||||
const actualResults = coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$last: "$sales"}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
.toArray();
|
||||
{
|
||||
// Test $last with index.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$last: "$sales"}}}, {$sort: {_id: 1}}], {
|
||||
hint: idxSpec,
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(expectedLastSales, actualResults);
|
||||
}
|
||||
|
||||
@ -8,9 +8,8 @@ import {arrayEq} from "jstests/aggregation/extras/utils.js";
|
||||
const coll = db[jsTestName()];
|
||||
coll.drop();
|
||||
|
||||
const largestInt =
|
||||
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
|
||||
// Basic correctness tests.
|
||||
let docs = [];
|
||||
@ -22,15 +21,18 @@ let expectedLastThree = [];
|
||||
let expectedAllResults = [];
|
||||
let expectedFirstNWithInitExpr = [];
|
||||
let expectedLastNWithInitExpr = [];
|
||||
for (const states
|
||||
of [{state: 'AZ', sales: 3}, {state: 'CA', sales: 2}, {state: 'NY', sales: kMaxSales}]) {
|
||||
for (const states of [
|
||||
{state: "AZ", sales: 3},
|
||||
{state: "CA", sales: 2},
|
||||
{state: "NY", sales: kMaxSales},
|
||||
]) {
|
||||
let allResults = [];
|
||||
let firstThree = [];
|
||||
let lastThree = [];
|
||||
let firstWithInitExpr = [];
|
||||
let lastWithInitExpr = [];
|
||||
const state = states['state'];
|
||||
const sales = states['sales'];
|
||||
const state = states["state"];
|
||||
const sales = states["sales"];
|
||||
for (let i = 0; i < kMaxSales; ++i) {
|
||||
const salesAmt = i * 10;
|
||||
if (i < sales) {
|
||||
@ -45,10 +47,10 @@ for (const states
|
||||
lastThree.push(salesAmt);
|
||||
}
|
||||
|
||||
if (i == 0 || (state == 'AZ' && i < defaultN)) {
|
||||
if (i == 0 || (state == "AZ" && i < defaultN)) {
|
||||
firstWithInitExpr.push(salesAmt);
|
||||
}
|
||||
if (i + 1 == sales || (state == 'AZ' && i + defaultN >= sales)) {
|
||||
if (i + 1 == sales || (state == "AZ" && i + defaultN >= sales)) {
|
||||
lastWithInitExpr.push(salesAmt);
|
||||
}
|
||||
allResults.push(salesAmt);
|
||||
@ -64,125 +66,121 @@ for (const states
|
||||
assert.commandWorked(coll.insert(docs));
|
||||
|
||||
function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
const actualFirstNResults =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
|
||||
])
|
||||
.toArray();
|
||||
const actualFirstNResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}}])
|
||||
.toArray();
|
||||
|
||||
// As these are unordered operators, we need to ensure we can deterministically test the values
|
||||
// returned by firstN/lastN. As the output is not guaranteed to be in order, arrayEq is used
|
||||
// instead.
|
||||
assert(arrayEq(expectedFirstNResults, actualFirstNResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " +
|
||||
tojson(actualFirstNResults));
|
||||
assert(
|
||||
arrayEq(expectedFirstNResults, actualFirstNResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " + tojson(actualFirstNResults),
|
||||
);
|
||||
|
||||
const firstNResultsWithInitExpr =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
sales: {
|
||||
$firstN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
const firstNResultsWithInitExpr = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
sales: {
|
||||
$firstN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
let expectedResult = [];
|
||||
expectedFirstNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
assert(arrayEq(expectedResult, firstNResultsWithInitExpr),
|
||||
() => "expected " + tojson(expectedResult) + " actual " +
|
||||
tojson(firstNResultsWithInitExpr));
|
||||
expectedFirstNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, firstNResultsWithInitExpr),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(firstNResultsWithInitExpr),
|
||||
);
|
||||
|
||||
const firstNResultsWithInitExprAndVariableGroupId =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: "$stateObj",
|
||||
sales: {
|
||||
$firstN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
const firstNResultsWithInitExprAndVariableGroupId = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: "$stateObj",
|
||||
sales: {
|
||||
$firstN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
expectedResult = [];
|
||||
expectedFirstNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
assert(arrayEq(expectedResult, firstNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " +
|
||||
tojson(firstNResultsWithInitExprAndVariableGroupId));
|
||||
expectedFirstNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, firstNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(firstNResultsWithInitExprAndVariableGroupId),
|
||||
);
|
||||
|
||||
const actualLastNResults =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$lastN: {input: "$sales", n: n}}}},
|
||||
])
|
||||
.toArray();
|
||||
const actualLastNResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$lastN: {input: "$sales", n: n}}}}])
|
||||
.toArray();
|
||||
assert(
|
||||
arrayEq(expectedLastNResults, actualLastNResults),
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNResults));
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNResults),
|
||||
);
|
||||
|
||||
const lastNResultsWithInitExpr =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
sales: {
|
||||
$lastN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
const lastNResultsWithInitExpr = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
sales: {
|
||||
$lastN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
expectedResult = [];
|
||||
expectedLastNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
expectedLastNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, lastNResultsWithInitExpr),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExpr));
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExpr),
|
||||
);
|
||||
|
||||
const lastNResultsWithInitExprAndVariableGroupId =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: "$stateObj",
|
||||
sales: {
|
||||
$lastN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
const lastNResultsWithInitExprAndVariableGroupId = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
_id: "$stateObj",
|
||||
sales: {
|
||||
$lastN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
expectedResult = [];
|
||||
expectedLastNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
assert(arrayEq(expectedResult, lastNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " +
|
||||
tojson(lastNResultsWithInitExprAndVariableGroupId));
|
||||
expectedLastNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, lastNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExprAndVariableGroupId),
|
||||
);
|
||||
|
||||
function reorderBucketResults(bucketResults) {
|
||||
// Using a computed projection will put the fields out of order. As such, we re-order them
|
||||
@ -199,67 +197,67 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
// to compare the $bucketAuto results to the expected $group results (because there are more
|
||||
// buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
let actualFirstNBucketAutoResults =
|
||||
coll.aggregate([
|
||||
{$sort: {state: 1, sales: 1}},
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {$firstN: {input: "$sales", n: n}}}
|
||||
}
|
||||
let actualFirstNBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{$sort: {state: 1, sales: 1}},
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {$firstN: {input: "$sales", n: n}}},
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
reorderBucketResults(actualFirstNBucketAutoResults);
|
||||
assert(arrayEq(expectedFirstNResults, actualFirstNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " +
|
||||
tojson(actualFirstNBucketAutoResults));
|
||||
assert(
|
||||
arrayEq(expectedFirstNResults, actualFirstNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " + tojson(actualFirstNBucketAutoResults),
|
||||
);
|
||||
|
||||
let actualLastNBucketAutoResults =
|
||||
coll.aggregate([
|
||||
{$sort: {state: 1, sales: 1}},
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {$lastN: {input: "$sales", n: n}}}
|
||||
}
|
||||
let actualLastNBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{$sort: {state: 1, sales: 1}},
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {$lastN: {input: "$sales", n: n}}},
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
reorderBucketResults(actualLastNBucketAutoResults);
|
||||
assert(arrayEq(expectedLastNResults, actualLastNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " +
|
||||
tojson(actualLastNBucketAutoResults));
|
||||
assert(
|
||||
arrayEq(expectedLastNResults, actualLastNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNBucketAutoResults),
|
||||
);
|
||||
|
||||
// Verify that an index on {_id: 1, sales: -1} will produce the expected results.
|
||||
const idxSpec = {_id: 1, sales: -1};
|
||||
assert.commandWorked(coll.createIndex(idxSpec));
|
||||
|
||||
const indexedFirstNResults =
|
||||
coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
.toArray();
|
||||
const indexedFirstNResults = coll
|
||||
.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(expectedFirstNResults, indexedFirstNResults);
|
||||
|
||||
const indexedLastNResults =
|
||||
coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$lastN: {input: "$sales", n: n}}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
.toArray();
|
||||
const indexedLastNResults = coll
|
||||
.aggregate(
|
||||
[{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$lastN: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}],
|
||||
{hint: idxSpec},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(expectedLastNResults, indexedLastNResults);
|
||||
}
|
||||
|
||||
@ -269,56 +267,67 @@ runFirstLastN(defaultN, expectedFirstThree, expectedLastThree);
|
||||
runFirstLastN(largestInt, expectedAllResults, expectedAllResults);
|
||||
|
||||
// Reject non-integral values of n.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: 'string'}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787902);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: "string"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787902,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: 3.2}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787903);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: 3.2}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: -1}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787908);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: -1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787908,
|
||||
);
|
||||
|
||||
// Verify that 'n' cannot be greater than the largest signed 64 bit int.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: largestIntPlus1}}}}
|
||||
],
|
||||
cursor: {}
|
||||
}),
|
||||
5787903);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: largestIntPlus1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903,
|
||||
);
|
||||
|
||||
// Reject invalid specifications.
|
||||
|
||||
// Extra fields
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: {'st': '$state'},
|
||||
sales: {$firstN: {input: '$sales', n: 2, randomField: "randomArg"}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787901);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
sales: {$firstN: {input: "$sales", n: 2, randomField: "randomArg"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
5787901,
|
||||
);
|
||||
|
||||
// Missing arguments.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales'}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787906);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787906,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand(
|
||||
"aggregate",
|
||||
{pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {n: 2}}}}], cursor: {}}),
|
||||
5787907);
|
||||
coll.runCommand("aggregate", {pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {n: 2}}}}], cursor: {}}),
|
||||
5787907,
|
||||
);
|
||||
|
||||
@ -22,24 +22,28 @@ const docs = [
|
||||
assert.commandWorked(coll.insertMany(docs));
|
||||
|
||||
function assertTypeMismatch(aggFunction, aggFunctionArgument) {
|
||||
const pipeline = [{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
}
|
||||
}];
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 9961600, aggFunction);
|
||||
}
|
||||
|
||||
function assertNoError(aggFunction, aggFunctionArgument) {
|
||||
const pipeline = [{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
}
|
||||
}];
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
|
||||
}
|
||||
|
||||
|
||||
@ -28,13 +28,15 @@ const docs = [
|
||||
assert.commandWorked(coll.insertMany(docs));
|
||||
|
||||
function assertNoError(aggFunction, aggFunctionArgument) {
|
||||
const pipeline = [{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
}
|
||||
}];
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
|
||||
}
|
||||
|
||||
|
||||
@ -17,24 +17,26 @@ function reduce(key, values) {
|
||||
return Array.sum(values);
|
||||
}
|
||||
|
||||
let groupPipe = [{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCount: {
|
||||
$_internalJsReduce: {
|
||||
data: {k: "$word", v: "$val"},
|
||||
eval: reduce,
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
let groupPipe = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCount: {
|
||||
$_internalJsReduce: {
|
||||
data: {k: "$word", v: "$val"},
|
||||
eval: reduce,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let command = {
|
||||
aggregate: 'js_reduce',
|
||||
aggregate: "js_reduce",
|
||||
cursor: {},
|
||||
pipeline: groupPipe,
|
||||
allowDiskUse: true // Set allowDiskUse to true to force the expression to run on a shard in the
|
||||
// passthrough suites, where javascript execution is supported.
|
||||
allowDiskUse: true, // Set allowDiskUse to true to force the expression to run on a shard in the
|
||||
// passthrough suites, where javascript execution is supported.
|
||||
};
|
||||
|
||||
const expectedResults = [
|
||||
@ -100,17 +102,17 @@ assert.commandFailedWithCode(db.runCommand(command), 31242);
|
||||
|
||||
groupPipe[0].$group.wordCount.$_internalJsReduce = {
|
||||
notEval: 1,
|
||||
notData: 1
|
||||
notData: 1,
|
||||
};
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31243);
|
||||
|
||||
groupPipe[0].$group.wordCount.$_internalJsReduce = {
|
||||
eval: reduce,
|
||||
data: {v: 1}
|
||||
data: {v: 1},
|
||||
};
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31251);
|
||||
groupPipe[0].$group.wordCount.$_internalJsReduce.data = {
|
||||
key: 1,
|
||||
value: 1
|
||||
value: 1,
|
||||
};
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31251);
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31251);
|
||||
|
||||
@ -27,19 +27,20 @@ function reduce(key, values) {
|
||||
const command = {
|
||||
aggregate: coll.getName(),
|
||||
cursor: {},
|
||||
runtimeConstants:
|
||||
{localNow: new Date(), clusterTime: new Timestamp(0, 0), jsScope: {modulus: modulus}},
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCountMod: {
|
||||
$_internalJsReduce: {
|
||||
data: {k: "$word", v: "$val"},
|
||||
eval: reduce,
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
runtimeConstants: {localNow: new Date(), clusterTime: new Timestamp(0, 0), jsScope: {modulus: modulus}},
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCountMod: {
|
||||
$_internalJsReduce: {
|
||||
data: {k: "$word", v: "$val"},
|
||||
eval: reduce,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fromRouter: true,
|
||||
};
|
||||
|
||||
|
||||
@ -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",
|
||||
});
|
||||
|
||||
@ -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",
|
||||
});
|
||||
|
||||
@ -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",
|
||||
});
|
||||
|
||||
@ -10,18 +10,17 @@ coll.drop();
|
||||
let docs = [];
|
||||
const n = 4;
|
||||
|
||||
const largestInt =
|
||||
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
|
||||
// Some big number that will allow us to test big without running this testcase into next decade.
|
||||
const bigN = 10000;
|
||||
|
||||
const states = [
|
||||
{state: 'CA', sales: 10},
|
||||
{state: 'NY', sales: 7},
|
||||
{state: 'TX', sales: 4},
|
||||
{state: 'WY', sales: bigN}
|
||||
{state: "CA", sales: 10},
|
||||
{state: "NY", sales: 7},
|
||||
{state: "TX", sales: 4},
|
||||
{state: "WY", sales: bigN},
|
||||
];
|
||||
let expectedMinNResults = [];
|
||||
let expectedMaxNResults = [];
|
||||
@ -31,8 +30,8 @@ let expectedBigNMinNResults = [];
|
||||
let expectedBigNMaxNResults = [];
|
||||
|
||||
for (const stateDoc of states) {
|
||||
const state = stateDoc['state'];
|
||||
const sales = stateDoc['sales'];
|
||||
const state = stateDoc["state"];
|
||||
const sales = stateDoc["sales"];
|
||||
let minArr = [];
|
||||
let maxArr = [];
|
||||
let minArrForLargestInt = [];
|
||||
@ -80,12 +79,9 @@ assert.commandWorked(coll.insert(docs));
|
||||
// Note that the output documents are sorted by '_id' so that we can compare actual groups against
|
||||
// expected groups (we cannot perform unordered comparison because order matters for $minN/$maxN).
|
||||
function runAndCompareMinMaxN(nFunction, n, expectedResults) {
|
||||
const actualResults =
|
||||
coll.aggregate([
|
||||
{$group: {_id: '$state', sales: {[nFunction]: {input: '$sales', n: n}}}},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray();
|
||||
const actualResults = coll
|
||||
.aggregate([{$group: {_id: "$state", sales: {[nFunction]: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}])
|
||||
.toArray();
|
||||
assert.eq(expectedResults, actualResults);
|
||||
|
||||
// Basic correctness test for $minN/$maxN used in $bucketAuto. Though $bucketAuto uses
|
||||
@ -94,19 +90,19 @@ function runAndCompareMinMaxN(nFunction, n, expectedResults) {
|
||||
// to compare the $bucketAuto results to the expected $group results (because there are more
|
||||
// buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
let actualBucketAutoResults =
|
||||
coll.aggregate([
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {[nFunction]: {input: '$sales', n: n}}}
|
||||
}
|
||||
let actualBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {[nFunction]: {input: "$sales", n: n}}},
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
// Using a computed projection will put the fields out of order. As such, we re-order them
|
||||
// below.
|
||||
@ -131,23 +127,25 @@ runAndCompareMinMaxN("$maxN", bigN - 1, expectedBigNMaxNResults);
|
||||
|
||||
// Verify that we can dynamically compute 'n' based on the group key for $group.
|
||||
const groupKeyNExpr = {
|
||||
$cond: {if: {$eq: ['$st', 'CA']}, then: 10, else: 4}
|
||||
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4},
|
||||
};
|
||||
const dynamicMinNResults =
|
||||
coll.aggregate([{
|
||||
$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
|
||||
}])
|
||||
.toArray();
|
||||
const dynamicMinNResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: groupKeyNExpr}}},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
// Verify that the 'CA' group has 10 results, while all others have only 4.
|
||||
for (const result of dynamicMinNResults) {
|
||||
assert(result.hasOwnProperty('_id'), tojson(result));
|
||||
const groupKey = result['_id'];
|
||||
assert(groupKey.hasOwnProperty('st'), tojson(groupKey));
|
||||
const state = groupKey['st'];
|
||||
assert(result.hasOwnProperty('minSales'), tojson(result));
|
||||
const salesArray = result['minSales'];
|
||||
if (state === 'CA') {
|
||||
assert(result.hasOwnProperty("_id"), tojson(result));
|
||||
const groupKey = result["_id"];
|
||||
assert(groupKey.hasOwnProperty("st"), tojson(groupKey));
|
||||
const state = groupKey["st"];
|
||||
assert(result.hasOwnProperty("minSales"), tojson(result));
|
||||
const salesArray = result["minSales"];
|
||||
if (state === "CA") {
|
||||
assert.eq(salesArray.length, 10, tojson(salesArray));
|
||||
} else {
|
||||
assert.eq(salesArray.length, 4, tojson(salesArray));
|
||||
@ -157,75 +155,94 @@ for (const result of dynamicMinNResults) {
|
||||
// Error cases
|
||||
|
||||
// Cannot reference the group key in $minN when using $bucketAuto.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 2,
|
||||
output: {minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
}),
|
||||
4544714);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 2,
|
||||
output: {minSales: {$minN: {input: "$sales", n: groupKeyNExpr}}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
4544714,
|
||||
);
|
||||
|
||||
// Reject non-integral/negative values of n.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline:
|
||||
[{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 'string'}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787902);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: "string"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787902,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 3.2}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787903);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: 3.2}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: -1}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787908);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: -1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787908,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 0}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787908);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: 0}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787908,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: largestIntPlus1}}}}
|
||||
],
|
||||
cursor: {}
|
||||
}),
|
||||
5787903);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: largestIntPlus1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903,
|
||||
);
|
||||
|
||||
// Reject invalid specifications.
|
||||
|
||||
// Missing arguments.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales'}}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787906);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787906,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand(
|
||||
"aggregate",
|
||||
{pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {n: 2}}}}], cursor: {}}),
|
||||
5787907);
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {n: 2}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787907,
|
||||
);
|
||||
|
||||
// Extra field.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: {'st': '$state'},
|
||||
minSales: {$minN: {input: '$sales', n: 2, randomField: "randomArg"}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787901);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
minSales: {$minN: {input: "$sales", n: 2, randomField: "randomArg"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
5787901,
|
||||
);
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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",
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -13,7 +13,7 @@ coll.drop();
|
||||
coll.insert({x: 42});
|
||||
|
||||
function assertInvalidSyntax({pSpec, letSpec, errorCode, msg}) {
|
||||
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let : letSpec, cursor: {}};
|
||||
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let: letSpec, cursor: {}};
|
||||
if (errorCode) {
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", command), errorCode, msg);
|
||||
} else {
|
||||
@ -22,34 +22,33 @@ function assertInvalidSyntax({pSpec, letSpec, errorCode, msg}) {
|
||||
}
|
||||
|
||||
function assertValidSyntax({pSpec, letSpec, msg}) {
|
||||
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let : letSpec, cursor: {}};
|
||||
let command = {pipeline: [{$group: {_id: null, p: pSpec}}], let: letSpec, cursor: {}};
|
||||
assert.commandWorked(coll.runCommand("aggregate", command), msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test missing or unexpected fields in $percentile spec.
|
||||
*/
|
||||
assertInvalidSyntax(
|
||||
{pSpec: {$percentile: 0.5}, msg: "Should fail if $percentile is not an object"});
|
||||
assertInvalidSyntax({pSpec: {$percentile: 0.5}, msg: "Should fail if $percentile is not an object"});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if $percentile is missing 'p' field"
|
||||
msg: "Should fail if $percentile is missing 'p' field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5], method: "approximate"}},
|
||||
msg: "Should fail if $percentile is missing 'input' field"
|
||||
msg: "Should fail if $percentile is missing 'input' field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5], input: "$x"}},
|
||||
msg: "Should fail if $percentile is missing 'method' field"
|
||||
msg: "Should fail if $percentile is missing 'method' field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5], input: "$x", method: "approximate", extras: 42}},
|
||||
msg: "Should fail if $percentile contains an unexpected field"
|
||||
msg: "Should fail if $percentile contains an unexpected field",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -57,23 +56,22 @@ assertInvalidSyntax({
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: 0.5, input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if 'p' field in $percentile isn't array"
|
||||
msg: "Should fail if 'p' field in $percentile isn't array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [], input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if 'p' field in $percentile is an empty array"
|
||||
msg: "Should fail if 'p' field in $percentile is an empty array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, "foo"], input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if 'p' field in $percentile is an array with a non-numeric element"
|
||||
msg: "Should fail if 'p' field in $percentile is an array with a non-numeric element",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 10], input: "$x", method: "approximate"}},
|
||||
msg:
|
||||
"Should fail if 'p' field in $percentile is an array with any value outside of [0, 1] range"
|
||||
msg: "Should fail if 'p' field in $percentile is an array with any value outside of [0, 1] range",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -81,32 +79,31 @@ assertInvalidSyntax({
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: ["$x"], input: "$x", method: "approximate"}},
|
||||
msg: "'p' should not accept non-const expressions"
|
||||
msg: "'p' should not accept non-const expressions",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: {$add: [0.1, 0.5]}, input: "$x", method: "approximate"}},
|
||||
msg: "'p' should not accept expressions that evaluate to a non-array"
|
||||
msg: "'p' should not accept expressions that evaluate to a non-array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {
|
||||
$percentile:
|
||||
{p: {$concatArrays: [[0.01, 0.1], ["foo"]]}, input: "$x", method: "approximate"}
|
||||
$percentile: {p: {$concatArrays: [[0.01, 0.1], ["foo"]]}, input: "$x", method: "approximate"},
|
||||
},
|
||||
msg: "'p' should not accept expressions that evaluate to an array with non-numeric elements"
|
||||
msg: "'p' should not accept expressions that evaluate to an array with non-numeric elements",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
|
||||
letSpec: {pvals: 0.5},
|
||||
msg: "'p' should not accept variables that evaluate to a non-array"
|
||||
msg: "'p' should not accept variables that evaluate to a non-array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
|
||||
letSpec: {pvals: [0.5, "foo"]},
|
||||
msg: "'p' should not accept variables that evaluate to an array with non-numeric elements"
|
||||
msg: "'p' should not accept variables that evaluate to an array with non-numeric elements",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -114,37 +111,36 @@ assertInvalidSyntax({
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: 42}},
|
||||
msg: "$percentile should fail if 'method' field isn't a string"
|
||||
msg: "$percentile should fail if 'method' field isn't a string",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "fancy"}},
|
||||
msg: "$percentile should fail if 'method' isn't one of _predefined_ strings"
|
||||
msg: "$percentile should fail if 'method' isn't one of _predefined_ strings",
|
||||
});
|
||||
|
||||
if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "discrete"}},
|
||||
msg: "Should work with discrete 'method'"
|
||||
msg: "Should work with discrete 'method'",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.InternalErrorNotSupported,
|
||||
msg: "Should work with continuous 'method'"
|
||||
msg: "Should work with continuous 'method'",
|
||||
});
|
||||
|
||||
} else {
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "discrete"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$percentile should fail because discrete 'method' isn't supported yet"
|
||||
msg: "$percentile should fail because discrete 'method' isn't supported yet",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$percentile should fail because continuous 'method' isn't supported yet"
|
||||
msg: "$percentile should fail because continuous 'method' isn't supported yet",
|
||||
});
|
||||
}
|
||||
|
||||
@ -153,50 +149,48 @@ if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {p: [0.5], input: "$x", method: "approximate"}},
|
||||
msg: "$median should fail if 'p' is defined"
|
||||
msg: "$median should fail if 'p' is defined",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {method: "approximate"}},
|
||||
msg: "$median should fail if 'input' field is missing"
|
||||
msg: "$median should fail if 'input' field is missing",
|
||||
});
|
||||
|
||||
assertInvalidSyntax(
|
||||
{pSpec: {$median: {input: "$x"}}, msg: "Median should fail if 'method' field is missing"});
|
||||
assertInvalidSyntax({pSpec: {$median: {input: "$x"}}, msg: "Median should fail if 'method' field is missing"});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "approximate", extras: 42}},
|
||||
msg: "$median should fail if there is an unexpected field"
|
||||
msg: "$median should fail if there is an unexpected field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "fancy"}},
|
||||
msg: "$median should fail if 'method' isn't one of the _predefined_ strings"
|
||||
msg: "$median should fail if 'method' isn't one of the _predefined_ strings",
|
||||
});
|
||||
|
||||
if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
assertValidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "discrete"}},
|
||||
msg: "Should work with discrete 'method'"
|
||||
msg: "Should work with discrete 'method'",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.InternalErrorNotSupported,
|
||||
msg: "Should work with continuous 'method'"
|
||||
msg: "Should work with continuous 'method'",
|
||||
});
|
||||
|
||||
} else {
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "discrete"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$median should fail because discrete 'method' isn't supported yet"
|
||||
msg: "$median should fail because discrete 'method' isn't supported yet",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$median should fail because continuous 'method' isn't supported yet"
|
||||
msg: "$median should fail because continuous 'method' isn't supported yet",
|
||||
});
|
||||
}
|
||||
|
||||
@ -207,58 +201,65 @@ if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
*/
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.0, 0.0001, 0.5, 0.995, 1.0], input: "$x", method: "approximate"}},
|
||||
msg: "Should be able to specify an array of percentiles"
|
||||
msg: "Should be able to specify an array of percentiles",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.9], input: {$divide: ["$x", 2]}, method: "approximate"}},
|
||||
msg: "Should be able to specify 'input' as an expression"
|
||||
msg: "Should be able to specify 'input' as an expression",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.9], input: "x", method: "approximate"}},
|
||||
msg: "Non-numeric inputs should be gracefully ignored"
|
||||
msg: "Non-numeric inputs should be gracefully ignored",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.9], input: {$add: [2, "$x"]}, method: "approximate"}},
|
||||
msg: "'input' should be able to use expressions"
|
||||
msg: "'input' should be able to use expressions",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {
|
||||
$percentile: {p: [0.5, 0.9], input: {$concatArrays: [[2], ["$x"]]}, method: "approximate"}
|
||||
$percentile: {p: [0.5, 0.9], input: {$concatArrays: [[2], ["$x"]]}, method: "approximate"},
|
||||
},
|
||||
msg: "'input' should be able to use expressions even if the result of their eval is non-numeric"
|
||||
msg: "'input' should be able to use expressions even if the result of their eval is non-numeric",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {
|
||||
$percentile:
|
||||
{p: {$concatArrays: [[0.01, 0.1], [0.9, 0.99]]}, input: "$x", method: "approximate"}
|
||||
$percentile: {
|
||||
p: {
|
||||
$concatArrays: [
|
||||
[0.01, 0.1],
|
||||
[0.9, 0.99],
|
||||
],
|
||||
},
|
||||
input: "$x",
|
||||
method: "approximate",
|
||||
},
|
||||
},
|
||||
msg: "'p' should be able to use expressions that evaluate to an array"
|
||||
msg: "'p' should be able to use expressions that evaluate to an array",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [{$add: [0.1, 0.5]}], input: "$x", method: "approximate"}},
|
||||
msg: "'p' should be able to use expressions for the array elements"
|
||||
msg: "'p' should be able to use expressions for the array elements",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
|
||||
letSpec: {pvals: [0.5, 0.9]},
|
||||
msg: "'p' should be able to use variables for the array"
|
||||
msg: "'p' should be able to use variables for the array",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: ["$$p1", "$$p2"], input: "$x", method: "approximate"}},
|
||||
letSpec: {p1: 0.5, p2: 0.9},
|
||||
msg: "'p' should be able to use variables for the array elements"
|
||||
msg: "'p' should be able to use variables for the array elements",
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests for valid $median.
|
||||
*/
|
||||
assertValidSyntax(
|
||||
{pSpec: {$median: {input: "$x", method: "approximate"}}, msg: "Simple base case for $median."});
|
||||
assertValidSyntax({pSpec: {$median: {input: "$x", method: "approximate"}}, msg: "Simple base case for $median."});
|
||||
|
||||
@ -10,13 +10,19 @@ assert(coll.drop());
|
||||
// Test that $setUnion produces a single array containing all the unique values from the input
|
||||
// arrays. As $setUnion does not provide guarantees on ordering, the result is sorted for easy
|
||||
// comparison.
|
||||
assert.commandWorked(coll.insert([{_id: 0, nums: [1, 2, 3]}, {_id: 1, nums: [4, 5, 6]}]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, nums: [1, 2, 3]},
|
||||
{_id: 1, nums: [4, 5, 6]},
|
||||
]),
|
||||
);
|
||||
|
||||
let result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$nums'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
let result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$nums"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6]}]);
|
||||
|
||||
@ -24,14 +30,20 @@ assert(coll.drop());
|
||||
|
||||
// Test that $setUnion deduplicates the values. That is, each unique value will only appear once in
|
||||
// the ouput of the accumulation.
|
||||
assert.commandWorked(coll.insert(
|
||||
[{_id: 0, nums: [1, 2, 3]}, {_id: 1, nums: [2, 3, 4]}, {_id: 2, nums: [3, 4, 5, 6]}]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, nums: [1, 2, 3]},
|
||||
{_id: 1, nums: [2, 3, 4]},
|
||||
{_id: 2, nums: [3, 4, 5, 6]},
|
||||
]),
|
||||
);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$nums'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$nums"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6]}]);
|
||||
|
||||
@ -41,28 +53,32 @@ assert(coll.drop());
|
||||
// value will only appear once in the ouput of the accumulation.
|
||||
assert.commandWorked(coll.insert([{_id: 0, nums: [1, 1, 2, 2, 3, 3]}]));
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$nums'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$nums"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3]}]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
// Nested arrays should still be arrays in the result of $setUnion.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, vals: [["nested"], 1, 2]},
|
||||
{_id: 1, vals: [3, 4, ["nested"]]},
|
||||
{_id: 2, vals: [4, ["nested", "extra"]]}
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, vals: [["nested"], 1, 2]},
|
||||
{_id: 1, vals: [3, 4, ["nested"]]},
|
||||
{_id: 2, vals: [4, ["nested", "extra"]]},
|
||||
]),
|
||||
);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$vals'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$vals"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, ["nested"], ["nested", "extra"]]}]);
|
||||
|
||||
@ -70,67 +86,100 @@ assert(coll.drop());
|
||||
|
||||
// $setUnion should deduplicate objects as well. Note that documents which differ in field order are
|
||||
// considered unique.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, vals: [{a: 1, b: 2}]},
|
||||
{_id: 1, vals: [{b: 2, a: 1}]},
|
||||
{_id: 2, vals: [{a: 1, b: 2}]},
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, vals: [{a: 1, b: 2}]},
|
||||
{_id: 1, vals: [{b: 2, a: 1}]},
|
||||
{_id: 2, vals: [{a: 1, b: 2}]},
|
||||
]),
|
||||
);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$vals'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$vals"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [{a: 1, b: 2}, {b: 2, a: 1}]}]);
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
setUnionArr: [
|
||||
{a: 1, b: 2},
|
||||
{b: 2, a: 1},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
// $setUnion should 'skip over' documents that do not have the array field. Importantly, do not
|
||||
// insert null for documents that do not have the referenced field.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 1, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
|
||||
{_id: 2, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 3, author: "Matt", publisher: "Pub3", books: ["Happy!"]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$setUnion: '$books'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$allBooks', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
|
||||
{_id: 2, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 3, author: "Matt", publisher: "Pub3", books: ["Happy!"]},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$setUnion: "$books"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$allBooks", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: ["Happy!", "Smile :)"]}]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
// $setUnion dotted field.
|
||||
assert.commandWorked(coll.insert(
|
||||
[{_id: 1, a: {b: [1, 2, 3]}}, {_id: 2, a: {b: [3, 4, 5, 6]}}, {_id: 3, a: {b: [7, 8, 9]}}]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$setUnion: '$a.b'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$nums', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: {b: [1, 2, 3]}},
|
||||
{_id: 2, a: {b: [3, 4, 5, 6]}},
|
||||
{_id: 3, a: {b: [7, 8, 9]}},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$setUnion: "$a.b"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$nums", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
// $setUnion dotted field, array halfway on path.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
|
||||
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$setUnion: '$a.b'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$nums', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
|
||||
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$setUnion: "$a.b"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$nums", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]}]);
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
setUnionArr: [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[10, 11, 12],
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
@ -140,8 +189,7 @@ const notArrays = [1, "string", {object: "object"}, null];
|
||||
for (const notAnArray of notArrays) {
|
||||
assert.commandWorked(coll.insert([{_id: "doesNotMatter", vals: notAnArray}]));
|
||||
|
||||
assertErrorCode(
|
||||
coll, [{$group: {_id: null, v: {$setUnion: '$vals'}}}], ErrorCodes.TypeMismatch);
|
||||
assertErrorCode(coll, [{$group: {_id: null, v: {$setUnion: "$vals"}}}], ErrorCodes.TypeMismatch);
|
||||
|
||||
assert.commandWorked(coll.deleteOne({_id: "doesNotMatter"}));
|
||||
}
|
||||
@ -149,24 +197,27 @@ for (const notAnArray of notArrays) {
|
||||
assert(coll.drop());
|
||||
|
||||
// Basic test of $setUnion with grouping.
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 1, author: "Kyra", publisher: "Pub1", books: ["Book 1"]},
|
||||
{_id: 2, author: "Nick", publisher: "Pub3", books: ["Book 2"]},
|
||||
{_id: 3, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]}
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, author: "Kyra", publisher: "Pub1", books: ["Book 1"]},
|
||||
{_id: 2, author: "Nick", publisher: "Pub3", books: ["Book 2"]},
|
||||
{_id: 3, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]},
|
||||
]),
|
||||
);
|
||||
|
||||
result =
|
||||
coll.aggregate([
|
||||
{$group: {_id: '$publisher', booksByPublisher: {$setUnion: '$books'}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$booksByPublisher', sortBy: 1}}}}
|
||||
])
|
||||
.toArray();
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: "$publisher", booksByPublisher: {$setUnion: "$books"}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$booksByPublisher", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(
|
||||
result,
|
||||
[{_id: "Pub1", setUnionArr: ["Book 1"]}, {_id: "Pub3", setUnionArr: ["Book 2", "Book 3"]}]);
|
||||
assert.eq(result, [
|
||||
{_id: "Pub1", setUnionArr: ["Book 1"]},
|
||||
{_id: "Pub3", setUnionArr: ["Book 2", "Book 3"]},
|
||||
]);
|
||||
|
||||
// Basic correctness tests for $setUnion used in $bucket and $bucketAuto. Though $bucket and
|
||||
// $bucketAuto use accumulators in the same way that $group does, the tests below verifies that
|
||||
@ -179,20 +230,23 @@ for (let i = 0; i < 10; i++) {
|
||||
coll.insertMany(docs);
|
||||
|
||||
// $bucket
|
||||
result =
|
||||
coll.aggregate([{
|
||||
$bucket: {groupBy: '$_id', boundaries: [0, 5, 10], output: {nums: {$setUnion: "$arr"}}}
|
||||
}])
|
||||
.toArray();
|
||||
assert.eq(result, [{"_id": 0, "nums": [42]}, {"_id": 5, "nums": [42]}]);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{
|
||||
$bucket: {groupBy: "$_id", boundaries: [0, 5, 10], output: {nums: {$setUnion: "$arr"}}},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [
|
||||
{"_id": 0, "nums": [42]},
|
||||
{"_id": 5, "nums": [42]},
|
||||
]);
|
||||
|
||||
// $bucketAuto
|
||||
result =
|
||||
coll.aggregate(
|
||||
[{$bucketAuto: {groupBy: '$_id', buckets: 2, output: {nums: {$setUnion: "$arr"}}}}])
|
||||
.toArray();
|
||||
assert.eq(
|
||||
result,
|
||||
[{"_id": {"min": 0, "max": 5}, "nums": [42]}, {"_id": {"min": 5, "max": 9}, "nums": [42]}]);
|
||||
result = coll.aggregate([{$bucketAuto: {groupBy: "$_id", buckets: 2, output: {nums: {$setUnion: "$arr"}}}}]).toArray();
|
||||
assert.eq(result, [
|
||||
{"_id": {"min": 0, "max": 5}, "nums": [42]},
|
||||
{"_id": {"min": 5, "max": 9}, "nums": [42]},
|
||||
]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
@ -10,7 +10,7 @@ coll.drop();
|
||||
const sharded = FixtureHelpers.isSharded(coll);
|
||||
|
||||
const memoryLimitMB = sharded ? 200 : 100;
|
||||
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
|
||||
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
|
||||
|
||||
const bulk = coll.initializeUnorderedBulkOp();
|
||||
for (let i = 0; i < memoryLimitMB + 10; i++) {
|
||||
@ -22,17 +22,18 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
|
||||
|
||||
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
|
||||
// so may hit the group error code before we hit ExceededMemoryLimit.
|
||||
const pipeline = [{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$setUnion: '$bigArr'}}}];
|
||||
const expectedCodes =
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
|
||||
const pipeline = [{$sort: {sortKey: 1}}, {$group: {_id: null, bigArray: {$setUnion: "$bigArr"}}}];
|
||||
const expectedCodes = [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit];
|
||||
|
||||
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
|
||||
assert.commandFailedWithCode(
|
||||
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
|
||||
expectedCodes);
|
||||
expectedCodes,
|
||||
);
|
||||
|
||||
// The $setUnion accumulator does not support spilling to disk, so ensure that it will fail even
|
||||
// when disk use is allowed.
|
||||
assert.commandFailedWithCode(
|
||||
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}),
|
||||
expectedCodes);
|
||||
expectedCodes,
|
||||
);
|
||||
|
||||
@ -9,18 +9,21 @@ import "jstests/libs/query/sbe_assert_error_override.js";
|
||||
const coll = db[jsTestName()];
|
||||
coll.drop();
|
||||
|
||||
const largestInt =
|
||||
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
|
||||
// Makes a string for a unique sales associate name that looks like 'Jim the 4 from CA'.
|
||||
const associateName = (i, state) => ["Jim", "Pam", "Dwight", "Phyllis"][i % 4] + " the " +
|
||||
parseInt(i / 4) + " from " + state;
|
||||
const associateName = (i, state) =>
|
||||
["Jim", "Pam", "Dwight", "Phyllis"][i % 4] + " the " + parseInt(i / 4) + " from " + state;
|
||||
|
||||
// Basic correctness tests.
|
||||
let docs = [];
|
||||
const defaultN = 4;
|
||||
const states = [{state: "CA", sales: 10}, {state: "NY", sales: 7}, {state: "TX", sales: 4}];
|
||||
const states = [
|
||||
{state: "CA", sales: 10},
|
||||
{state: "NY", sales: 7},
|
||||
{state: "TX", sales: 4},
|
||||
];
|
||||
let expectedBottomNAscResults = [];
|
||||
let expectedTopNAscResults = [];
|
||||
let expectedBottomNDescResults = [];
|
||||
@ -65,17 +68,17 @@ function buildTopNBottomNSpec(op, sortSpec, outputSpec, nValue) {
|
||||
* Helper that verifies that 'op' and 'sortSpec' produce 'expectedResults'.
|
||||
*/
|
||||
function assertExpected(op, sortSpec, expectedResults) {
|
||||
const actual =
|
||||
coll.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$state",
|
||||
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)
|
||||
}
|
||||
const actual = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$state",
|
||||
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN),
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(expectedResults, actual);
|
||||
|
||||
// Basic correctness test for $top/$topN/$bottom/$bottomN used in $bucketAuto. Though
|
||||
@ -84,20 +87,19 @@ function assertExpected(op, sortSpec, expectedResults) {
|
||||
// allows us to compare the $bucketAuto results to the expected $group results (because there
|
||||
// are more buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
let actualBucketAutoResults =
|
||||
coll.aggregate([
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
buckets: 10 * 1000,
|
||||
output:
|
||||
{associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)}
|
||||
}
|
||||
let actualBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)},
|
||||
},
|
||||
{$project: {_id: "$_id.min", associates: 1}},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
{$project: {_id: "$_id.min", associates: 1}},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
// Using a computed projection will put the fields out of order. As such, we re-order them
|
||||
// below.
|
||||
@ -116,21 +118,20 @@ assertExpected("$bottomN", {sales: -1}, expectedBottomNDescResults);
|
||||
assertExpected("$topN", {sales: -1}, expectedTopNDescResults);
|
||||
|
||||
// Verify that we can compute multiple topN/bottomN groupings in the same $group.
|
||||
const combinedGroup =
|
||||
coll.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$state",
|
||||
bottomAsc: buildTopNBottomNSpec("$bottomN", {sales: 1}, "$associate", defaultN),
|
||||
bottomDesc:
|
||||
buildTopNBottomNSpec("$bottomN", {sales: -1}, "$associate", defaultN),
|
||||
topAsc: buildTopNBottomNSpec("$topN", {sales: 1}, "$associate", defaultN),
|
||||
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN)
|
||||
}
|
||||
const combinedGroup = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$state",
|
||||
bottomAsc: buildTopNBottomNSpec("$bottomN", {sales: 1}, "$associate", defaultN),
|
||||
bottomDesc: buildTopNBottomNSpec("$bottomN", {sales: -1}, "$associate", defaultN),
|
||||
topAsc: buildTopNBottomNSpec("$topN", {sales: 1}, "$associate", defaultN),
|
||||
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN),
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray();
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
let bottomAsc = [];
|
||||
let bottomDesc = [];
|
||||
@ -143,26 +144,25 @@ for (const doc of combinedGroup) {
|
||||
topDesc.push({_id: doc["_id"], associates: doc["topDesc"]});
|
||||
}
|
||||
|
||||
assert.eq([bottomAsc, bottomDesc, topAsc, topDesc], [
|
||||
expectedBottomNAscResults,
|
||||
expectedBottomNDescResults,
|
||||
expectedTopNAscResults,
|
||||
expectedTopNDescResults
|
||||
]);
|
||||
assert.eq(
|
||||
[bottomAsc, bottomDesc, topAsc, topDesc],
|
||||
[expectedBottomNAscResults, expectedBottomNDescResults, expectedTopNAscResults, expectedTopNDescResults],
|
||||
);
|
||||
|
||||
// Verify that we can dynamically compute 'n' based on the group key for $group.
|
||||
const groupKeyNExpr = {
|
||||
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4}
|
||||
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4},
|
||||
};
|
||||
const dynamicBottomNResults =
|
||||
coll.aggregate([{
|
||||
const dynamicBottomNResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
bottomAssociates:
|
||||
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
|
||||
}
|
||||
}])
|
||||
.toArray();
|
||||
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
// Verify that the 'CA' group has 10 results, while all others have only 4.
|
||||
for (const result of dynamicBottomNResults) {
|
||||
@ -181,46 +181,54 @@ for (const result of dynamicBottomNResults) {
|
||||
|
||||
// When output evaluates to missing for the single version, it should be promoted to null like in
|
||||
// $first.
|
||||
const outputMissing = coll.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
bottom: {$bottom: {output: "$b", sortBy: {sales: 1}}},
|
||||
top: {$top: {output: "$b", sortBy: {sales: 1}}}
|
||||
}
|
||||
})
|
||||
.toArray();
|
||||
const outputMissing = coll
|
||||
.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
bottom: {$bottom: {output: "$b", sortBy: {sales: 1}}},
|
||||
top: {$top: {output: "$b", sortBy: {sales: 1}}},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(null, outputMissing[0]["top"]);
|
||||
assert.eq(null, outputMissing[0]["bottom"]);
|
||||
|
||||
// Error cases.
|
||||
|
||||
// Cannot reference the group key in $bottomN when using $bucketAuto.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 2,
|
||||
output: {
|
||||
bottomAssociates:
|
||||
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
|
||||
}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
}),
|
||||
4544714);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 2,
|
||||
output: {
|
||||
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
4544714,
|
||||
);
|
||||
|
||||
// Verify that 'n' cannot be greater than the largest signed 64 bit int.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
$group: {
|
||||
_id: {'st': '$state'},
|
||||
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
}),
|
||||
5787903);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903,
|
||||
);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
@ -235,7 +243,7 @@ function gameScoreGenerator(i) {
|
||||
_id: i,
|
||||
game: "G" + gameId,
|
||||
player: players[playerId] + Math.floor(i / players.length),
|
||||
score: score
|
||||
score: score,
|
||||
};
|
||||
}
|
||||
const nGames = 100;
|
||||
@ -247,40 +255,44 @@ assert.commandWorked(coll.insert(games));
|
||||
|
||||
const gameSpec = {
|
||||
player: "$player",
|
||||
score: "$score"
|
||||
score: "$score",
|
||||
};
|
||||
for (const nVal of [defaultN, largestInt]) {
|
||||
const gameResults =
|
||||
coll.aggregate([{
|
||||
const gameResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$game",
|
||||
bottomAsc: buildTopNBottomNSpec("$bottomN", {score: 1}, gameSpec, nVal),
|
||||
topAsc: buildTopNBottomNSpec("$topN", {score: 1}, gameSpec, nVal),
|
||||
topDesc: buildTopNBottomNSpec("$topN", {score: -1}, gameSpec, nVal),
|
||||
bottomDesc: buildTopNBottomNSpec("$bottomN", {score: -1}, gameSpec, nVal),
|
||||
}
|
||||
}])
|
||||
.toArray();
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
for (const doc of gameResults) {
|
||||
let assertResultsInOrder = function(index, fieldName, arr, isAsc) {
|
||||
let assertResultsInOrder = function (index, fieldName, arr, isAsc) {
|
||||
const [first, second] = [arr[index - 1]["score"], arr[index]["score"]];
|
||||
const cmpResult = isAsc ? first < second : first > second;
|
||||
assert(cmpResult,
|
||||
"Incorrect order from accumulator corresponding to field '" + fieldName +
|
||||
"'; results: " + tojson(arr));
|
||||
assert(
|
||||
cmpResult,
|
||||
"Incorrect order from accumulator corresponding to field '" + fieldName + "'; results: " + tojson(arr),
|
||||
);
|
||||
};
|
||||
|
||||
let testFieldNames = function(fNames, isAsc) {
|
||||
let testFieldNames = function (fNames, isAsc) {
|
||||
for (const fieldName of fNames) {
|
||||
const arr = doc[fieldName];
|
||||
// Verify that 'nVal' is greater or equal to the number of results returned.
|
||||
// Note that we upconvert to NumberDecimal to account for 'largestInt' being a
|
||||
// NumberDecimal.
|
||||
assert.gte(NumberDecimal(nVal),
|
||||
NumberDecimal(arr.length),
|
||||
nVal + " is not GTE array length of " + tojson(arr) + " for field " +
|
||||
fieldName);
|
||||
assert.gte(
|
||||
NumberDecimal(nVal),
|
||||
NumberDecimal(arr.length),
|
||||
nVal + " is not GTE array length of " + tojson(arr) + " for field " + fieldName,
|
||||
);
|
||||
for (let i = 1; i < arr.length; ++i) {
|
||||
assertResultsInOrder(i, fieldName, arr, isAsc);
|
||||
}
|
||||
@ -294,12 +306,14 @@ for (const nVal of [defaultN, largestInt]) {
|
||||
|
||||
const rejectInvalidSpec = (op, assign, errCode, delProps = []) => {
|
||||
let spec = Object.assign({}, {output: "$associate", n: 2, sortBy: {sales: 1}}, assign);
|
||||
delProps.forEach(delProp => delete spec[delProp]);
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, bottomAssociates: {[op]: spec}}}],
|
||||
cursor: {}
|
||||
}),
|
||||
errCode);
|
||||
delProps.forEach((delProp) => delete spec[delProp]);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, bottomAssociates: {[op]: spec}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
errCode,
|
||||
);
|
||||
};
|
||||
|
||||
// Reject non-integral/negative values of n.
|
||||
@ -326,27 +340,26 @@ rejectInvalidSpec("$bottom", {}, 5788002);
|
||||
// Sort on embedded field.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: {b: i}}))));
|
||||
const embeddedResult =
|
||||
coll.aggregate(
|
||||
{$group: {_id: "", result: {$bottomN: {n: 3, output: "$a.b", sortBy: {"a.b": 1}}}}})
|
||||
.toArray();
|
||||
const embeddedResult = coll
|
||||
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a.b", sortBy: {"a.b": 1}}}}})
|
||||
.toArray();
|
||||
assert.eq([2, 3, 4], embeddedResult[0].result);
|
||||
|
||||
// Sort on array
|
||||
assert(coll.drop());
|
||||
const makeArray = (i) => [i, i + 1, i + 2];
|
||||
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: makeArray(i)}))));
|
||||
const nestedResult =
|
||||
coll.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
.toArray();
|
||||
const nestedResult = coll
|
||||
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
.toArray();
|
||||
assert.eq([makeArray(2), makeArray(3), makeArray(4)], nestedResult[0].result);
|
||||
|
||||
// Sort on doubly nested array.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: [makeArray(i)]}))));
|
||||
const doublyNestedResult =
|
||||
coll.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
.toArray();
|
||||
const doublyNestedResult = coll
|
||||
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
.toArray();
|
||||
assert.eq([[makeArray(2)], [makeArray(3)], [makeArray(4)]], doublyNestedResult[0].result);
|
||||
|
||||
// Compound Sorting.
|
||||
@ -354,56 +367,57 @@ coll.drop();
|
||||
const as = [1, 2, 3];
|
||||
const bs = [1, 2, 3];
|
||||
const crossProduct = (arr1, arr2) =>
|
||||
arr1.map(a => arr2.map(b => ({a, b}))).reduce((docs, inner) => docs.concat(inner));
|
||||
arr1.map((a) => arr2.map((b) => ({a, b}))).reduce((docs, inner) => docs.concat(inner));
|
||||
const fullAscending = crossProduct(as, bs);
|
||||
const aAscendingBDescending = crossProduct(as, bs.reverse());
|
||||
|
||||
assert.commandWorked(coll.insertMany(fullAscending));
|
||||
const actualFullAscending =
|
||||
coll.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}}
|
||||
}
|
||||
})
|
||||
.toArray();
|
||||
const actualFullAscending = coll
|
||||
.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(fullAscending, actualFullAscending[0]["sorted"]);
|
||||
|
||||
const actualAAscendingBDescending =
|
||||
coll.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}}
|
||||
}
|
||||
})
|
||||
.toArray();
|
||||
const actualAAscendingBDescending = coll
|
||||
.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(aAscendingBDescending, actualAAscendingBDescending[0]["sorted"]);
|
||||
|
||||
// $meta sort specification.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany(
|
||||
["apples apples pears", "pears pears", "apples apples apples", "apples doughnuts"].map(
|
||||
text => ({text}))));
|
||||
assert.commandWorked(
|
||||
coll.insertMany(
|
||||
["apples apples pears", "pears pears", "apples apples apples", "apples doughnuts"].map((text) => ({text})),
|
||||
),
|
||||
);
|
||||
assert.commandWorked(coll.createIndex({text: "text"}));
|
||||
const sortStageResult =
|
||||
coll.aggregate(
|
||||
[{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
|
||||
.toArray()
|
||||
.map(doc => doc["text"]);
|
||||
const sortStageResult = coll
|
||||
.aggregate([{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
|
||||
.toArray()
|
||||
.map((doc) => doc["text"]);
|
||||
const testOperatorText = (op) => {
|
||||
const opNResult =
|
||||
coll.aggregate([
|
||||
{$match: {$text: {$search: "apples pears"}}},
|
||||
{
|
||||
$group: {
|
||||
_id: "",
|
||||
result: {
|
||||
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
.toArray();
|
||||
const opNResult = coll
|
||||
.aggregate([
|
||||
{$match: {$text: {$search: "apples pears"}}},
|
||||
{
|
||||
$group: {
|
||||
_id: "",
|
||||
result: {
|
||||
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(opNResult.length, 1);
|
||||
assert.eq(sortStageResult, opNResult[0]["result"]);
|
||||
};
|
||||
@ -417,10 +431,9 @@ testOperatorText("$topN");
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany([{a: 1}, {a: 2}, {a: 3}]));
|
||||
const testConstantOutputAndSort = (op) => {
|
||||
const results =
|
||||
coll.aggregate(
|
||||
[{$group: {_id: null, result: {[op]: {n: 3, output: "abc", sortBy: {a: 1}}}}}])
|
||||
.toArray();
|
||||
const results = coll
|
||||
.aggregate([{$group: {_id: null, result: {[op]: {n: 3, output: "abc", sortBy: {a: 1}}}}}])
|
||||
.toArray();
|
||||
assert.eq(results.length, 1, results);
|
||||
assert.docEq(results[0], {_id: null, result: ["abc", "abc", "abc"]}, results);
|
||||
};
|
||||
|
||||
@ -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},
|
||||
});
|
||||
|
||||
@ -24,7 +24,7 @@ function buildAggCmd(pipeline, batchSize) {
|
||||
return {
|
||||
aggregate: t.getName(),
|
||||
pipeline: pipeline,
|
||||
cursor: (batchSize === undefined ? {} : {batchSize: batchSize}),
|
||||
cursor: batchSize === undefined ? {} : {batchSize: batchSize},
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,8 +42,7 @@ function aggCursor(pipeline, firstBatchSize, followupBatchSize) {
|
||||
const cmdOut = db.runCommand(buildAggCmd(pipeline, firstBatchSize));
|
||||
assert.commandWorked(cmdOut);
|
||||
|
||||
if (firstBatchSize !== undefined)
|
||||
assert.lte(cmdOut.cursor.firstBatch.length, firstBatchSize);
|
||||
if (firstBatchSize !== undefined) assert.lte(cmdOut.cursor.firstBatch.length, firstBatchSize);
|
||||
|
||||
return makeCursor(cmdOut, followupBatchSize);
|
||||
}
|
||||
@ -53,24 +52,22 @@ function aggCursor(pipeline, firstBatchSize, followupBatchSize) {
|
||||
//
|
||||
|
||||
let bigArray = [];
|
||||
for (let i = 0; i < 1000; i++)
|
||||
bigArray.push(i);
|
||||
for (let i = 0; i < 1000; i++) bigArray.push(i);
|
||||
|
||||
let bigStr = Array(1001).toString(); // 1000 bytes of ','
|
||||
let bigStr = Array(1001).toString(); // 1000 bytes of ','
|
||||
|
||||
for (let i = 0; i < 100; i++)
|
||||
t.insert({_id: i, bigArray: bigArray, bigStr: bigStr});
|
||||
for (let i = 0; i < 100; i++) t.insert({_id: i, bigArray: bigArray, bigStr: bigStr});
|
||||
|
||||
//
|
||||
// do testing
|
||||
//
|
||||
|
||||
// successfully handles results > 16MB (bigArray.length * bytes in bigStr * t.count() == 100MB)
|
||||
let cursor = aggCursor([{$unwind: '$bigArray'}]); // default settings
|
||||
let cursor = aggCursor([{$unwind: "$bigArray"}]); // default settings
|
||||
assert.eq(cursor.itcount(), bigArray.length * t.count());
|
||||
cursor = aggCursor([{$unwind: '$bigArray'}], 0); // empty first batch
|
||||
cursor = aggCursor([{$unwind: "$bigArray"}], 0); // empty first batch
|
||||
assert.eq(cursor.itcount(), bigArray.length * t.count());
|
||||
cursor = aggCursor([{$unwind: '$bigArray'}], 5, 5); // many small batches
|
||||
cursor = aggCursor([{$unwind: "$bigArray"}], 5, 5); // many small batches
|
||||
assert.eq(cursor.itcount(), bigArray.length * t.count());
|
||||
|
||||
// empty result set results in cursor.id == 0 unless batchSize is 0;
|
||||
@ -87,21 +84,21 @@ res = t.runCommand(buildAggCmd([{$noSuchStage: 1}], 0));
|
||||
assert.commandFailed(res);
|
||||
|
||||
// data dependent errors can get ok:1 but fail in getMore if they don't fail in first batch
|
||||
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, '$bigStr']}}}], 1));
|
||||
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, "$bigStr"]}}}], 1));
|
||||
assert.commandFailed(res);
|
||||
|
||||
// Setting batchSize 0 doesn't guarantee that command will succeed: it may fail during plan
|
||||
// selection.
|
||||
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, '$bigStr']}}}], 0));
|
||||
res = t.runCommand(buildAggCmd([{$project: {cantAddString: {$add: [1, "$bigStr"]}}}], 0));
|
||||
if (res.ok) {
|
||||
assert.throws(function() {
|
||||
assert.throws(function () {
|
||||
makeCursor(res).itcount();
|
||||
});
|
||||
}
|
||||
|
||||
// error if collection dropped after first batch
|
||||
cursor = aggCursor([{$unwind: '$bigArray'}], 0);
|
||||
cursor = aggCursor([{$unwind: "$bigArray"}], 0);
|
||||
t.drop();
|
||||
assert.throws(function() {
|
||||
assert.throws(function () {
|
||||
cursor.itcount();
|
||||
});
|
||||
|
||||
@ -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}));
|
||||
|
||||
@ -5,17 +5,19 @@ t.drop();
|
||||
|
||||
// first 63MB
|
||||
for (let i = 0; i < 63; i++) {
|
||||
assert.commandWorked(t.insert({a: 'a'.repeat(1024 * 1024)}));
|
||||
assert.commandWorked(t.insert({a: "a".repeat(1024 * 1024)}));
|
||||
}
|
||||
|
||||
// the remaining ~1MB with room for field names and other overhead
|
||||
assert.commandWorked(t.insert({a: 'a'.repeat(1024 * 1024 - 1106)}));
|
||||
assert.commandWorked(t.insert({a: "a".repeat(1024 * 1024 - 1106)}));
|
||||
|
||||
// do not use cursor form, since it has a different workaroud for this issue.
|
||||
assert.commandFailed(db.runCommand({
|
||||
aggregate: t.getName(),
|
||||
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: '$a'}}}}]
|
||||
}));
|
||||
assert.commandFailed(
|
||||
db.runCommand({
|
||||
aggregate: t.getName(),
|
||||
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: "$a"}}}}],
|
||||
}),
|
||||
);
|
||||
|
||||
// Make sure the server is still up.
|
||||
assert.commandWorked(db.runCommand('ping'));
|
||||
assert.commandWorked(db.runCommand("ping"));
|
||||
|
||||
@ -20,23 +20,24 @@ coll.drop();
|
||||
assert.commandWorked(coll.insertOne({date: new Timestamp(1341337661, 1)}));
|
||||
assert.commandWorked(coll.insertOne({date: new Date(1341337661000)}));
|
||||
// Aggregate checking various combinations of the constant and the field
|
||||
let agg_timestamp = coll.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
dayOfMonth: {$dayOfMonth: '$date'},
|
||||
dayOfWeek: {$dayOfWeek: '$date'},
|
||||
dayOfYear: {$dayOfYear: '$date'},
|
||||
hour: {$hour: '$date'},
|
||||
minute: {$minute: '$date'},
|
||||
month: {$month: '$date'},
|
||||
second: {$second: '$date'},
|
||||
week: {$week: '$date'},
|
||||
year: {$year: '$date'}
|
||||
}
|
||||
})
|
||||
.toArray();
|
||||
let agg_timestamp = coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
dayOfMonth: {$dayOfMonth: "$date"},
|
||||
dayOfWeek: {$dayOfWeek: "$date"},
|
||||
dayOfYear: {$dayOfYear: "$date"},
|
||||
hour: {$hour: "$date"},
|
||||
minute: {$minute: "$date"},
|
||||
month: {$month: "$date"},
|
||||
second: {$second: "$date"},
|
||||
week: {$week: "$date"},
|
||||
year: {$year: "$date"},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
// Assert the two entries are equal
|
||||
assert.eq(agg_timestamp[0], agg_timestamp[1], 'agg_timestamp failed');
|
||||
assert.eq(agg_timestamp[0], agg_timestamp[1], "agg_timestamp failed");
|
||||
|
||||
// Clear db for timestamp to date compare test
|
||||
// For historical reasons the compare the same if they are the same 64-bit representation.
|
||||
@ -49,31 +50,30 @@ agg_timestamp = coll.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
// comparison is different code path based on order (same as in bson)
|
||||
ts_date: {$eq: ['$time', '$date']},
|
||||
date_ts: {$eq: ['$date', '$time']}
|
||||
}
|
||||
ts_date: {$eq: ["$time", "$date"]},
|
||||
date_ts: {$eq: ["$date", "$time"]},
|
||||
},
|
||||
});
|
||||
assert.eq(agg_timestamp.toArray(),
|
||||
[{ts_date: false, date_ts: false}, {ts_date: false, date_ts: false}]);
|
||||
assert.eq(agg_timestamp.toArray(), [
|
||||
{ts_date: false, date_ts: false},
|
||||
{ts_date: false, date_ts: false},
|
||||
]);
|
||||
|
||||
// Clear db for timestamp comparison tests
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insertOne({time: new Timestamp(1341337661, 1), time2: new Timestamp(1341337661, 2)}));
|
||||
assert.commandWorked(coll.insertOne({time: new Timestamp(1341337661, 1), time2: new Timestamp(1341337661, 2)}));
|
||||
agg_timestamp = coll.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
cmp: {$cmp: ['$time', '$time2']},
|
||||
eq: {$eq: ['$time', '$time2']},
|
||||
gt: {$gt: ['$time', '$time2']},
|
||||
gte: {$gte: ['$time', '$time2']},
|
||||
lt: {$lt: ['$time', '$time2']},
|
||||
lte: {$lte: ['$time', '$time2']},
|
||||
ne: {$ne: ['$time', '$time2']}
|
||||
}
|
||||
cmp: {$cmp: ["$time", "$time2"]},
|
||||
eq: {$eq: ["$time", "$time2"]},
|
||||
gt: {$gt: ["$time", "$time2"]},
|
||||
gte: {$gte: ["$time", "$time2"]},
|
||||
lt: {$lt: ["$time", "$time2"]},
|
||||
lte: {$lte: ["$time", "$time2"]},
|
||||
ne: {$ne: ["$time", "$time2"]},
|
||||
},
|
||||
});
|
||||
var agg_timestampresult =
|
||||
[{cmp: -1, eq: false, gt: false, gte: false, lt: true, lte: true, ne: true}];
|
||||
var agg_timestampresult = [{cmp: -1, eq: false, gt: false, gte: false, lt: true, lte: true, ne: true}];
|
||||
// Assert the results are as expected
|
||||
assert.eq(
|
||||
agg_timestamp.toArray(), agg_timestampresult, 'agg_timestamp failed comparing two timestamps');
|
||||
assert.eq(agg_timestamp.toArray(), agg_timestampresult, "agg_timestamp failed comparing two timestamps");
|
||||
|
||||
@ -20,14 +20,16 @@ otherColl.drop();
|
||||
assert.commandWorked(otherColl.insert({_id: "id0", x: 1}));
|
||||
assert.commandWorked(otherColl.insert({_id: "id1", x: 2}));
|
||||
|
||||
assert.commandWorked(coll.insert({
|
||||
_id: 0,
|
||||
link: new DBRef(otherColl.getName(), "id0", db.getName()),
|
||||
linkArray: [
|
||||
new DBRef(otherColl.getName(), "id0", db.getName()),
|
||||
new DBRef(otherColl.getName(), "id1", db.getName())
|
||||
]
|
||||
}));
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 0,
|
||||
link: new DBRef(otherColl.getName(), "id0", db.getName()),
|
||||
linkArray: [
|
||||
new DBRef(otherColl.getName(), "id0", db.getName()),
|
||||
new DBRef(otherColl.getName(), "id1", db.getName()),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
function projectOnlyPipeline(projection) {
|
||||
const aggRes = coll.aggregate({$project: projection}).toArray();
|
||||
@ -38,34 +40,36 @@ function projectOnlyPipeline(projection) {
|
||||
|
||||
// Refer to a DBRef sub-field in a projection.
|
||||
assert.eq(projectOnlyPipeline({refVal: "$link.$ref"}), [{_id: 0, refVal: otherColl.getName()}]);
|
||||
assert.eq(projectOnlyPipeline({refVal: "$linkArray.$ref"}),
|
||||
[{_id: 0, refVal: [otherColl.getName(), otherColl.getName()]}]);
|
||||
assert.eq(projectOnlyPipeline({refVal: "$linkArray.$ref"}), [
|
||||
{_id: 0, refVal: [otherColl.getName(), otherColl.getName()]},
|
||||
]);
|
||||
|
||||
assert.eq(projectOnlyPipeline({idVal: "$link.$id"}), [{_id: 0, idVal: "id0"}]);
|
||||
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$id"}), [{_id: 0, idVal: ["id0", "id1"]}]);
|
||||
|
||||
assert.eq(projectOnlyPipeline({idVal: "$link.$db"}), [{_id: 0, idVal: db.getName()}]);
|
||||
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$db"}),
|
||||
[{_id: 0, idVal: [db.getName(), db.getName()]}]);
|
||||
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$db"}), [{_id: 0, idVal: [db.getName(), db.getName()]}]);
|
||||
|
||||
// Use a DBRef sub-field in an expression.
|
||||
assert.eq(projectOnlyPipeline({idLen: {$strLenCP: "$link.$id"}}), [{_id: 0, idLen: "id0".length}]);
|
||||
|
||||
// Project away DBRef values.
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 0}, linkArray: 0}),
|
||||
[{_id: 0, link: {$id: "id0", $db: db.getName()}}]);
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 0}, linkArray: 0}), [{_id: 0, link: {$id: "id0", $db: db.getName()}}]);
|
||||
|
||||
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [{
|
||||
_id: 0,
|
||||
linkArray: [
|
||||
{$ref: otherColl.getName(), $db: db.getName()},
|
||||
{$ref: otherColl.getName(), $db: db.getName()}
|
||||
]
|
||||
}]);
|
||||
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [
|
||||
{
|
||||
_id: 0,
|
||||
linkArray: [
|
||||
{$ref: otherColl.getName(), $db: db.getName()},
|
||||
{$ref: otherColl.getName(), $db: db.getName()},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// Assigning to a DBRef field.
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 1, $id: 1, $db: "someOtherDB"}}),
|
||||
[{_id: 0, link: new DBRef(otherColl.getName(), "id0", "someOtherDB")}]);
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 1, $id: 1, $db: "someOtherDB"}}), [
|
||||
{_id: 0, link: new DBRef(otherColl.getName(), "id0", "someOtherDB")},
|
||||
]);
|
||||
|
||||
// While not a 'feature' we advertise, it is allowed to assign to top-level DBRef fields.
|
||||
assert.eq(projectOnlyPipeline({$ref: "$link.$ref"}), [{_id: 0, $ref: otherColl.getName()}]);
|
||||
@ -75,46 +79,61 @@ assert.eq(projectOnlyPipeline({$ref: "$link.$ref"}), [{_id: 0, $ref: otherColl.g
|
||||
assert.throwsWithCode(() => coll.aggregate({$project: {x: "$$ref"}}).toArray(), 17276);
|
||||
|
||||
// It can be accessed through $$ROOT, however.
|
||||
assert.eq(coll.aggregate([
|
||||
// Rather than go through the trouble of inserting a document with a top-level
|
||||
// $-prefixed field, create one in an intermediate $project stage.
|
||||
{$project: {"$ref": "hello world"}},
|
||||
// Make sure that no optimization coalesces the above projection stage with the
|
||||
// below one.
|
||||
{$_internalInhibitOptimization: {}},
|
||||
{$project: {x: "$$ROOT.$ref"}}
|
||||
])
|
||||
.toArray(),
|
||||
[{_id: 0, x: "hello world"}]);
|
||||
assert.eq(
|
||||
coll
|
||||
.aggregate([
|
||||
// Rather than go through the trouble of inserting a document with a top-level
|
||||
// $-prefixed field, create one in an intermediate $project stage.
|
||||
{$project: {"$ref": "hello world"}},
|
||||
// Make sure that no optimization coalesces the above projection stage with the
|
||||
// below one.
|
||||
{$_internalInhibitOptimization: {}},
|
||||
{$project: {x: "$$ROOT.$ref"}},
|
||||
])
|
||||
.toArray(),
|
||||
[{_id: 0, x: "hello world"}],
|
||||
);
|
||||
|
||||
// Do a count (using $group) on a DBRef field.
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: 1}}}).toArray(),
|
||||
[{_id: db.getName(), count: 1}]);
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: 1}}}).toArray(), [{_id: db.getName(), count: 1}]);
|
||||
|
||||
// Refer to a DBRef field in an accumulator.
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: {$size: "$linkArray.$ref"}}}})
|
||||
.toArray(),
|
||||
[{_id: db.getName(), count: 2}]);
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: {$size: "$linkArray.$ref"}}}}).toArray(), [
|
||||
{_id: db.getName(), count: 2},
|
||||
]);
|
||||
|
||||
// Use $lookup with a DBRef.
|
||||
|
||||
// Equality match version.
|
||||
const lookupEqualityPipeline = [{$lookup: {from: otherColl.getName(),
|
||||
localField: "link.$id",
|
||||
foreignField: "_id",
|
||||
as: "joinedField"}},
|
||||
{$project: {link: 0, linkArray: 0}}];
|
||||
assert.eq(coll.aggregate(lookupEqualityPipeline).toArray(),
|
||||
[{_id: 0, joinedField: [{_id: "id0", x: 1}]}]);
|
||||
const lookupEqualityPipeline = [
|
||||
{$lookup: {from: otherColl.getName(), localField: "link.$id", foreignField: "_id", as: "joinedField"}},
|
||||
{$project: {link: 0, linkArray: 0}},
|
||||
];
|
||||
assert.eq(coll.aggregate(lookupEqualityPipeline).toArray(), [{_id: 0, joinedField: [{_id: "id0", x: 1}]}]);
|
||||
|
||||
// Foreign pipeline.
|
||||
const lookupSubPipePipeline = [{$lookup: {from: otherColl.getName(),
|
||||
let: {idsWanted: "$linkArray.$id"},
|
||||
pipeline: [{$match: {$expr: {$in: ["$_id", "$$idsWanted"]}}}],
|
||||
as: "joinedField"}},
|
||||
{$project: {link: 0, linkArray: 0}}];
|
||||
assert(anyEq(coll.aggregate(lookupSubPipePipeline).toArray(),
|
||||
[{_id: 0, joinedField: [{_id: "id0", x: 1}, {_id: "id1", x: 2}]}]));
|
||||
const lookupSubPipePipeline = [
|
||||
{
|
||||
$lookup: {
|
||||
from: otherColl.getName(),
|
||||
let: {idsWanted: "$linkArray.$id"},
|
||||
pipeline: [{$match: {$expr: {$in: ["$_id", "$$idsWanted"]}}}],
|
||||
as: "joinedField",
|
||||
},
|
||||
},
|
||||
{$project: {link: 0, linkArray: 0}},
|
||||
];
|
||||
assert(
|
||||
anyEq(coll.aggregate(lookupSubPipePipeline).toArray(), [
|
||||
{
|
||||
_id: 0,
|
||||
joinedField: [
|
||||
{_id: "id0", x: 1},
|
||||
{_id: "id1", x: 2},
|
||||
],
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
(function testGraphLookup() {
|
||||
// $graphLookup using DBRef.
|
||||
@ -122,28 +141,34 @@ assert(anyEq(coll.aggregate(lookupSubPipePipeline).toArray(),
|
||||
graphLookupColl.drop();
|
||||
|
||||
// id0 -> id1 -> id2 -> id0
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id0", link: new DBRef(graphLookupColl.getName(), "id1", db.getName())}));
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id1", link: new DBRef(graphLookupColl.getName(), "id2", db.getName())}));
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id2", link: new DBRef(graphLookupColl.getName(), "id0", db.getName())}));
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id0", link: new DBRef(graphLookupColl.getName(), "id1", db.getName())}),
|
||||
);
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id1", link: new DBRef(graphLookupColl.getName(), "id2", db.getName())}),
|
||||
);
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id2", link: new DBRef(graphLookupColl.getName(), "id0", db.getName())}),
|
||||
);
|
||||
|
||||
// id3 -> id4
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id3", link: new DBRef(graphLookupColl.getName(), "id4", db.getName())}));
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id3", link: new DBRef(graphLookupColl.getName(), "id4", db.getName())}),
|
||||
);
|
||||
assert.commandWorked(graphLookupColl.insert({_id: "id4", link: null}));
|
||||
|
||||
const graphLookupPipeline = [{
|
||||
$graphLookup: {
|
||||
from: graphLookupColl.getName(),
|
||||
startWith: "$link.$id",
|
||||
connectFromField: "link.$id",
|
||||
connectToField: "_id",
|
||||
as: "connectedDocuments"
|
||||
}
|
||||
},
|
||||
{$sort: {_id: 1}}];
|
||||
const graphLookupPipeline = [
|
||||
{
|
||||
$graphLookup: {
|
||||
from: graphLookupColl.getName(),
|
||||
startWith: "$link.$id",
|
||||
connectFromField: "link.$id",
|
||||
connectToField: "_id",
|
||||
as: "connectedDocuments",
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
];
|
||||
|
||||
const res = graphLookupColl.aggregate(graphLookupPipeline).toArray();
|
||||
// id0, id1, and id2 are all connected.
|
||||
@ -175,7 +200,7 @@ assert.commandWorked(coll.createIndex({"link.$ref": 1}, {unique: true}));
|
||||
thirdColl
|
||||
.aggregate([
|
||||
{$project: {"link.$ref": otherColl.getName(), "link.$id": "id0", sentinel: "foo"}},
|
||||
{$merge: {into: coll.getName(), on: "link.$ref", whenMatched: "replace"}}
|
||||
{$merge: {into: coll.getName(), on: "link.$ref", whenMatched: "replace"}},
|
||||
])
|
||||
.itcount();
|
||||
|
||||
@ -190,13 +215,14 @@ thirdColl
|
||||
$merge: {
|
||||
into: coll.getName(),
|
||||
on: "link.$ref",
|
||||
whenMatched: [{
|
||||
$project:
|
||||
{"link.$ref": "otherRef", "link.$id": "otherId", "link.$db": "otherDB"}
|
||||
}],
|
||||
whenNotMatched: "discard"
|
||||
}
|
||||
}
|
||||
whenMatched: [
|
||||
{
|
||||
$project: {"link.$ref": "otherRef", "link.$id": "otherId", "link.$db": "otherDB"},
|
||||
},
|
||||
],
|
||||
whenNotMatched: "discard",
|
||||
},
|
||||
},
|
||||
])
|
||||
.itcount();
|
||||
assert.eq(coll.find().toArray()[0], {_id: 0, link: new DBRef("otherRef", "otherId", "otherDB")});
|
||||
assert.eq(coll.find().toArray()[0], {_id: 0, link: new DBRef("otherRef", "otherId", "otherDB")});
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -6,7 +6,7 @@ const testDB = db.getSiblingDB("jsTestName");
|
||||
assert.commandWorked(testDB.dropDatabase());
|
||||
|
||||
const coll = testDB.coll;
|
||||
const largeString = 'x'.repeat(10 * 1024 * 1024);
|
||||
const largeString = "x".repeat(10 * 1024 * 1024);
|
||||
assert.commandWorked(coll.insert({a: 1, b: largeString}));
|
||||
|
||||
// Use '$addFields' to create extra-large documents in the middle of the pipeline followed by
|
||||
@ -15,7 +15,7 @@ const pipeline = [
|
||||
{$_internalInhibitOptimization: {}},
|
||||
{$addFields: {c: {$concat: ["$b", "-"]}}},
|
||||
{$match: {c: {$exists: true}}},
|
||||
{$project: {a: 1}}
|
||||
{$project: {a: 1}},
|
||||
];
|
||||
|
||||
assert.doesNotThrow(() => coll.aggregate(pipeline).toArray());
|
||||
assert.doesNotThrow(() => coll.aggregate(pipeline).toArray());
|
||||
|
||||
@ -47,10 +47,10 @@ function runAgg(pipeline) {
|
||||
function testLargeIn() {
|
||||
jsTestLog("Testing large $in");
|
||||
// Int limit is different than double limit.
|
||||
const filterValsInts = range(1200000).map(i => NumberInt(i));
|
||||
const filterValsInts = range(1200000).map((i) => NumberInt(i));
|
||||
runAgg([{$match: {a: {$in: filterValsInts}}}]);
|
||||
|
||||
const filterValsDoubles = range(1000000).map(i => i * 1.0);
|
||||
const filterValsDoubles = range(1000000).map((i) => i * 1.0);
|
||||
runAgg([{$match: {a: {$in: filterValsDoubles}}}]);
|
||||
}
|
||||
|
||||
@ -58,10 +58,10 @@ function testLargeIn() {
|
||||
function testLargeSwitch() {
|
||||
jsTestLog("Testing large $switch");
|
||||
const cases = range(150000)
|
||||
.map(function(i) {
|
||||
return {case: {$gt: ["$a", i]}, then: i};
|
||||
})
|
||||
.reverse();
|
||||
.map(function (i) {
|
||||
return {case: {$gt: ["$a", i]}, then: i};
|
||||
})
|
||||
.reverse();
|
||||
runAgg([{$project: {b: {$switch: {branches: cases, default: 345678}}}}]);
|
||||
}
|
||||
|
||||
@ -72,21 +72,23 @@ function testLargeBucket() {
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
boundaries.push(i);
|
||||
}
|
||||
runAgg([{
|
||||
$bucket: {
|
||||
groupBy: "$a",
|
||||
boundaries: boundaries,
|
||||
default: "default",
|
||||
output: {"count": {$sum: 1}}
|
||||
}
|
||||
}]);
|
||||
runAgg([
|
||||
{
|
||||
$bucket: {
|
||||
groupBy: "$a",
|
||||
boundaries: boundaries,
|
||||
default: "default",
|
||||
output: {"count": {$sum: 1}},
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
// Construct a {$project: {a0: 1, a1: 1, ...}}.
|
||||
function testLargeProject() {
|
||||
jsTestLog("Testing large $project");
|
||||
const projectFields = {};
|
||||
range(1000000).forEach(function(i) {
|
||||
range(1000000).forEach(function (i) {
|
||||
projectFields["a" + i] = NumberInt(1);
|
||||
});
|
||||
runAgg([{$project: projectFields}]);
|
||||
@ -105,51 +107,51 @@ function testLargeAndOrPredicates() {
|
||||
|
||||
// Large $match of the form {$match: {a0: 1, a1: 1, ...}}
|
||||
const largeMatch = {};
|
||||
range(800000).forEach(function(i) {
|
||||
range(800000).forEach(function (i) {
|
||||
largeMatch["a" + i] = NumberInt(1);
|
||||
});
|
||||
runAgg([{$match: largeMatch}]);
|
||||
|
||||
function intStream(n) {
|
||||
return range(n).map(i => NumberInt(i));
|
||||
return range(n).map((i) => NumberInt(i));
|
||||
}
|
||||
|
||||
const andOrFilters = [
|
||||
// Plain a=i filter.
|
||||
intStream(500000).map(function(i) {
|
||||
intStream(500000).map(function (i) {
|
||||
return {a: i};
|
||||
}),
|
||||
// a_i = i filter. Different field for each value.
|
||||
intStream(500000).map(function(i) {
|
||||
intStream(500000).map(function (i) {
|
||||
const field = "a" + i;
|
||||
return {[field]: i};
|
||||
}),
|
||||
// Mix of lt and gt with the same field.
|
||||
intStream(500000).map(function(i) {
|
||||
intStream(500000).map(function (i) {
|
||||
const predicate = i % 2 ? {$lt: i} : {$gt: i};
|
||||
return {a: predicate};
|
||||
}),
|
||||
// Mix of lt and gt with different fields.
|
||||
intStream(400000).map(function(i) {
|
||||
intStream(400000).map(function (i) {
|
||||
const field = "a" + i;
|
||||
const predicate = i % 2 ? {$lt: i} : {$gt: i};
|
||||
return {[field]: predicate};
|
||||
}),
|
||||
// Mix of lt and gt wrapped in not with different fields.
|
||||
intStream(300000).map(function(i) {
|
||||
intStream(300000).map(function (i) {
|
||||
const field = "a" + i;
|
||||
const predicate = i % 2 ? {$lt: i} : {$gt: i};
|
||||
return {[field]: {$not: predicate}};
|
||||
}),
|
||||
// $exists on different fields.
|
||||
intStream(400000).map(function(i) {
|
||||
intStream(400000).map(function (i) {
|
||||
const field = "a" + i;
|
||||
return {[field]: {$exists: true}};
|
||||
}),
|
||||
intStream(400000).map(function(i) {
|
||||
intStream(400000).map(function (i) {
|
||||
const field = "a" + i;
|
||||
return {[field]: {$exists: false}};
|
||||
})
|
||||
}),
|
||||
];
|
||||
for (const m of andOrFilters) {
|
||||
runAgg([{$match: {$and: m}}]);
|
||||
@ -161,7 +163,7 @@ function testLongFieldNames() {
|
||||
jsTestLog("Testing $match with long field name");
|
||||
// Test with a long field name that's accepted by the server.
|
||||
{
|
||||
const longFieldName = 'a'.repeat(10_000_000);
|
||||
const longFieldName = "a".repeat(10_000_000);
|
||||
const predicate = {[longFieldName]: 1};
|
||||
runAgg([{$match: predicate}]);
|
||||
runAgg([{$match: {$and: [predicate]}}]);
|
||||
@ -170,7 +172,7 @@ function testLongFieldNames() {
|
||||
|
||||
// Test with a field name that's too long, where the server rejects it.
|
||||
{
|
||||
const extraLongFieldName = 'a'.repeat(17_000_000);
|
||||
const extraLongFieldName = "a".repeat(17_000_000);
|
||||
const predicate = {[extraLongFieldName]: 1};
|
||||
assert.throwsWithCode(() => runAgg([{$match: predicate}]), 17260);
|
||||
assert.throwsWithCode(() => runAgg([{$match: {$and: [predicate]}}]), 17260);
|
||||
@ -192,9 +194,9 @@ function testDeeplyNestedPath() {
|
||||
// Test pipeline length.
|
||||
function testPipelineLimits() {
|
||||
jsTestLog("Testing large agg pipelines");
|
||||
const pipelineLimit =
|
||||
assert.commandWorked(db.adminCommand({getParameter: 1, internalPipelineLengthLimit: 1}))
|
||||
.internalPipelineLengthLimit;
|
||||
const pipelineLimit = assert.commandWorked(
|
||||
db.adminCommand({getParameter: 1, internalPipelineLengthLimit: 1}),
|
||||
).internalPipelineLengthLimit;
|
||||
let stages = [
|
||||
{$limit: 1},
|
||||
{$skip: 1},
|
||||
@ -209,7 +211,7 @@ function testPipelineLimits() {
|
||||
];
|
||||
|
||||
for (const stage of stages) {
|
||||
const pipeline = range(pipelineLimit).map(_ => stage);
|
||||
const pipeline = range(pipelineLimit).map((_) => stage);
|
||||
jsTestLog(stage);
|
||||
runAgg(pipeline);
|
||||
}
|
||||
@ -223,13 +225,13 @@ function testPipelineLimits() {
|
||||
let fieldIndex = 0;
|
||||
function generateNestedAndOrHelper(type, branchingFactor, maxDepth) {
|
||||
if (maxDepth === 0) {
|
||||
const field = 'a' + fieldIndex;
|
||||
const field = "a" + fieldIndex;
|
||||
const query = {[field]: NumberInt(fieldIndex)};
|
||||
fieldIndex++;
|
||||
return query;
|
||||
}
|
||||
|
||||
const oppositeType = type === '$and' ? '$or' : '$and';
|
||||
const oppositeType = type === "$and" ? "$or" : "$and";
|
||||
const children = [];
|
||||
for (let i = 0; i < branchingFactor; i++) {
|
||||
const childQuery = generateNestedAndOrHelper(oppositeType, branchingFactor, maxDepth - 1);
|
||||
@ -246,7 +248,7 @@ function generateNestedAndOr(type, branchingFactor, maxDepth) {
|
||||
|
||||
function testNestedAndOr() {
|
||||
jsTestLog("Testing nested $and/$or");
|
||||
for (const topLevelType of ['$and', '$or']) {
|
||||
for (const topLevelType of ["$and", "$or"]) {
|
||||
// Test different types of nested queries
|
||||
let [branchingFactor, maxDepth] = [3, 10];
|
||||
const deepNarrowQuery = generateNestedAndOr(topLevelType, branchingFactor, maxDepth);
|
||||
@ -303,7 +305,7 @@ const tests = [
|
||||
testPipelineLimits,
|
||||
testLargeSetFunction,
|
||||
testLargeConcatFunction,
|
||||
testLargeArrayToObjectFunction
|
||||
testLargeArrayToObjectFunction,
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -33,7 +33,7 @@ function setMemoryParamHelper(paramName, memoryLimit) {
|
||||
cmdObj: {
|
||||
setParameter: 1,
|
||||
[paramName]: memoryLimit,
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.gt(commandResArr.length, 0, "Setting memory limit on primaries failed");
|
||||
const oldMemoryLimit = assert.commandWorked(commandResArr[0]).was;
|
||||
@ -57,7 +57,7 @@ const memoryLimitMB = sharded ? 200 : 100;
|
||||
|
||||
const isSbeGroupLookupPushdownEnabled = checkSbeRestrictedOrFullyEnabled(db);
|
||||
|
||||
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
|
||||
const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ','
|
||||
for (let i = 0; i < memoryLimitMB + 1; i++)
|
||||
assert.commandWorked(coll.insert({_id: i, bigStr: i + bigStr, random: Math.random()}));
|
||||
|
||||
@ -66,24 +66,21 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
|
||||
function test({pipeline, expectedCodes, canSpillToDisk}) {
|
||||
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
|
||||
assert.commandFailedWithCode(
|
||||
db.runCommand(
|
||||
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
|
||||
expectedCodes);
|
||||
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
|
||||
expectedCodes,
|
||||
);
|
||||
|
||||
// If this command supports spilling to disk, ensure that it will succeed when disk use is
|
||||
// allowed.
|
||||
const res = db.runCommand(
|
||||
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true});
|
||||
const res = db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true});
|
||||
if (canSpillToDisk) {
|
||||
assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(),
|
||||
coll.count()); // all tests output one doc per input doc
|
||||
assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(), coll.count()); // all tests output one doc per input doc
|
||||
|
||||
if (isSbeGroupLookupPushdownEnabled) {
|
||||
const explain = db.runCommand({
|
||||
explain:
|
||||
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}
|
||||
explain: {aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true},
|
||||
});
|
||||
const hashAggGroups = getSbePlanStages(explain, 'group');
|
||||
const hashAggGroups = getSbePlanStages(explain, "group");
|
||||
if (hashAggGroups.length > 0) {
|
||||
assert.eq(hashAggGroups.length, 1, explain);
|
||||
const hashAggGroup = hashAggGroups[0];
|
||||
@ -101,7 +98,7 @@ function setHashAggParameters(memoryLimit, atLeast) {
|
||||
cmdObj: {
|
||||
setParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill: memoryLimit,
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.gt(memLimitCommandResArr.length, 0, "Setting memory limit on primaries failed.");
|
||||
const oldMemoryLimit = assert.commandWorked(memLimitCommandResArr[0]).was;
|
||||
@ -111,7 +108,7 @@ function setHashAggParameters(memoryLimit, atLeast) {
|
||||
cmdObj: {
|
||||
setParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashAggMemoryCheckPerAdvanceAtLeast: atLeast,
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.gt(atLeastCommandResArr.length, 0, "Setting atLeast limit on primaries failed.");
|
||||
const oldAtLeast = assert.commandWorked(atLeastCommandResArr[0]).was;
|
||||
@ -129,10 +126,10 @@ function testWithHashAggMemoryLimit({pipeline, expectedCodes, canSpillToDisk, me
|
||||
}
|
||||
|
||||
testWithHashAggMemoryLimit({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true,
|
||||
memoryLimit: 1024
|
||||
memoryLimit: 1024,
|
||||
});
|
||||
|
||||
// Sorting with _id would use index which doesn't require external sort, so sort by 'random'
|
||||
@ -140,73 +137,70 @@ testWithHashAggMemoryLimit({
|
||||
test({
|
||||
pipeline: [{$sort: {random: 1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$sort: {bigStr: 1}}], // big key and value
|
||||
pipeline: [{$sort: {bigStr: 1}}], // big key and value
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
// Test that sort + large limit won't crash the server (SERVER-10136)
|
||||
test({
|
||||
pipeline: [{$sort: {bigStr: 1}}, {$limit: 1000 * 1000 * 1000}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
// Test combining two external sorts in both same and different orders.
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {_id: 1}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {_id: 1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {_id: -1}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {_id: -1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {random: 1}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {random: 1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$sort: {random: 1}}, {$group: {_id: '$_id', bigStr: {$first: '$bigStr'}}}],
|
||||
pipeline: [{$sort: {random: 1}}, {$group: {_id: "$_id", bigStr: {$first: "$bigStr"}}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
|
||||
// so may hit the group error code before we hit ExceededMemoryLimit.
|
||||
test({
|
||||
pipeline: [{$group: {_id: null, bigArray: {$push: '$bigStr'}}}],
|
||||
expectedCodes:
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false
|
||||
pipeline: [{$group: {_id: null, bigArray: {$push: "$bigStr"}}}],
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline:
|
||||
[{$group: {_id: null, bigArray: {$addToSet: {$concat: ['$bigStr', {$toString: "$_id"}]}}}}],
|
||||
expectedCodes:
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false
|
||||
pipeline: [{$group: {_id: null, bigArray: {$addToSet: {$concat: ["$bigStr", {$toString: "$_id"}]}}}}],
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false,
|
||||
});
|
||||
|
||||
for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) {
|
||||
for (const op of ["$firstN", "$lastN", "$minN", "$maxN", "$topN", "$bottomN"]) {
|
||||
jsTestLog("Testing op " + op);
|
||||
let spec = {n: 100000000};
|
||||
if (op === '$topN' || op === '$bottomN') {
|
||||
spec['sortBy'] = {random: 1};
|
||||
spec['output'] = '$bigStr';
|
||||
if (op === "$topN" || op === "$bottomN") {
|
||||
spec["sortBy"] = {random: 1};
|
||||
spec["output"] = "$bigStr";
|
||||
} else {
|
||||
// $firstN/$lastN/$minN/$maxN accept 'input'.
|
||||
spec['input'] = '$bigStr';
|
||||
spec["input"] = "$bigStr";
|
||||
}
|
||||
|
||||
// By grouping all of the entries in the same group, it is the case that we will either
|
||||
@ -216,18 +210,17 @@ for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) {
|
||||
// reduce the memory consumption of our group in this case.
|
||||
test({
|
||||
pipeline: [{$group: {_id: null, bigArray: {[op]: spec}}}],
|
||||
expectedCodes:
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false,
|
||||
});
|
||||
|
||||
// Because each group uses less than the configured limit, but cumulatively they exceed
|
||||
// the limit for $group, we only check for 'QueryExceededMemoryLimitNoDiskUseAllowed'
|
||||
// when disk use is disabled.
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigArray: {[op]: spec}}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigArray: {[op]: spec}}}],
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed],
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
}
|
||||
// don't leave large collection laying around
|
||||
@ -245,25 +238,23 @@ for (let i = 0; i < numGroups; ++i) {
|
||||
_id: counter++,
|
||||
a: i,
|
||||
b: 100 * i + j,
|
||||
c: 100 * i + j % 5,
|
||||
c: 100 * i + (j % 5),
|
||||
obj: {a: i, b: j},
|
||||
random: Math.random()
|
||||
random: Math.random(),
|
||||
};
|
||||
assert.commandWorked(coll.insert(doc));
|
||||
}
|
||||
}
|
||||
|
||||
function setHashGroupMemoryParameters(memoryLimit) {
|
||||
return setMemoryParamHelper(
|
||||
"internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", memoryLimit);
|
||||
return setMemoryParamHelper("internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", memoryLimit);
|
||||
}
|
||||
|
||||
// Runs a group query containing the given 'accumulator' after sorting the data by the given
|
||||
// 'sortInputBy' field. Then verifies that the query results are equal to 'expectedOutput'. If SBE
|
||||
// is enabled, also runs explain and checks that the execution stats show that spilling occurred.
|
||||
function testAccumulator({accumulator, sortInputBy, expectedOutput, ignoreArrayOrder = false}) {
|
||||
const pipeline =
|
||||
[{$sort: {[sortInputBy]: 1}}, {$group: {_id: "$a", acc: accumulator}}, {$sort: {_id: 1}}];
|
||||
const pipeline = [{$sort: {[sortInputBy]: 1}}, {$group: {_id: "$a", acc: accumulator}}, {$sort: {_id: 1}}];
|
||||
const results = coll.aggregate(pipeline).toArray();
|
||||
|
||||
if (ignoreArrayOrder) {
|
||||
@ -289,9 +280,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 100},
|
||||
{_id: 2, acc: 200},
|
||||
{_id: 3, acc: 300},
|
||||
{_id: 4, acc: 400}
|
||||
]
|
||||
|
||||
{_id: 4, acc: 400},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -302,8 +292,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 109},
|
||||
{_id: 2, acc: 209},
|
||||
{_id: 3, acc: 309},
|
||||
{_id: 4, acc: 409}
|
||||
]
|
||||
{_id: 4, acc: 409},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -314,8 +304,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 100},
|
||||
{_id: 2, acc: 200},
|
||||
{_id: 3, acc: 300},
|
||||
{_id: 4, acc: 400}
|
||||
]
|
||||
{_id: 4, acc: 400},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -326,8 +316,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 109},
|
||||
{_id: 2, acc: 209},
|
||||
{_id: 3, acc: 309},
|
||||
{_id: 4, acc: 409}
|
||||
]
|
||||
{_id: 4, acc: 409},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -338,8 +328,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 1045},
|
||||
{_id: 2, acc: 2045},
|
||||
{_id: 3, acc: 3045},
|
||||
{_id: 4, acc: 4045}
|
||||
]
|
||||
{_id: 4, acc: 4045},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -350,8 +340,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 104.5},
|
||||
{_id: 2, acc: 204.5},
|
||||
{_id: 3, acc: 304.5},
|
||||
{_id: 4, acc: 404.5}
|
||||
]
|
||||
{_id: 4, acc: 404.5},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -388,19 +378,19 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: {a: 1, b: 9}},
|
||||
{_id: 2, acc: {a: 2, b: 9}},
|
||||
{_id: 3, acc: {a: 3, b: 9}},
|
||||
{_id: 4, acc: {a: 4, b: 9}}
|
||||
{_id: 4, acc: {a: 4, b: 9}},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
(function() {
|
||||
const kMemLimit = 100;
|
||||
let oldMemSettings = setHashGroupMemoryParameters(kMemLimit);
|
||||
try {
|
||||
testSpillingForVariousAccumulators();
|
||||
} finally {
|
||||
setHashGroupMemoryParameters(oldMemSettings);
|
||||
}
|
||||
(function () {
|
||||
const kMemLimit = 100;
|
||||
let oldMemSettings = setHashGroupMemoryParameters(kMemLimit);
|
||||
try {
|
||||
testSpillingForVariousAccumulators();
|
||||
} finally {
|
||||
setHashGroupMemoryParameters(oldMemSettings);
|
||||
}
|
||||
})();
|
||||
|
||||
assert(coll.drop());
|
||||
@ -418,7 +408,9 @@ function setupCollections(localRecords, foreignRecords, foreignField) {
|
||||
|
||||
function setHashLookupParameters(memoryLimit) {
|
||||
return setMemoryParamHelper(
|
||||
"internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill", memoryLimit);
|
||||
"internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill",
|
||||
memoryLimit,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -433,23 +425,26 @@ function runTest_MultipleLocalForeignRecords({
|
||||
foreignRecords,
|
||||
foreignField,
|
||||
idsExpectedToMatch,
|
||||
spillsToDisk
|
||||
spillsToDisk,
|
||||
}) {
|
||||
setupCollections(localRecords, foreignRecords, foreignField);
|
||||
const pipeline = [{
|
||||
$lookup: {
|
||||
from: foreignColl.getName(),
|
||||
localField: localField,
|
||||
foreignField: foreignField,
|
||||
as: "matched"
|
||||
}}];
|
||||
const pipeline = [
|
||||
{
|
||||
$lookup: {
|
||||
from: foreignColl.getName(),
|
||||
localField: localField,
|
||||
foreignField: foreignField,
|
||||
as: "matched",
|
||||
},
|
||||
},
|
||||
];
|
||||
const results = localColl.aggregate(pipeline, {allowDiskUse: true}).toArray();
|
||||
const explain = localColl.explain('executionStats').aggregate(pipeline, {allowDiskUse: true});
|
||||
const explain = localColl.explain("executionStats").aggregate(pipeline, {allowDiskUse: true});
|
||||
const pipelineWasSplit = !!explain.splitPipeline;
|
||||
|
||||
// If sharding is enabled, or the pipeline was split, then '$lookup' is not pushed down to SBE.
|
||||
if (isSbeGroupLookupPushdownEnabled && !sharded && !pipelineWasSplit) {
|
||||
const hLookups = getSbePlanStages(explain, 'hash_lookup');
|
||||
const hLookups = getSbePlanStages(explain, "hash_lookup");
|
||||
assert.eq(hLookups.length, 1, explain);
|
||||
const hLookup = hLookups[0];
|
||||
assert(hLookup, explain);
|
||||
@ -465,12 +460,12 @@ function runTest_MultipleLocalForeignRecords({
|
||||
|
||||
// Extract matched foreign ids from the "matched" field.
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const matchedIds = results[i].matched.map(x => (x._id));
|
||||
const matchedIds = results[i].matched.map((x) => x._id);
|
||||
// Order of the elements within the arrays is not significant for 'assertArrayEq'.
|
||||
assertArrayEq({
|
||||
actual: matchedIds,
|
||||
expected: idsExpectedToMatch[i],
|
||||
extraErrorMsg: " **TEST** " + testDescription + " " + tojson(explain)
|
||||
extraErrorMsg: " **TEST** " + testDescription + " " + tojson(explain),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -494,7 +489,7 @@ function runHashLookupSpill({memoryLimit, spillsToDisk}) {
|
||||
foreignRecords: docs,
|
||||
foreignField: "a",
|
||||
idsExpectedToMatch: [[1, 2, 4]],
|
||||
spillsToDisk: spillsToDisk
|
||||
spillsToDisk: spillsToDisk,
|
||||
});
|
||||
})();
|
||||
|
||||
@ -526,20 +521,19 @@ function runHashLookupSpill({memoryLimit, spillsToDisk}) {
|
||||
foreignRecords: foreignDocs,
|
||||
foreignField: "b",
|
||||
idsExpectedToMatch: [[], [8], [9, 10], [11, 12, 13], [11, 12, 13], []],
|
||||
spillsToDisk: spillsToDisk
|
||||
spillsToDisk: spillsToDisk,
|
||||
});
|
||||
})();
|
||||
|
||||
return oldSettings;
|
||||
}
|
||||
|
||||
const oldMemSettings =
|
||||
assert
|
||||
.commandWorked(db.adminCommand({
|
||||
getParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1
|
||||
}))
|
||||
.internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
|
||||
const oldMemSettings = assert.commandWorked(
|
||||
db.adminCommand({
|
||||
getParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1,
|
||||
}),
|
||||
).internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
|
||||
|
||||
(function runAllDiskTest() {
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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"]);
|
||||
|
||||
@ -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"];
|
||||
})();
|
||||
|
||||
@ -9,7 +9,7 @@ for (let i = 0; i < 10; ++i) {
|
||||
}
|
||||
|
||||
const collation = {
|
||||
collation: {locale: "zh", backwards: false}
|
||||
collation: {locale: "zh", backwards: false},
|
||||
};
|
||||
|
||||
const firstResults = coll.aggregate([{$sort: {_id: 1}}], collation).toArray();
|
||||
@ -19,4 +19,4 @@ assert.commandWorked(coll.explain().aggregate([], collation));
|
||||
|
||||
const secondResults = coll.aggregate([{$sort: {_id: 1}}], collation).toArray();
|
||||
// Assert that the result didn't change after an explain helper is issued.
|
||||
assert.eq(firstResults, secondResults);
|
||||
assert.eq(firstResults, secondResults);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -21,10 +21,12 @@ assert.commandWorked(sourceColl.insert({_id: 1}));
|
||||
// Verifies that running the execution explains do not error, perform any writes, or create the
|
||||
// target collection.
|
||||
function assertExecutionExplainOk(writingStage, verbosity) {
|
||||
assert.commandWorked(db.runCommand({
|
||||
explain: {aggregate: sourceColl.getName(), pipeline: [writingStage], cursor: {}},
|
||||
verbosity: verbosity
|
||||
}));
|
||||
assert.commandWorked(
|
||||
db.runCommand({
|
||||
explain: {aggregate: sourceColl.getName(), pipeline: [writingStage], cursor: {}},
|
||||
verbosity: verbosity,
|
||||
}),
|
||||
);
|
||||
assert.eq(targetColl.find().itcount(), 0);
|
||||
// Verify that the collection was not created.
|
||||
const collectionList = db.getCollectionInfos({name: targetColl.getName()});
|
||||
@ -34,8 +36,7 @@ function assertExecutionExplainOk(writingStage, verbosity) {
|
||||
// Test that $out can be explained with 'queryPlanner' explain verbosity and does not perform
|
||||
// any writes.
|
||||
let explain = sourceColl.explain("queryPlanner").aggregate([{$out: targetColl.getName()}]);
|
||||
let explainedPipeline =
|
||||
getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
|
||||
let explainedPipeline = getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
|
||||
assert.eq(1, explainedPipeline.length);
|
||||
assert(explainedPipeline[0].$out);
|
||||
let outExplain = explainedPipeline[0];
|
||||
@ -50,13 +51,13 @@ assertExecutionExplainOk({$out: targetColl.getName()}, "executionStats");
|
||||
assertExecutionExplainOk({$out: targetColl.getName()}, "allPlansExecution");
|
||||
|
||||
// Test each $merge mode with each explain verbosity.
|
||||
withEachMergeMode(function({whenMatchedMode, whenNotMatchedMode}) {
|
||||
withEachMergeMode(function ({whenMatchedMode, whenNotMatchedMode}) {
|
||||
const mergeStage = {
|
||||
$merge: {
|
||||
into: targetColl.getName(),
|
||||
whenMatched: whenMatchedMode,
|
||||
whenNotMatched: whenNotMatchedMode
|
||||
}
|
||||
whenNotMatched: whenNotMatchedMode,
|
||||
},
|
||||
};
|
||||
|
||||
// Verify that execution explains don't error for $merge.
|
||||
@ -64,8 +65,7 @@ withEachMergeMode(function({whenMatchedMode, whenNotMatchedMode}) {
|
||||
assertExecutionExplainOk(mergeStage, "allPlansExecution");
|
||||
|
||||
const explain = sourceColl.explain("queryPlanner").aggregate([mergeStage]);
|
||||
let explainedPipeline =
|
||||
getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
|
||||
let explainedPipeline = getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
|
||||
assert.eq(1, explainedPipeline.length);
|
||||
assert(explainedPipeline[0].$merge);
|
||||
const mergeExplain = explainedPipeline[0];
|
||||
|
||||
@ -14,24 +14,21 @@ assert.eq(result[0]["y"], 0);
|
||||
// Confirm that we're not using DoubleDoubleSummation for $add expression with a set of double
|
||||
// values.
|
||||
let arr = [
|
||||
1.4831356930199802e-05, -3.121724665346865, 3041897608700.073, 1001318343149.7166,
|
||||
-1714.6229586696593, 1731390114894580.8, 6.256645803154374e-08, -107144114533844.25,
|
||||
-0.08839485091750919, -265119153.02185738, -0.02450615965231944, 0.0002684331017079073,
|
||||
32079040427.68358, -0.04733295911845742, 0.061381859083076085, -25329.59126796951,
|
||||
-0.0009567520620034965, -1553879364344.9932, -2.1101077525869814e-08, -298421079729.5547,
|
||||
0.03182394834273594, 22.201944843278916, -33.35667991109125, 11496013.960449915,
|
||||
-40652595.33210472, 3.8496066090328163, 2.5074042398147304e-08, -0.02208724071782122,
|
||||
-134211.37290639878, 0.17640433666616578, 4.463787499171126, 9.959669945399718,
|
||||
129265976.35224283, 1.5865526187526546e-07, -4746011.710555799, -712048598925.0789,
|
||||
582214206210.4034, 0.025236204812875362, 530078170.91147506, -14.865307666195053,
|
||||
1.6727994895185032e-05, -113386276.03121366, -6.135827207137054, 10644945799901.145,
|
||||
-100848907797.1582, 2.2404406961625282e-08, 1.315662618424494e-09, -0.832190208349044,
|
||||
-9.779323414999364, -546522170658.2997
|
||||
1.4831356930199802e-5, -3.121724665346865, 3041897608700.073, 1001318343149.7166, -1714.6229586696593,
|
||||
1731390114894580.8, 6.256645803154374e-8, -107144114533844.25, -0.08839485091750919, -265119153.02185738,
|
||||
-0.02450615965231944, 0.0002684331017079073, 32079040427.68358, -0.04733295911845742, 0.061381859083076085,
|
||||
-25329.59126796951, -0.0009567520620034965, -1553879364344.9932, -2.1101077525869814e-8, -298421079729.5547,
|
||||
0.03182394834273594, 22.201944843278916, -33.35667991109125, 11496013.960449915, -40652595.33210472,
|
||||
3.8496066090328163, 2.5074042398147304e-8, -0.02208724071782122, -134211.37290639878, 0.17640433666616578,
|
||||
4.463787499171126, 9.959669945399718, 129265976.35224283, 1.5865526187526546e-7, -4746011.710555799,
|
||||
-712048598925.0789, 582214206210.4034, 0.025236204812875362, 530078170.91147506, -14.865307666195053,
|
||||
1.6727994895185032e-5, -113386276.03121366, -6.135827207137054, 10644945799901.145, -100848907797.1582,
|
||||
2.2404406961625282e-8, 1.315662618424494e-9, -0.832190208349044, -9.779323414999364, -546522170658.2997,
|
||||
];
|
||||
let doc = {_id: 0};
|
||||
let i = 0;
|
||||
let queryArr = [];
|
||||
arr.forEach(num => {
|
||||
arr.forEach((num) => {
|
||||
i++;
|
||||
doc[`f_${i}`] = num;
|
||||
queryArr.push(`$f_${i}`);
|
||||
@ -43,7 +40,10 @@ assert.commandWorked(coll.insert(doc));
|
||||
let addResult = coll.aggregate([{$project: {add: {$add: queryArr}}}]).toArray();
|
||||
let sumResult = coll.aggregate([{$project: {sum: {$sum: queryArr}}}]).toArray();
|
||||
assert.neq(addResult[0]["add"], sumResult[0]["sum"]);
|
||||
assert.eq(addResult[0]["add"], arr.reduce((a, b) => a + b));
|
||||
assert.eq(
|
||||
addResult[0]["add"],
|
||||
arr.reduce((a, b) => a + b),
|
||||
);
|
||||
|
||||
assert.eq(true, coll.drop());
|
||||
// Doubles are rounded to int64 when added to Date
|
||||
@ -52,17 +52,12 @@ assert.commandWorked(coll.insert({_id: 1, lhs: new Date(1683794065002), rhs: 1.4
|
||||
assert.commandWorked(coll.insert({_id: 2, lhs: new Date(1683794065002), rhs: 1.5}));
|
||||
assert.commandWorked(coll.insert({_id: 3, lhs: new Date(1683794065002), rhs: 1.7}));
|
||||
// Decimals are rounded to int64, when tie rounded to even, when added to Date
|
||||
assert.commandWorked(
|
||||
coll.insert({_id: 4, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.4")}));
|
||||
assert.commandWorked(
|
||||
coll.insert({_id: 5, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.5")}));
|
||||
assert.commandWorked(
|
||||
coll.insert({_id: 6, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.7")}));
|
||||
assert.commandWorked(
|
||||
coll.insert({_id: 7, lhs: new Date(1683794065002), rhs: new NumberDecimal("2.5")}));
|
||||
assert.commandWorked(coll.insert({_id: 4, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.4")}));
|
||||
assert.commandWorked(coll.insert({_id: 5, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.5")}));
|
||||
assert.commandWorked(coll.insert({_id: 6, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.7")}));
|
||||
assert.commandWorked(coll.insert({_id: 7, lhs: new Date(1683794065002), rhs: new NumberDecimal("2.5")}));
|
||||
|
||||
let result1 =
|
||||
coll.aggregate([{$project: {sum: {$add: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}]).toArray();
|
||||
let result1 = coll.aggregate([{$project: {sum: {$add: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}]).toArray();
|
||||
assert.eq(result1[0].sum, new Date(1683794065003));
|
||||
assert.eq(result1[1].sum, new Date(1683794065003));
|
||||
assert.eq(result1[2].sum, new Date(1683794065004));
|
||||
@ -74,12 +69,16 @@ assert.eq(result1[7].sum, new Date(1683794065004));
|
||||
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([{
|
||||
_id: 0,
|
||||
veryBigPositiveLong: NumberLong("9223372036854775806"),
|
||||
veryBigPositiveDouble: 9223372036854775806,
|
||||
veryBigPositiveDecimal: NumberDecimal("9223372036854775806")
|
||||
}]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{
|
||||
_id: 0,
|
||||
veryBigPositiveLong: NumberLong("9223372036854775806"),
|
||||
veryBigPositiveDouble: 9223372036854775806,
|
||||
veryBigPositiveDecimal: NumberDecimal("9223372036854775806"),
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
let pipeline = [{$project: {res: {$add: [new Date(10), "$veryBigPositiveLong"]}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow");
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -9,122 +9,128 @@ function getResultOfExpression(expr) {
|
||||
return resultArray[0].computed;
|
||||
}
|
||||
|
||||
assert.commandWorked(coll.insert({
|
||||
_id: 0,
|
||||
decimalVal: NumberDecimal("819.5359123621083"),
|
||||
doubleVal: 819.536,
|
||||
int64Val: NumberLong(820),
|
||||
int32Val: NumberInt(820),
|
||||
dateVal: ISODate("2019-01-30T07:30:10.137Z"),
|
||||
overflowDecimal: NumberDecimal("1e5000"),
|
||||
overflowDouble: 1e1000,
|
||||
overflowInt64: NumberLong("9223372036854775807"),
|
||||
nanDouble: NaN,
|
||||
nanDecimal: NumberDecimal("NaN"),
|
||||
}));
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 0,
|
||||
decimalVal: NumberDecimal("819.5359123621083"),
|
||||
doubleVal: 819.536,
|
||||
int64Val: NumberLong(820),
|
||||
int32Val: NumberInt(820),
|
||||
dateVal: ISODate("2019-01-30T07:30:10.137Z"),
|
||||
overflowDecimal: NumberDecimal("1e5000"),
|
||||
overflowDouble: 1e1000,
|
||||
overflowInt64: NumberLong("9223372036854775807"),
|
||||
nanDouble: NaN,
|
||||
nanDecimal: NumberDecimal("NaN"),
|
||||
}),
|
||||
);
|
||||
|
||||
// Adding a Decimal128 value to a date literal.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", ISODate("2019-01-30T07:30:10.137Z")]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [ISODate("2019-01-30T07:30:10.137Z"), "$decimalVal"]}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", ISODate("2019-01-30T07:30:10.137Z")]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [ISODate("2019-01-30T07:30:10.137Z"), "$decimalVal"]}),
|
||||
);
|
||||
|
||||
// Adding a Decimal128 literal to a date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", NumberDecimal("819.5359123621083")]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [NumberDecimal("819.5359123621083"), "$dateVal"]}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", NumberDecimal("819.5359123621083")]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [NumberDecimal("819.5359123621083"), "$dateVal"]}),
|
||||
);
|
||||
|
||||
// Adding a Decimal128 value to a date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$decimalVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$decimalVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$decimalVal", "$dateVal"]}));
|
||||
|
||||
// Adding a double value to a date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$doubleVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$doubleVal", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$doubleVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$doubleVal", "$dateVal"]}));
|
||||
|
||||
// Adding an int64_t value to date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$int64Val", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int64Val", "$dateVal"]}));
|
||||
|
||||
// Adding an int32_t value to date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$int32Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$int32Val", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$int32Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int32Val", "$dateVal"]}));
|
||||
|
||||
// Addition with a date and multiple values of differing data types.
|
||||
assert.eq(ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:12.596Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$doubleVal", "$int64Val", "$dateVal"]}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:12.596Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$doubleVal", "$int64Val", "$dateVal"]}),
|
||||
);
|
||||
// The result of an addition must remain in the range of int64_t in order to convert back to a Date;
|
||||
// an overflow into the domain of double-precision floating point numbers triggers a query-fatal
|
||||
// error.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble"]}), ErrorCodes.Overflow);
|
||||
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowInt64"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowInt64"]}), ErrorCodes.Overflow);
|
||||
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$int64Val", "$overflowDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// An overflow into the domain of Decimal128 results in an overflow exception.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}), ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble", "$overflowDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// Adding a double-typed NaN to a date value.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}), ErrorCodes.Overflow);
|
||||
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}), ErrorCodes.Overflow);
|
||||
|
||||
// An NaN Decimal128 added to date results in an overflow exception.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}), ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}), ErrorCodes.Overflow);
|
||||
|
||||
// Addition with a date, a double-typed NaN, and a third value.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}),
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// Addition with a date, and both types of NaN.
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$nanDouble", "$nanDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// Throw error when there're two or more date in $add.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", 1, "$dateVal"]}), 4974202);
|
||||
|
||||
// Test very large long and verify that we're maintaining the precision of long arithmetic.
|
||||
// 2397083434877565865 and 239708343487756586 both cast to the same double value from longs
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.958Z"), getResultOfExpression({
|
||||
$add: [
|
||||
"$dateVal",
|
||||
NumberLong("2397083434877565865"),
|
||||
"$doubleVal",
|
||||
NumberLong("-2397083434877565864")
|
||||
]
|
||||
}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.958Z"),
|
||||
getResultOfExpression({
|
||||
$add: ["$dateVal", NumberLong("2397083434877565865"), "$doubleVal", NumberLong("-2397083434877565864")],
|
||||
}),
|
||||
);
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
*/
|
||||
const coll = db.all_elements_true;
|
||||
coll.drop();
|
||||
assert.commandWorked(
|
||||
coll.insert({_id: 0, allTrue: [true, true], someTrue: [true, false], noneTrue: [0, false]}));
|
||||
assert.commandWorked(coll.insert({_id: 0, allTrue: [true, true], someTrue: [true, false], noneTrue: [0, false]}));
|
||||
|
||||
function testOp(expression, expected) {
|
||||
const results = coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray();
|
||||
@ -20,4 +19,4 @@ testOp({$allElementsTrue: {$literal: [true, 0]}}, false);
|
||||
testOp({$allElementsTrue: {$literal: [true, 1, false]}}, false);
|
||||
testOp({$allElementsTrue: "$allTrue"}, true);
|
||||
testOp({$allElementsTrue: "$someTrue"}, false);
|
||||
testOp({$allElementsTrue: "$noneTrue"}, false);
|
||||
testOp({$allElementsTrue: "$noneTrue"}, false);
|
||||
|
||||
@ -5,18 +5,20 @@ import "jstests/libs/query/sbe_assert_error_override.js";
|
||||
|
||||
const coll = db.any_element_true;
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({
|
||||
_id: 0,
|
||||
allTrue: [true, true],
|
||||
someTrue: [true, false],
|
||||
noneTrue: [0, false],
|
||||
nonArray: 1,
|
||||
nullInput: [null],
|
||||
undefinedInput: [undefined],
|
||||
undefinedTrue: [undefined, true],
|
||||
nullTrue: [null, true],
|
||||
empty: []
|
||||
}));
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 0,
|
||||
allTrue: [true, true],
|
||||
someTrue: [true, false],
|
||||
noneTrue: [0, false],
|
||||
nonArray: 1,
|
||||
nullInput: [null],
|
||||
undefinedInput: [undefined],
|
||||
undefinedTrue: [undefined, true],
|
||||
nullTrue: [null, true],
|
||||
empty: [],
|
||||
}),
|
||||
);
|
||||
|
||||
function testOp(expression, expected) {
|
||||
const results = coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray();
|
||||
@ -27,8 +29,7 @@ function testOp(expression, expected) {
|
||||
}
|
||||
|
||||
function assertThrows(expression) {
|
||||
const error =
|
||||
assert.throws(() => coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray());
|
||||
const error = assert.throws(() => coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray());
|
||||
assert.commandFailedWithCode(error, 5159200);
|
||||
}
|
||||
|
||||
|
||||
@ -3,25 +3,25 @@
|
||||
const coll = db.arith_overflow;
|
||||
|
||||
function runTest(operator, expectedResults) {
|
||||
const result =
|
||||
coll.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
|
||||
.toArray()
|
||||
.map(r => r.res);
|
||||
const result = coll
|
||||
.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
|
||||
.toArray()
|
||||
.map((r) => r.res);
|
||||
assert.eq(result, expectedResults);
|
||||
}
|
||||
|
||||
// $add
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e+9), rhs: NumberInt(2e+9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e+18), rhs: NumberLong(9e+18)}));
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e9), rhs: NumberInt(2e9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e18), rhs: NumberLong(9e18)}));
|
||||
|
||||
runTest("$add", [NumberLong(4e+9), 1.8e+19]);
|
||||
runTest("$add", [NumberLong(4e9), 1.8e19]);
|
||||
|
||||
// $subtract
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e+9), rhs: NumberInt(-2e+9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e+18), rhs: NumberLong(-9e+18)}));
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e9), rhs: NumberInt(-2e9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e18), rhs: NumberLong(-9e18)}));
|
||||
|
||||
runTest("$subtract", [NumberLong(4e+9), 1.8e+19]);
|
||||
runTest("$subtract", [NumberLong(4e9), 1.8e19]);
|
||||
// $multiply uses same arguments
|
||||
runTest("$multiply", [NumberLong(-4e+18), -8.1e+37]);
|
||||
runTest("$multiply", [NumberLong(-4e18), -8.1e37]);
|
||||
|
||||
@ -6,212 +6,239 @@
|
||||
* requires_pipeline_optimization,
|
||||
* ]
|
||||
*/
|
||||
import {
|
||||
assertArrayEq,
|
||||
getExplainedPipelineFromAggregation
|
||||
} from "jstests/aggregation/extras/utils.js";
|
||||
import {assertArrayEq, getExplainedPipelineFromAggregation} from "jstests/aggregation/extras/utils.js";
|
||||
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
||||
|
||||
// TODO(SERVER-18047): Remove database creation once explain behavior is unified between replica
|
||||
// sets and sharded clusters.
|
||||
if (FixtureHelpers.isMongos(db) || TestData.testingReplicaSetEndpoint) {
|
||||
// Create database
|
||||
assert.commandWorked(db.adminCommand({'enableSharding': db.getName()}));
|
||||
assert.commandWorked(db.adminCommand({"enableSharding": db.getName()}));
|
||||
}
|
||||
|
||||
(function() {
|
||||
const collName = jsTest.name();
|
||||
const coll = db[collName];
|
||||
coll.drop();
|
||||
(function () {
|
||||
const collName = jsTest.name();
|
||||
const coll = db[collName];
|
||||
coll.drop();
|
||||
|
||||
const $x = "$x"; // fieldpath to "block" constant folding
|
||||
const $x = "$x"; // fieldpath to "block" constant folding
|
||||
|
||||
/**
|
||||
* Verify constant folding with explain output.
|
||||
* @param {(number | number[])[]} input Input arithmetic parameters, optionally nested deeply.
|
||||
* @param {number[] | number} expectedOutput Expected output parameters after constant folding, or a
|
||||
* scalar if the operation was calculated statically.
|
||||
* @param {string} message error message
|
||||
* @returns true if the explain output matches expectedOutput, and an assertion failure otherwise.
|
||||
*/
|
||||
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
|
||||
const buildExpressionFromArguments = (arr, op) => {
|
||||
if (Array.isArray(arr)) {
|
||||
return {[op]: arr.map(elt => buildExpressionFromArguments(elt, op))};
|
||||
} else if (typeof arr === 'string' || arr instanceof String) {
|
||||
return arr;
|
||||
} else {
|
||||
return {$const: arr};
|
||||
}
|
||||
};
|
||||
const expected = buildExpressionFromArguments(expectedOutput, op);
|
||||
/**
|
||||
* Verify constant folding with explain output.
|
||||
* @param {(number | number[])[]} input Input arithmetic parameters, optionally nested deeply.
|
||||
* @param {number[] | number} expectedOutput Expected output parameters after constant folding, or a
|
||||
* scalar if the operation was calculated statically.
|
||||
* @param {string} message error message
|
||||
* @returns true if the explain output matches expectedOutput, and an assertion failure otherwise.
|
||||
*/
|
||||
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
|
||||
const buildExpressionFromArguments = (arr, op) => {
|
||||
if (Array.isArray(arr)) {
|
||||
return {[op]: arr.map((elt) => buildExpressionFromArguments(elt, op))};
|
||||
} else if (typeof arr === "string" || arr instanceof String) {
|
||||
return arr;
|
||||
} else {
|
||||
return {$const: arr};
|
||||
}
|
||||
};
|
||||
const expected = buildExpressionFromArguments(expectedOutput, op);
|
||||
|
||||
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
|
||||
{$group: {_id: buildExpressionFromArguments(input, op), sum: {$sum: 1}}},
|
||||
]);
|
||||
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
|
||||
{$group: {_id: buildExpressionFromArguments(input, op), sum: {$sum: 1}}},
|
||||
]);
|
||||
|
||||
assert(processedPipeline[0] && processedPipeline[0].$group);
|
||||
assert.eq(processedPipeline[0].$group._id, expected, message);
|
||||
assert(processedPipeline[0] && processedPipeline[0].$group);
|
||||
assert.eq(processedPipeline[0].$group._id, expected, message);
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function assertConstantFoldingResults(input, addOutput, multiplyOutput, message) {
|
||||
assertConstantFoldingResultForOp("$add", input, addOutput, message);
|
||||
assertConstantFoldingResultForOp("$multiply", input, multiplyOutput, message);
|
||||
}
|
||||
function assertConstantFoldingResults(input, addOutput, multiplyOutput, message) {
|
||||
assertConstantFoldingResultForOp("$add", input, addOutput, message);
|
||||
assertConstantFoldingResultForOp("$multiply", input, multiplyOutput, message);
|
||||
}
|
||||
|
||||
// Totally fold constants.
|
||||
assertConstantFoldingResults([1, 2, 3], 6, 6, "All constants should fold.");
|
||||
assertConstantFoldingResults(
|
||||
[[1, 2], 3, 4, 5], 15, 120, "Nested operations with all constants should be folded away.");
|
||||
// Totally fold constants.
|
||||
assertConstantFoldingResults([1, 2, 3], 6, 6, "All constants should fold.");
|
||||
assertConstantFoldingResults(
|
||||
[[1, 2], 3, 4, 5],
|
||||
15,
|
||||
120,
|
||||
"Nested operations with all constants should be folded away.",
|
||||
);
|
||||
|
||||
// Left-associative test cases.
|
||||
assertConstantFoldingResults([1, 2, $x],
|
||||
[3, $x],
|
||||
[2, $x],
|
||||
"Constants should fold left-to-right before the first non-constant.");
|
||||
assertConstantFoldingResults(
|
||||
[$x, 1, 2],
|
||||
[$x, 1, 2],
|
||||
[$x, 1, 2],
|
||||
"Constants should not fold left-to-right after the first non-constant.");
|
||||
assertConstantFoldingResults(
|
||||
[1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
|
||||
// Left-associative test cases.
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, $x],
|
||||
[3, $x],
|
||||
[2, $x],
|
||||
"Constants should fold left-to-right before the first non-constant.",
|
||||
);
|
||||
assertConstantFoldingResults(
|
||||
[$x, 1, 2],
|
||||
[$x, 1, 2],
|
||||
[$x, 1, 2],
|
||||
"Constants should not fold left-to-right after the first non-constant.",
|
||||
);
|
||||
assertConstantFoldingResults([1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
|
||||
|
||||
assertConstantFoldingResults([5, 2, $x, 3, 4],
|
||||
[7, $x, 3, 4],
|
||||
[10, $x, 3, 4],
|
||||
"Constants should fold up until a non-constant.");
|
||||
assertConstantFoldingResults(
|
||||
[5, 2, $x, 3, 4],
|
||||
[7, $x, 3, 4],
|
||||
[10, $x, 3, 4],
|
||||
"Constants should fold up until a non-constant.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults([$x, 1, 2, 3],
|
||||
[$x, 1, 2, 3],
|
||||
[$x, 1, 2, 3],
|
||||
"Non-constant at start of operand list blocks folding constants.");
|
||||
assertConstantFoldingResults(
|
||||
[$x, 1, 2, 3],
|
||||
[$x, 1, 2, 3],
|
||||
[$x, 1, 2, 3],
|
||||
"Non-constant at start of operand list blocks folding constants.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults([[1, 2, $x], 3, 4, $x, 5],
|
||||
[[3, $x], 3, 4, $x, 5],
|
||||
[[2, $x], 3, 4, $x, 5],
|
||||
"Nested operation folds as expected.");
|
||||
assertConstantFoldingResults(
|
||||
[[1, 2, $x], 3, 4, $x, 5],
|
||||
[[3, $x], 3, 4, $x, 5],
|
||||
[[2, $x], 3, 4, $x, 5],
|
||||
"Nested operation folds as expected.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, [1, 2, $x], 3, 4, $x, 5],
|
||||
[3, [3, $x], 3, 4, $x, 5],
|
||||
[2, [2, $x], 3, 4, $x, 5],
|
||||
"Nested operation folds along with outer operation following left-associative rules.");
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, [1, 2, $x], 3, 4, $x, 5],
|
||||
[3, [3, $x], 3, 4, $x, 5],
|
||||
[2, [2, $x], 3, 4, $x, 5],
|
||||
"Nested operation folds along with outer operation following left-associative rules.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, [1, 2, $x, 5, 6], 3, 4, 5],
|
||||
[3, [3, $x, 5, 6], 3, 4, 5],
|
||||
[2, [2, $x, 5, 6], 3, 4, 5],
|
||||
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.");
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, [1, 2, $x, 5, 6], 3, 4, 5],
|
||||
[3, [3, $x, 5, 6], 3, 4, 5],
|
||||
[2, [2, $x, 5, 6], 3, 4, 5],
|
||||
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, $x, 4, [1, 2, $x, 5, 6], 3, 4, 5],
|
||||
[3, $x, 4, [3, $x, 5, 6], 3, 4, 5],
|
||||
[2, $x, 4, [2, $x, 5, 6], 3, 4, 5],
|
||||
"Nested operation folds along and even when fieldpath exists before it.");
|
||||
}());
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, $x, 4, [1, 2, $x, 5, 6], 3, 4, 5],
|
||||
[3, $x, 4, [3, $x, 5, 6], 3, 4, 5],
|
||||
[2, $x, 4, [2, $x, 5, 6], 3, 4, 5],
|
||||
"Nested operation folds along and even when fieldpath exists before it.",
|
||||
);
|
||||
})();
|
||||
|
||||
// Mixing $add and $multiply
|
||||
(function() {
|
||||
const collName = jsTest.name();
|
||||
const coll = db[collName];
|
||||
coll.drop();
|
||||
(function () {
|
||||
const collName = jsTest.name();
|
||||
const coll = db[collName];
|
||||
coll.drop();
|
||||
|
||||
const assertFoldedResult = (expr, expected, message) => {
|
||||
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
|
||||
{$group: {_id: expr, sum: {$sum: 1}}},
|
||||
]);
|
||||
const wrapLits = (arr) => {
|
||||
if (Array.isArray(arr)) {
|
||||
return arr.map(wrapLits);
|
||||
} else if (typeof arr === 'object') {
|
||||
let out = {};
|
||||
Object.keys(arr).forEach(k => {
|
||||
out[k] = wrapLits(arr[k]);
|
||||
});
|
||||
return out;
|
||||
} else if (typeof arr === 'string' || arr instanceof String) {
|
||||
return arr;
|
||||
} else {
|
||||
return {$const: arr};
|
||||
}
|
||||
const assertFoldedResult = (expr, expected, message) => {
|
||||
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
|
||||
{$group: {_id: expr, sum: {$sum: 1}}},
|
||||
]);
|
||||
const wrapLits = (arr) => {
|
||||
if (Array.isArray(arr)) {
|
||||
return arr.map(wrapLits);
|
||||
} else if (typeof arr === "object") {
|
||||
let out = {};
|
||||
Object.keys(arr).forEach((k) => {
|
||||
out[k] = wrapLits(arr[k]);
|
||||
});
|
||||
return out;
|
||||
} else if (typeof arr === "string" || arr instanceof String) {
|
||||
return arr;
|
||||
} else {
|
||||
return {$const: arr};
|
||||
}
|
||||
};
|
||||
|
||||
assert(processedPipeline[0] && processedPipeline[0].$group);
|
||||
assert.eq(processedPipeline[0].$group._id, wrapLits(expected), message);
|
||||
};
|
||||
|
||||
assert(processedPipeline[0] && processedPipeline[0].$group);
|
||||
assert.eq(processedPipeline[0].$group._id, wrapLits(expected), message);
|
||||
};
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
{$add: [3, {$multiply: [12, "$x", 5, 6]}, 6, 7]},
|
||||
"Multiply inside add will fold as much as it can.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
{$add: [3, {$multiply: [12, "$x", 5, 6]}, 6, 7]},
|
||||
"Multiply inside add will fold as much as it can.");
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
{$multiply: [2, {$add: [7, "$x", 5, 6]}, 6, 7]},
|
||||
"Add inside multiply will fold as much as it can.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
{$multiply: [2, {$add: [7, "$x", 5, 6]}, 6, 7]},
|
||||
"Add inside multiply will fold as much as it can.");
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
{$add: [369, "$x", 7, 8]},
|
||||
"Multiply without fieldpath will fold away and add will continue folding.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
{$add: [369, "$x", 7, 8]},
|
||||
"Multiply without fieldpath will fold away and add will continue folding.");
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
{$multiply: [216, "$x", 7, 8]},
|
||||
"Add without fieldpath will fold away and multiply will continue folding.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
{$multiply: [216, "$x", 7, 8]},
|
||||
"Add without fieldpath will fold away and multiply will continue folding.");
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, "$x", {$multiply: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
||||
{$add: [3, "$x", {$multiply: [12, "$x", 5, 6]}, 6, 7, 8]},
|
||||
"Constant folding nested $multiply proceeds even after outer $add stops folding.",
|
||||
);
|
||||
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, "$x", {$multiply: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
||||
{$add: [3, "$x", {$multiply: [12, "$x", 5, 6]}, 6, 7, 8]},
|
||||
"Constant folding nested $multiply proceeds even after outer $add stops folding.");
|
||||
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, "$x", {$add: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
||||
{$multiply: [2, "$x", {$add: [7, "$x", 5, 6]}, 6, 7, 8]},
|
||||
"Constant folding nested $add proceeds even after outer multiply stops folding.");
|
||||
}());
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, "$x", {$add: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
||||
{$multiply: [2, "$x", {$add: [7, "$x", 5, 6]}, 6, 7, 8]},
|
||||
"Constant folding nested $add proceeds even after outer multiply stops folding.",
|
||||
);
|
||||
})();
|
||||
|
||||
// Regression tests for BFs related to SERVER-63099.
|
||||
(function() {
|
||||
const coll = db[jsTest.name()];
|
||||
coll.drop();
|
||||
(function () {
|
||||
const coll = db[jsTest.name()];
|
||||
coll.drop();
|
||||
|
||||
const makePipeline = (id) => [{$group: {_id: id, sum: {$sum: 1}}}];
|
||||
const makePipeline = (id) => [{$group: {_id: id, sum: {$sum: 1}}}];
|
||||
|
||||
// Non-optimized comparisons -- make sure that non-optimized pipelines will give the same result as
|
||||
// optimized ones.
|
||||
// This is a regression test for BF-24149.
|
||||
coll.insert({_id: 0, v: NumberDecimal("917.6875119062092")});
|
||||
coll.insert({_id: 1, v: NumberDecimal("927.3345924210555")});
|
||||
// Non-optimized comparisons -- make sure that non-optimized pipelines will give the same result as
|
||||
// optimized ones.
|
||||
// This is a regression test for BF-24149.
|
||||
coll.insert({_id: 0, v: NumberDecimal("917.6875119062092")});
|
||||
coll.insert({_id: 1, v: NumberDecimal("927.3345924210555")});
|
||||
|
||||
const idToString = d => d._id.toJSON().$numberDecimal;
|
||||
const idToString = (d) => d._id.toJSON().$numberDecimal;
|
||||
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
|
||||
.toArray()
|
||||
.map(idToString),
|
||||
expected: [
|
||||
"915242528741.9469524422272990976000",
|
||||
"905721242210.0453137831269007622941",
|
||||
]
|
||||
});
|
||||
assertArrayEq({
|
||||
actual: coll
|
||||
.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
|
||||
.toArray()
|
||||
.map(idToString),
|
||||
expected: ["915242528741.9469524422272990976000", "905721242210.0453137831269007622941"],
|
||||
});
|
||||
|
||||
// BF-24945
|
||||
coll.drop();
|
||||
coll.insert({x: 0, y: 4.1});
|
||||
assert(numberDecimalsEqual(
|
||||
coll
|
||||
.aggregate(makePipeline(
|
||||
{$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$x", "$y"]}))
|
||||
.toArray()[0]
|
||||
._id,
|
||||
NumberDecimal(0)));
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate(makePipeline({
|
||||
$multiply:
|
||||
[NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$y", "$x"]
|
||||
}))
|
||||
.toArray()
|
||||
.map(idToString),
|
||||
expected: ["NaN"]
|
||||
});
|
||||
}());
|
||||
// BF-24945
|
||||
coll.drop();
|
||||
coll.insert({x: 0, y: 4.1});
|
||||
assert(
|
||||
numberDecimalsEqual(
|
||||
coll
|
||||
.aggregate(
|
||||
makePipeline({
|
||||
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$x", "$y"],
|
||||
}),
|
||||
)
|
||||
.toArray()[0]._id,
|
||||
NumberDecimal(0),
|
||||
),
|
||||
);
|
||||
assertArrayEq({
|
||||
actual: coll
|
||||
.aggregate(
|
||||
makePipeline({
|
||||
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$y", "$x"],
|
||||
}),
|
||||
)
|
||||
.toArray()
|
||||
.map(idToString),
|
||||
expected: ["NaN"],
|
||||
});
|
||||
})();
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -26,4 +26,4 @@ assertArray([null, null]);
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({}));
|
||||
let result = coll.aggregate([{$project: {out: []}}]).toArray()[0].out;
|
||||
assert.eq(result, []);
|
||||
assert.eq(result, []);
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -6,21 +6,21 @@ import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
|
||||
const coll = db.expression_binarySize;
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, x: ""},
|
||||
{_id: 1, x: "abc"},
|
||||
{_id: 2, x: "ab\0c"},
|
||||
{_id: 3, x: "abc\0"},
|
||||
{_id: 4, x: BinData(0, "")},
|
||||
{_id: 5, x: BinData(0, "1234")},
|
||||
{_id: 6, x: null},
|
||||
{_id: 7},
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, x: ""},
|
||||
{_id: 1, x: "abc"},
|
||||
{_id: 2, x: "ab\0c"},
|
||||
{_id: 3, x: "abc\0"},
|
||||
{_id: 4, x: BinData(0, "")},
|
||||
{_id: 5, x: BinData(0, "1234")},
|
||||
{_id: 6, x: null},
|
||||
{_id: 7},
|
||||
]),
|
||||
);
|
||||
|
||||
const result =
|
||||
coll.aggregate([{$sort: {_id: 1}}, {$addFields: {s: {$binarySize: "$x"}}}]).toArray();
|
||||
const result = coll.aggregate([{$sort: {_id: 1}}, {$addFields: {s: {$binarySize: "$x"}}}]).toArray();
|
||||
assert.eq(result, [
|
||||
|
||||
{_id: 0, x: "", s: 0},
|
||||
{_id: 1, x: "abc", s: 3},
|
||||
// Javascript strings and BSON strings can contain '\0', so both of these have length 4.
|
||||
@ -38,4 +38,4 @@ assert.eq(result, [
|
||||
|
||||
// $binarySize only accepts strings and BinData.
|
||||
assert.commandWorked(coll.insert({x: 42}));
|
||||
assertErrorCode(coll, {$project: {s: {$binarySize: "$x"}}}, 51276);
|
||||
assertErrorCode(coll, {$project: {s: {$binarySize: "$x"}}}, 51276);
|
||||
|
||||
@ -9,19 +9,22 @@ const collName = jsTestName();
|
||||
const coll = db[collName];
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, a: NumberInt(0), b: NumberInt(127), c: [NumberInt(0), NumberInt(127)]},
|
||||
{_id: 1, a: NumberInt(1), b: NumberInt(2), c: [NumberInt(1), NumberInt(2)]},
|
||||
{_id: 2, a: NumberInt(2), b: NumberInt(3), c: [NumberInt(2), NumberInt(3)]},
|
||||
{_id: 3, a: NumberInt(3), b: NumberInt(5), c: [NumberInt(3), NumberInt(5)]},
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, a: NumberInt(0), b: NumberInt(127), c: [NumberInt(0), NumberInt(127)]},
|
||||
{_id: 1, a: NumberInt(1), b: NumberInt(2), c: [NumberInt(1), NumberInt(2)]},
|
||||
{_id: 2, a: NumberInt(2), b: NumberInt(3), c: [NumberInt(2), NumberInt(3)]},
|
||||
{_id: 3, a: NumberInt(3), b: NumberInt(5), c: [NumberInt(3), NumberInt(5)]},
|
||||
]),
|
||||
);
|
||||
|
||||
function runAndAssert(expression, expectedResult) {
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
|
||||
.toArray()
|
||||
.map(doc => doc.r),
|
||||
expected: expectedResult
|
||||
actual: coll
|
||||
.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
|
||||
.toArray()
|
||||
.map((doc) => doc.r),
|
||||
expected: expectedResult,
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,60 +33,74 @@ runAndAssert("$bitOr", [127, 3, 3, 7]);
|
||||
runAndAssert("$bitXor", [127, 3, 1, 6]);
|
||||
|
||||
for (const operator of ["$bitAnd", "$bitOr", "$bitXor"]) {
|
||||
for (const operand
|
||||
of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
$project: {
|
||||
r: {[operator]: ["$a", operand]},
|
||||
}
|
||||
}]
|
||||
}),
|
||||
ErrorCodes.TypeMismatch);
|
||||
for (const operand of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {[operator]: ["$a", operand]},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
ErrorCodes.TypeMismatch,
|
||||
);
|
||||
}
|
||||
for (const argument of ["$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
$project: {
|
||||
r: {[operator]: argument},
|
||||
}
|
||||
}]
|
||||
}),
|
||||
ErrorCodes.TypeMismatch);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {[operator]: argument},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
ErrorCodes.TypeMismatch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate([
|
||||
{$project: {r: {$bitNot: "$a"}}},
|
||||
])
|
||||
.toArray()
|
||||
.map(doc => doc.r),
|
||||
expected: [-1, -2, -3, -4]
|
||||
actual: coll
|
||||
.aggregate([{$project: {r: {$bitNot: "$a"}}}])
|
||||
.toArray()
|
||||
.map((doc) => doc.r),
|
||||
expected: [-1, -2, -3, -4],
|
||||
});
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
$project: {
|
||||
r: {$bitNot: 12.5},
|
||||
}
|
||||
}]
|
||||
}),
|
||||
ErrorCodes.TypeMismatch);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {$bitNot: 12.5},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
ErrorCodes.TypeMismatch,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
$project: {
|
||||
r: {$bitNot: ["$a", "$b"]},
|
||||
}
|
||||
}]
|
||||
}),
|
||||
16020); // Error for incorrect number of arguments.
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {$bitNot: ["$a", "$b"]},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
16020,
|
||||
); // Error for incorrect number of arguments.
|
||||
|
||||
@ -3,8 +3,7 @@ coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1}));
|
||||
|
||||
function checkBsonSize() {
|
||||
assert.eq(Object.bsonsize(coll.findOne()),
|
||||
coll.aggregate([{$project: {s: {$bsonSize: "$$CURRENT"}}}]).next().s);
|
||||
assert.eq(Object.bsonsize(coll.findOne()), coll.aggregate([{$project: {s: {$bsonSize: "$$CURRENT"}}}]).next().s);
|
||||
}
|
||||
|
||||
checkBsonSize();
|
||||
@ -15,13 +14,13 @@ checkBsonSize();
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: {subdoc: 12345}}}));
|
||||
checkBsonSize();
|
||||
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(7)}}));
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(7)}}));
|
||||
checkBsonSize();
|
||||
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(500)}}));
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(500)}}));
|
||||
checkBsonSize();
|
||||
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(16 * 1e6)}}));
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(16 * 1e6)}}));
|
||||
checkBsonSize();
|
||||
|
||||
// embedded arrays
|
||||
@ -34,9 +33,11 @@ checkBsonSize();
|
||||
|
||||
// bsonSize's argument must be a document
|
||||
function checkExpectsDocument(badInput) {
|
||||
assert.throws(() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
|
||||
[],
|
||||
"$bsonSize requires a document input");
|
||||
assert.throws(
|
||||
() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
|
||||
[],
|
||||
"$bsonSize requires a document input",
|
||||
);
|
||||
}
|
||||
checkExpectsDocument(123);
|
||||
checkExpectsDocument("abc");
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -12,11 +12,11 @@ coll.drop();
|
||||
var results;
|
||||
const caseInsensitive = {
|
||||
locale: "en_US",
|
||||
strength: 2
|
||||
strength: 2,
|
||||
};
|
||||
const numericOrdering = {
|
||||
locale: "en_US",
|
||||
numericOrdering: true
|
||||
numericOrdering: true,
|
||||
};
|
||||
|
||||
// Test that $cmp respects the collection-default collation.
|
||||
@ -52,8 +52,7 @@ testExpressionWithCollation(coll, {$gte: ["b", "B"]}, true, caseInsensitive);
|
||||
testExpressionWithCollation(coll, {$in: ["A", [1, 2, "a", 3, 4]]}, true, caseInsensitive);
|
||||
|
||||
// Test that $indexOfArray respects the collation.
|
||||
testExpressionWithCollation(
|
||||
coll, {$indexOfArray: [[1, 2, "a", "b", "c", "B"], "B"]}, 3, caseInsensitive);
|
||||
testExpressionWithCollation(coll, {$indexOfArray: [[1, 2, "a", "b", "c", "B"], "B"]}, 3, caseInsensitive);
|
||||
|
||||
// Test that $indexOfBytes doesn't respect the collation.
|
||||
testExpressionWithCollation(coll, {$indexOfBytes: ["12abcB", "B"]}, 5, caseInsensitive);
|
||||
@ -65,20 +64,57 @@ testExpressionWithCollation(coll, {$indexOfCP: ["12abcB", "B"]}, 5, caseInsensit
|
||||
testExpressionWithCollation(coll, {$strcasecmp: ["100", "2"]}, -1, numericOrdering);
|
||||
|
||||
// Test that $setEquals respects the collation.
|
||||
testExpressionWithCollation(coll, {$setEquals: [["a", "B"], ["b", "A"]]}, true, caseInsensitive);
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$setEquals: [
|
||||
["a", "B"],
|
||||
["b", "A"],
|
||||
],
|
||||
},
|
||||
true,
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $setIntersection respects the collation.
|
||||
results =
|
||||
coll.aggregate([{$project: {out: {$setIntersection: [["a", "B", "c"], ["d", "b", "A"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$setIntersection: [
|
||||
["a", "B", "c"],
|
||||
["d", "b", "A"],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(2, results[0].out.length);
|
||||
|
||||
// Test that $setUnion respects the collation.
|
||||
results = coll.aggregate([{$project: {out: {$setUnion: [["a", "B", "c"], ["d", "b", "A"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$setUnion: [
|
||||
["a", "B", "c"],
|
||||
["d", "b", "A"],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(4, results[0].out.length);
|
||||
|
||||
@ -87,20 +123,39 @@ assert.eq(4, results[0].out.length);
|
||||
assert(coll.drop);
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, upper: "A", lower: "a"}));
|
||||
var results1 = coll.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
var results2 = coll.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
var results1 = coll
|
||||
.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
var results2 = coll
|
||||
.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(results1, results2);
|
||||
|
||||
// Test that $setDifference respects the collation.
|
||||
testExpressionWithCollation(coll, {$setDifference: [["a", "B"], ["b", "A"]]}, [], caseInsensitive);
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$setDifference: [
|
||||
["a", "B"],
|
||||
["b", "A"],
|
||||
],
|
||||
},
|
||||
[],
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $setIsSubset respects the collation.
|
||||
testExpressionWithCollation(
|
||||
coll, {$setIsSubset: [["a", "B"], ["b", "A", "c"]]}, true, caseInsensitive);
|
||||
coll,
|
||||
{
|
||||
$setIsSubset: [
|
||||
["a", "B"],
|
||||
["b", "A", "c"],
|
||||
],
|
||||
},
|
||||
true,
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $split doesn't respect the collation.
|
||||
testExpressionWithCollation(coll, {$split: ["abc", "B"]}, ["abc"], caseInsensitive);
|
||||
@ -108,130 +163,138 @@ testExpressionWithCollation(coll, {$split: ["abc", "B"]}, ["abc"], caseInsensiti
|
||||
// Test that an $and which can be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A"}));
|
||||
results = coll.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["b", "B"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["b", "B"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
|
||||
// Test that an $and which cannot be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A", str2: "B"}));
|
||||
results = coll.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["$str2", "b"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["$str2", "b"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
|
||||
// Test that an $or which can be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A"}));
|
||||
results = coll.aggregate([{$project: {out: {$or: [{$eq: ["$str", "a"]}, {$eq: ["b", "c"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$or: [{$eq: ["$str", "a"]}, {$eq: ["b", "c"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
|
||||
// Test that an $or which cannot be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A", str2: "B"}));
|
||||
results = coll.aggregate([{$project: {out: {$or: [{$eq: ["$str", "c"]}, {$eq: ["$str2", "b"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$or: [{$eq: ["$str", "c"]}, {$eq: ["$str2", "b"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
|
||||
// Test that $filter's subexpressions respect the collation.
|
||||
testExpressionWithCollation(coll,
|
||||
{
|
||||
$filter: {
|
||||
input: {
|
||||
$cond: {
|
||||
if: {$eq: ["FOO", "foo"]},
|
||||
then: ["a", "b", "A", "c", "C", "d"],
|
||||
else: null
|
||||
}
|
||||
},
|
||||
as: "str",
|
||||
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]}
|
||||
}
|
||||
},
|
||||
["a", "A", "c", "C"],
|
||||
caseInsensitive);
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$filter: {
|
||||
input: {
|
||||
$cond: {
|
||||
if: {$eq: ["FOO", "foo"]},
|
||||
then: ["a", "b", "A", "c", "C", "d"],
|
||||
else: null,
|
||||
},
|
||||
},
|
||||
as: "str",
|
||||
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]},
|
||||
},
|
||||
},
|
||||
["a", "A", "c", "C"],
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $let's subexpressions respect the collation.
|
||||
testExpressionWithCollation(coll,
|
||||
{
|
||||
$let: {
|
||||
vars: {str: {$cond: [{$eq: ["A", "a"]}, "b", "c"]}},
|
||||
in : {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]}
|
||||
}
|
||||
},
|
||||
"d",
|
||||
caseInsensitive);
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$let: {
|
||||
vars: {str: {$cond: [{$eq: ["A", "a"]}, "b", "c"]}},
|
||||
in: {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]},
|
||||
},
|
||||
},
|
||||
"d",
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $map's subexpressions respect the collation.
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$map: {
|
||||
input: {$cond: [{$eq: ["A", "a"]}, ["aa", "a", "AA", "b"], null]},
|
||||
as: "val",
|
||||
in : {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]}
|
||||
}
|
||||
coll,
|
||||
{
|
||||
$map: {
|
||||
input: {$cond: [{$eq: ["A", "a"]}, ["aa", "a", "AA", "b"], null]},
|
||||
as: "val",
|
||||
in: {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]},
|
||||
},
|
||||
[true, false, true, false],
|
||||
caseInsensitive);
|
||||
},
|
||||
[true, false, true, false],
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $group stage's _id expressions respect the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1}));
|
||||
results = coll.aggregate([{$group: {_id: {a: {$eq: ["a", "A"]}, b: {$eq: ["b", "B"]}}}}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate([{$group: {_id: {a: {$eq: ["a", "A"]}, b: {$eq: ["b", "B"]}}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0]._id.a);
|
||||
assert.eq(true, results[0]._id.b);
|
||||
|
||||
// Test that $reduce's subexpressions respect the collation.
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$reduce: {
|
||||
input: {$cond: [{$eq: ["a", "A"]}, [1, 2, 3], null]},
|
||||
initialValue: {$cond: [{$eq: ["a", "A"]}, {sum: 1}, {sum: 0}]},
|
||||
in : {
|
||||
$cond: [{$eq: ["a", "A"]}, {sum: {$add: ["$$value.sum", "$$this"]}}, {sum: 0}]
|
||||
}
|
||||
}
|
||||
coll,
|
||||
{
|
||||
$reduce: {
|
||||
input: {$cond: [{$eq: ["a", "A"]}, [1, 2, 3], null]},
|
||||
initialValue: {$cond: [{$eq: ["a", "A"]}, {sum: 1}, {sum: 0}]},
|
||||
in: {
|
||||
$cond: [{$eq: ["a", "A"]}, {sum: {$add: ["$$value.sum", "$$this"]}}, {sum: 0}],
|
||||
},
|
||||
},
|
||||
{sum: 7},
|
||||
caseInsensitive);
|
||||
},
|
||||
{sum: 7},
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $switch's subexpressions respect the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, a: "A"}));
|
||||
assert.commandWorked(coll.insert({_id: 2, b: "B"}));
|
||||
assert.commandWorked(coll.insert({_id: 3, c: "C"}));
|
||||
results = coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$switch: {
|
||||
branches: [
|
||||
{case: {$eq: ["$a", "a"]}, then: "foo"},
|
||||
{case: {$eq: ["$b", "b"]}, then: "bar"}
|
||||
],
|
||||
default: "baz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$switch: {
|
||||
branches: [
|
||||
{case: {$eq: ["$a", "a"]}, then: "foo"},
|
||||
{case: {$eq: ["$b", "b"]}, then: "bar"},
|
||||
],
|
||||
default: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(3, results.length);
|
||||
assert.eq("foo", results[0].out);
|
||||
assert.eq("bar", results[1].out);
|
||||
@ -240,21 +303,33 @@ assert.eq("baz", results[2].out);
|
||||
// Test that a $zip's subexpressions respect the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0, evens: [0, 2, 4], odds: [1, 3]}));
|
||||
results = coll.aggregate([{
|
||||
$project: {
|
||||
out: {
|
||||
$zip: {
|
||||
inputs: [
|
||||
{$cond: [{$eq: ["A", "a"]}, "$evens", "$odds"]},
|
||||
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]}
|
||||
],
|
||||
defaults: [0, {$cond: [{$eq: ["C", "c"]}, 5, 7]}],
|
||||
useLongestLength: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
{collation: caseInsensitive})
|
||||
.toArray();
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$zip: {
|
||||
inputs: [
|
||||
{$cond: [{$eq: ["A", "a"]}, "$evens", "$odds"]},
|
||||
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]},
|
||||
],
|
||||
defaults: [0, {$cond: [{$eq: ["C", "c"]}, 5, 7]}],
|
||||
useLongestLength: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq([[0, 1], [2, 3], [4, 5]], results[0].out);
|
||||
assert.eq(
|
||||
[
|
||||
[0, 1],
|
||||
[2, 3],
|
||||
[4, 5],
|
||||
],
|
||||
results[0].out,
|
||||
);
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
const coll = db[jsTest.name()];
|
||||
coll.drop();
|
||||
const numbers = [1.0, NumberInt("1"), NumberLong("1"), NumberDecimal("1.0")];
|
||||
const specials = [{val: NaN, path: "$nan"}, {val: Infinity, path: "$inf"}];
|
||||
const specials = [
|
||||
{val: NaN, path: "$nan"},
|
||||
{val: Infinity, path: "$inf"},
|
||||
];
|
||||
|
||||
assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
|
||||
|
||||
@ -11,15 +14,9 @@ assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
|
||||
(function testCommutativityWithConstArguments() {
|
||||
specials.forEach((special) => {
|
||||
numbers.forEach((num) => {
|
||||
const expected = [
|
||||
{a: (num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val)}
|
||||
];
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [special.val, num]}, _id: 0}}])
|
||||
.toArray());
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [num, special.val]}, _id: 0}}])
|
||||
.toArray());
|
||||
const expected = [{a: num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val}];
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [special.val, num]}, _id: 0}}]).toArray());
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [num, special.val]}, _id: 0}}]).toArray());
|
||||
});
|
||||
});
|
||||
})();
|
||||
@ -27,16 +24,10 @@ assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
|
||||
(function testCommutativityWithNonConstArgument() {
|
||||
specials.forEach((special) => {
|
||||
numbers.forEach((num) => {
|
||||
const expected = [
|
||||
{a: (num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val)}
|
||||
];
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [special.path, num]}, _id: 0}}])
|
||||
.toArray());
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [num, special.path]}, _id: 0}}])
|
||||
.toArray());
|
||||
const expected = [{a: num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val}];
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [special.path, num]}, _id: 0}}]).toArray());
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [num, special.path]}, _id: 0}}]).toArray());
|
||||
});
|
||||
});
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,4 +8,4 @@ coll.drop();
|
||||
assert.commandWorked(coll.insert({}));
|
||||
|
||||
assert.eq(coll.findOne({}, {_id: false, "a": {$concat: [{$toLower: "$b"}]}}), {a: ""});
|
||||
assert.eq(coll.findOne({}, {_id: false, "a": {$concat: []}}), {a: ""});
|
||||
assert.eq(coll.findOne({}, {_id: false, "a": {$concat: []}}), {a: ""});
|
||||
|
||||
@ -19,27 +19,29 @@ import {checkSbeFullyEnabled} from "jstests/libs/query/sbe_util.js";
|
||||
const coll = db.projection_expr_concat_arrays;
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insertOne({
|
||||
int_arr: [1, 2, 3, 4],
|
||||
dbl_arr: [10.0, 20.1, 20.4, 50.5],
|
||||
nested_arr: [["an", "array"], "arr", [[], [[], "a", "b"]]],
|
||||
str_arr: ["a", "b", "c"],
|
||||
obj_arr: [{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}],
|
||||
null_arr: [null, null, null],
|
||||
one_null_arr: [null],
|
||||
one_str_arr: ["one"],
|
||||
empty_arr: [],
|
||||
null_val: null,
|
||||
str_val: "a string",
|
||||
dbl_val: 2.0,
|
||||
int_val: 1,
|
||||
obj_val: {a: 1, b: "two"}
|
||||
}));
|
||||
assert.commandWorked(
|
||||
coll.insertOne({
|
||||
int_arr: [1, 2, 3, 4],
|
||||
dbl_arr: [10.0, 20.1, 20.4, 50.5],
|
||||
nested_arr: [["an", "array"], "arr", [[], [[], "a", "b"]]],
|
||||
str_arr: ["a", "b", "c"],
|
||||
obj_arr: [{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}],
|
||||
null_arr: [null, null, null],
|
||||
one_null_arr: [null],
|
||||
one_str_arr: ["one"],
|
||||
empty_arr: [],
|
||||
null_val: null,
|
||||
str_val: "a string",
|
||||
dbl_val: 2.0,
|
||||
int_val: 1,
|
||||
obj_val: {a: 1, b: "two"},
|
||||
}),
|
||||
);
|
||||
|
||||
function runAndAssert(operands, expectedResult) {
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).map(doc => doc.f),
|
||||
expected: expectedResult
|
||||
actual: coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).map((doc) => doc.f),
|
||||
expected: expectedResult,
|
||||
});
|
||||
}
|
||||
function runAndAssertNull(operands) {
|
||||
@ -47,8 +49,7 @@ function runAndAssertNull(operands) {
|
||||
}
|
||||
|
||||
function runAndAssertThrows(operands) {
|
||||
const error =
|
||||
assert.throws(() => coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).toArray());
|
||||
const error = assert.throws(() => coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).toArray());
|
||||
assert.commandFailedWithCode(error, 28664);
|
||||
}
|
||||
|
||||
@ -65,37 +66,54 @@ runAndAssert([[0], "$int_arr", [5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7]]);
|
||||
runAndAssert(["$int_arr", "$str_arr"], [[1, 2, 3, 4, "a", "b", "c"]]);
|
||||
runAndAssert(
|
||||
["$obj_arr", "$obj_arr", "$null_arr"],
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, null, null, null]]);
|
||||
runAndAssert(["$int_arr", "$str_arr", "$nested_arr"],
|
||||
[[1, 2, 3, 4, "a", "b", "c", ["an", "array"], "arr", [[], [[], "a", "b"]]]]);
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, null, null, null]],
|
||||
);
|
||||
runAndAssert(
|
||||
["$int_arr", "$str_arr", "$nested_arr"],
|
||||
[[1, 2, 3, 4, "a", "b", "c", ["an", "array"], "arr", [[], [[], "a", "b"]]]],
|
||||
);
|
||||
runAndAssert(["$int_arr", "$obj_arr"], [[1, 2, 3, 4, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}]]);
|
||||
runAndAssert(["$obj_arr"], [[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}]]);
|
||||
runAndAssert(["$obj_arr", [{o: 123, b: 1}, {y: "o", d: "a"}]],
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {o: 123, b: 1}, {y: "o", d: "a"}]]);
|
||||
runAndAssert(
|
||||
[
|
||||
"$obj_arr",
|
||||
[
|
||||
{o: 123, b: 1},
|
||||
{y: "o", d: "a"},
|
||||
],
|
||||
],
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {o: 123, b: 1}, {y: "o", d: "a"}]],
|
||||
);
|
||||
|
||||
// Confirm that arrays containing null can be concatenated.
|
||||
runAndAssert(["$null_arr"], [[null, null, null]]);
|
||||
runAndAssert([[null], "$null_arr"], [[null, null, null, null]]);
|
||||
runAndAssert("$one_null_arr", [[null]]);
|
||||
runAndAssert(["$null_arr", "$one_null_arr", "$int_arr", "$null_arr"],
|
||||
[[null, null, null, null, 1, 2, 3, 4, null, null, null]]);
|
||||
runAndAssert(
|
||||
["$null_arr", "$one_null_arr", "$int_arr", "$null_arr"],
|
||||
[[null, null, null, null, 1, 2, 3, 4, null, null, null]],
|
||||
);
|
||||
|
||||
// Test operands that form more complex expressions.
|
||||
runAndAssert([{$concatArrays: "$int_arr"}], [[1, 2, 3, 4]]);
|
||||
runAndAssert([{$concatArrays: "$int_arr"}, {$concatArrays: {$concatArrays: "$str_arr"}}],
|
||||
[[1, 2, 3, 4, "a", "b", "c"]]);
|
||||
runAndAssert(["$str_arr", {$filter: {input: "$int_arr",
|
||||
as: "num",
|
||||
cond: { $and: [
|
||||
{ $gte: [ "$$num", 2 ] },
|
||||
{ $lte: [ "$$num", 3 ] }
|
||||
] }}}, "$int_arr"],
|
||||
[["a", "b", "c", 2, 3, 1, 2, 3, 4]]);
|
||||
runAndAssert(
|
||||
[{$concatArrays: "$int_arr"}, {$concatArrays: {$concatArrays: "$str_arr"}}],
|
||||
[[1, 2, 3, 4, "a", "b", "c"]],
|
||||
);
|
||||
runAndAssert(
|
||||
[
|
||||
"$str_arr",
|
||||
{$filter: {input: "$int_arr", as: "num", cond: {$and: [{$gte: ["$$num", 2]}, {$lte: ["$$num", 3]}]}}},
|
||||
"$int_arr",
|
||||
],
|
||||
[["a", "b", "c", 2, 3, 1, 2, 3, 4]],
|
||||
);
|
||||
|
||||
// Confirm that empty arrays can be concatenated with variables.
|
||||
runAndAssert(
|
||||
["$str_arr", {$filter: {input: [], cond: {$isArray: [{$concatArrays: [[], "$$this"]}]}}}],
|
||||
[["a", "b", "c"]]);
|
||||
[["a", "b", "c"]],
|
||||
);
|
||||
|
||||
// Concatenation with no arguments results in the empty array.
|
||||
runAndAssert([], [[]]);
|
||||
@ -113,11 +131,7 @@ runAndAssertNull(["$not_a_field"]);
|
||||
runAndAssertNull(["$null_val"]);
|
||||
runAndAssertNull(["$not_a_field", "$null_val"]);
|
||||
runAndAssertNull(["$null_val", "$not_a_field"]);
|
||||
runAndAssertNull([
|
||||
{$concatArrays: "$int_arr"},
|
||||
null,
|
||||
{$concatArrays: {$concatArrays: ["$obj_arr", "$str_arr"]}}
|
||||
]);
|
||||
runAndAssertNull([{$concatArrays: "$int_arr"}, null, {$concatArrays: {$concatArrays: ["$obj_arr", "$str_arr"]}}]);
|
||||
|
||||
// Confirm edge case where if null precedes non-array input, null is returned.
|
||||
runAndAssertNull(["$int_arr", "$null_val", "$int_val"]);
|
||||
@ -153,30 +167,38 @@ runAndAssertThrows(["$int_arr", 32]);
|
||||
assert(coll.drop());
|
||||
|
||||
// Test case where find returns multiple documents.
|
||||
assert.commandWorked(coll.insertMany([
|
||||
{arr1: [42, 35.0, 197865432], arr2: ["albatross", "abbacus", "alien"]},
|
||||
{arr1: [1], arr2: ["albatross", "abbacus", "alien"]},
|
||||
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: []},
|
||||
{arr1: [], arr2: ["foo", "bar"]},
|
||||
{arr1: [], arr2: []},
|
||||
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: null},
|
||||
{some_field: "foo"},
|
||||
]));
|
||||
runAndAssert(["$arr1", "$arr2"], [
|
||||
[42, 35.0, 197865432, "albatross", "abbacus", "alien"],
|
||||
[1, "albatross", "abbacus", "alien"],
|
||||
[1, 2, 3, 4, 5, 6, 11, 12, 23],
|
||||
["foo", "bar"],
|
||||
[],
|
||||
null,
|
||||
null
|
||||
]);
|
||||
runAndAssert(["$arr1", [1, 2, 3], "$arr2"], [
|
||||
[42, 35.0, 197865432, 1, 2, 3, "albatross", "abbacus", "alien"],
|
||||
[1, 1, 2, 3, "albatross", "abbacus", "alien"],
|
||||
[1, 2, 3, 4, 5, 6, 11, 12, 23, 1, 2, 3],
|
||||
["foo", 1, 2, 3, "bar"],
|
||||
[1, 2, 3],
|
||||
null,
|
||||
null
|
||||
]);
|
||||
assert.commandWorked(
|
||||
coll.insertMany([
|
||||
{arr1: [42, 35.0, 197865432], arr2: ["albatross", "abbacus", "alien"]},
|
||||
{arr1: [1], arr2: ["albatross", "abbacus", "alien"]},
|
||||
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: []},
|
||||
{arr1: [], arr2: ["foo", "bar"]},
|
||||
{arr1: [], arr2: []},
|
||||
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: null},
|
||||
{some_field: "foo"},
|
||||
]),
|
||||
);
|
||||
runAndAssert(
|
||||
["$arr1", "$arr2"],
|
||||
[
|
||||
[42, 35.0, 197865432, "albatross", "abbacus", "alien"],
|
||||
[1, "albatross", "abbacus", "alien"],
|
||||
[1, 2, 3, 4, 5, 6, 11, 12, 23],
|
||||
["foo", "bar"],
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
],
|
||||
);
|
||||
runAndAssert(
|
||||
["$arr1", [1, 2, 3], "$arr2"],
|
||||
[
|
||||
[42, 35.0, 197865432, 1, 2, 3, "albatross", "abbacus", "alien"],
|
||||
[1, 1, 2, 3, "albatross", "abbacus", "alien"],
|
||||
[1, 2, 3, 4, 5, 6, 11, 12, 23, 1, 2, 3],
|
||||
["foo", 1, 2, 3, "bar"],
|
||||
[1, 2, 3],
|
||||
null,
|
||||
null,
|
||||
],
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -46,39 +46,42 @@ assertResult(1, [{$and: []}, {$add: [1]}, {$add: [1, 1]}]);
|
||||
assertResult(2, [{$or: []}, {$add: [1]}, {$add: [1, 1]}]);
|
||||
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({t: true, f: false, x: 'foo', y: 'bar'}));
|
||||
assert.commandWorked(coll.insert({t: true, f: false, x: "foo", y: "bar"}));
|
||||
|
||||
// Field path expressions.
|
||||
assertResult('foo', ['$t', '$x', '$y']);
|
||||
assertResult('bar', ['$f', '$x', '$y']);
|
||||
assertResult("foo", ["$t", "$x", "$y"]);
|
||||
assertResult("bar", ["$f", "$x", "$y"]);
|
||||
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({}));
|
||||
|
||||
// Coerce to bool.
|
||||
assertResult('a', [1, 'a', 'b']);
|
||||
assertResult('a', ['', 'a', 'b']);
|
||||
assertResult('b', [0, 'a', 'b']);
|
||||
assertResult("a", [1, "a", "b"]);
|
||||
assertResult("a", ["", "a", "b"]);
|
||||
assertResult("b", [0, "a", "b"]);
|
||||
|
||||
// Nested.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({noonSense: 'am', mealCombined: 'no'}));
|
||||
assert.commandWorked(coll.insert({noonSense: 'am', mealCombined: 'yes'}));
|
||||
assert.commandWorked(coll.insert({noonSense: 'pm', mealCombined: 'yes'}));
|
||||
assert.commandWorked(coll.insert({noonSense: 'pm', mealCombined: 'no'}));
|
||||
assert.eq(['breakfast', 'brunch', 'dinner', 'linner'],
|
||||
coll.aggregate([
|
||||
{
|
||||
$project: {
|
||||
meal: {
|
||||
$cond: [
|
||||
{$eq: ['$noonSense', 'am']},
|
||||
{$cond: [{$eq: ['$mealCombined', 'yes']}, 'brunch', 'breakfast']},
|
||||
{$cond: [{$eq: ['$mealCombined', 'yes']}, 'linner', 'dinner']}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{$sort: {meal: 1}}
|
||||
])
|
||||
.map(doc => doc.meal));
|
||||
assert.commandWorked(coll.insert({noonSense: "am", mealCombined: "no"}));
|
||||
assert.commandWorked(coll.insert({noonSense: "am", mealCombined: "yes"}));
|
||||
assert.commandWorked(coll.insert({noonSense: "pm", mealCombined: "yes"}));
|
||||
assert.commandWorked(coll.insert({noonSense: "pm", mealCombined: "no"}));
|
||||
assert.eq(
|
||||
["breakfast", "brunch", "dinner", "linner"],
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
meal: {
|
||||
$cond: [
|
||||
{$eq: ["$noonSense", "am"]},
|
||||
{$cond: [{$eq: ["$mealCombined", "yes"]}, "brunch", "breakfast"]},
|
||||
{$cond: [{$eq: ["$mealCombined", "yes"]}, "linner", "dinner"]},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{$sort: {meal: 1}},
|
||||
])
|
||||
.map((doc) => doc.meal),
|
||||
);
|
||||
|
||||
@ -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});
|
||||
|
||||
@ -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});
|
||||
|
||||
@ -218,8 +218,7 @@ const invalidArgumentValueDocs = [
|
||||
},
|
||||
];
|
||||
|
||||
runConvertTests(
|
||||
{coll, requiresFCV81, conversionTestDocs, illegalConversionTestDocs, invalidArgumentValueDocs});
|
||||
runConvertTests({coll, requiresFCV81, conversionTestDocs, illegalConversionTestDocs, invalidArgumentValueDocs});
|
||||
|
||||
// Additional tests covering shortcuts and string byteOrder.
|
||||
function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
|
||||
@ -227,61 +226,66 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
|
||||
assert.commandWorked(coll.insertMany(documents));
|
||||
|
||||
let aggResult = coll.aggregate(convertPipeline).toArray();
|
||||
aggResult.forEach(doc => {
|
||||
aggResult.forEach((doc) => {
|
||||
assert.eq(doc.output, doc.expected);
|
||||
});
|
||||
}
|
||||
|
||||
(function testConvertBindataToLong() {
|
||||
let pipeline = [{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}}
|
||||
}
|
||||
}];
|
||||
let pipeline = [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}},
|
||||
},
|
||||
},
|
||||
];
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0x00000000000000c8", 8 byte long
|
||||
docs: [{longInput: NumberLong(200), expected: BinData(0, "AAAAAAAAAMg=")}]
|
||||
docs: [{longInput: NumberLong(200), expected: BinData(0, "AAAAAAAAAMg=")}],
|
||||
});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToInt() {
|
||||
let pipeline = [{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}}
|
||||
}
|
||||
}];
|
||||
let pipeline = [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}},
|
||||
},
|
||||
},
|
||||
];
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xfffffe89", 4 byte int
|
||||
docs: [{IntInput: NumberInt(-375), expected: BinData(0, "///+iQ==")}]
|
||||
docs: [{IntInput: NumberInt(-375), expected: BinData(0, "///+iQ==")}],
|
||||
});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToDouble() {
|
||||
let pipeline = [{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}}
|
||||
}
|
||||
}];
|
||||
let pipeline = [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}},
|
||||
},
|
||||
},
|
||||
];
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xC004CCCCCCCCCCCD", 8 byte double precision double
|
||||
docs: [{DoubleInput: -2.6, expected: BinData(0, "wATMzMzMzM0=")}]
|
||||
docs: [{DoubleInput: -2.6, expected: BinData(0, "wATMzMzMzM0=")}],
|
||||
});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToIntShortCut() {
|
||||
let pipeline = [{$project: {_id: 0, expected: 1, output: {$toInt: "$binDataInput"}}}];
|
||||
// Hex: "0x=02", 1 byte integer
|
||||
testConvertNumeric(
|
||||
{pipeline: pipeline, docs: [{binDataInput: BinData(0, "Ag=="), expected: NumberInt(2)}]});
|
||||
testConvertNumeric({pipeline: pipeline, docs: [{binDataInput: BinData(0, "Ag=="), expected: NumberInt(2)}]});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToLongShortCut() {
|
||||
@ -289,7 +293,7 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xA2020000", 4 byte long
|
||||
docs: [{binDataInput: BinData(6, "ogIAAA=="), expected: NumberLong(674)}]
|
||||
docs: [{binDataInput: BinData(6, "ogIAAA=="), expected: NumberLong(674)}],
|
||||
});
|
||||
})();
|
||||
|
||||
@ -298,6 +302,6 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xCDCCCCCCCCCC04C0", 4 byte long
|
||||
docs: [{binDataInput: BinData(0, "zczMzMzMBMA="), expected: -2.6}]
|
||||
docs: [{binDataInput: BinData(0, "zczMzMzMBMA="), expected: -2.6}],
|
||||
});
|
||||
})();
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ function basicTest() {
|
||||
const pipeline = [
|
||||
{$addFields: {uuidField: {$createUUID: {}}}},
|
||||
{$project: {uuidStrField: {$toString: "$uuidField"}, uuidField: 1}},
|
||||
{$project: {_id: 0}}
|
||||
{$project: {_id: 0}},
|
||||
];
|
||||
|
||||
const resultArray = coll.aggregate(pipeline).toArray();
|
||||
@ -56,21 +56,21 @@ function lookupTest() {
|
||||
setupCollection();
|
||||
|
||||
const pipeline = [
|
||||
{$lookup: {
|
||||
from: collName,
|
||||
let: {
|
||||
docId: "$_id"
|
||||
{
|
||||
$lookup: {
|
||||
from: collName,
|
||||
let: {
|
||||
docId: "$_id",
|
||||
},
|
||||
pipeline: [{$addFields: {uuid: {$createUUID: {}}}}],
|
||||
as: "result",
|
||||
},
|
||||
pipeline: [
|
||||
{$addFields: {uuid: {$createUUID: {}}}}
|
||||
],
|
||||
as: "result"
|
||||
}},
|
||||
},
|
||||
];
|
||||
|
||||
const resultArray = coll.aggregate(pipeline).toArray();
|
||||
assert.eq(resultArray.length, docCount);
|
||||
const s = resultArray.flatMap(doc => doc.result).map(doc => doc.uuid);
|
||||
const s = resultArray.flatMap((doc) => doc.result).map((doc) => doc.uuid);
|
||||
const set = new Set(s);
|
||||
assert.eq(set.size, docCount * docCount);
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ function basicTest() {
|
||||
// Evaluate a $group with another $currentDate expression.
|
||||
{$group: {_id: "$time1", time2: {$first: {$currentDate: {}}}}},
|
||||
{$project: {time1: "$_id", time2: 1}},
|
||||
{$project: {_id: 0}}
|
||||
{$project: {_id: 0}},
|
||||
];
|
||||
|
||||
const resultArray = coll.aggregate(pipeline).toArray();
|
||||
@ -55,13 +55,15 @@ try {
|
||||
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluation", {ms: 2}),
|
||||
primaryNodeOnly: false,
|
||||
});
|
||||
failPoints.push(...FixtureHelpers.mapOnEachShardNode({
|
||||
db: db.getSiblingDB("admin"),
|
||||
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluationSBE", {ms: 2}),
|
||||
primaryNodeOnly: false,
|
||||
}));
|
||||
failPoints.push(
|
||||
...FixtureHelpers.mapOnEachShardNode({
|
||||
db: db.getSiblingDB("admin"),
|
||||
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluationSBE", {ms: 2}),
|
||||
primaryNodeOnly: false,
|
||||
}),
|
||||
);
|
||||
|
||||
basicTest();
|
||||
} finally {
|
||||
failPoints.forEach(failPoint => failPoint.off());
|
||||
failPoints.forEach((failPoint) => failPoint.off());
|
||||
}
|
||||
|
||||
@ -26,18 +26,16 @@ function runAndAssertResultOrErrorCode(date, result, errorCode) {
|
||||
function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
|
||||
executeAggregationTestCase(coll, {
|
||||
pipeline: [{$project: {_id: 0, newDate: dateArithmeticsSpec}}],
|
||||
inputDocuments: [
|
||||
{_id: 1, date: ISODate("2020-12-31T12:10:05"), unit: "month", timezone: "Europe/Paris"}
|
||||
],
|
||||
inputDocuments: [{_id: 1, date: ISODate("2020-12-31T12:10:05"), unit: "month", timezone: "Europe/Paris"}],
|
||||
expectedErrorCode: expectedErrorCode,
|
||||
expectedResults: expectedResult
|
||||
|
||||
expectedResults: expectedResult,
|
||||
});
|
||||
}
|
||||
|
||||
(function testDateAddWithValidInputs() {
|
||||
runAndAssert({$dateAdd: {startDate: ISODate("2020-11-30T12:10:05Z"), unit: "day", amount: 1}},
|
||||
[{newDate: ISODate("2020-12-01T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: ISODate("2020-11-30T12:10:05Z"), unit: "day", amount: 1}}, [
|
||||
{newDate: ISODate("2020-12-01T12:10:05Z")},
|
||||
]);
|
||||
|
||||
// Test that adding with a null argument (non-existing field) results in null.
|
||||
runAndAssert({$dateAdd: {startDate: "$dateSent", unit: "day", amount: 1}}, [{newDate: null}]);
|
||||
@ -46,148 +44,179 @@ function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: null}}, [{newDate: null}]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1, timezone: null}},
|
||||
[{newDate: null}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1, timezone: null}}, [{newDate: null}]);
|
||||
|
||||
// Test combination of null and invalid arguments.
|
||||
runAndAssertResultOrErrorCode({$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
|
||||
[{newDate: null}],
|
||||
ErrorCodes.FailedToParse);
|
||||
runAndAssertResultOrErrorCode(
|
||||
{$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
|
||||
[{newDate: null}],
|
||||
ErrorCodes.FailedToParse,
|
||||
);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "New year day", unit: "$timeunit", amount: 1}},
|
||||
[{newDate: null}]);
|
||||
runAndAssert({$dateAdd: {startDate: "New year day", unit: "$timeunit", amount: 1}}, [{newDate: null}]);
|
||||
|
||||
runAndAssertResultOrErrorCode(
|
||||
{$dateAdd: {startDate: "$date", unit: "workday", amount: "$amount", timezone: "Unknown"}},
|
||||
[{newDate: null}],
|
||||
ErrorCodes.FailedToParse);
|
||||
ErrorCodes.FailedToParse,
|
||||
);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1.5, timezone: null}},
|
||||
[{newDate: null}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1.5, timezone: null}}, [{newDate: null}]);
|
||||
|
||||
// Tests when startDate and result date cross the DST time change in a timezone.
|
||||
runAndAssert({
|
||||
$dateAdd: {
|
||||
startDate: ISODate("2020-10-24T18:10:00Z"),
|
||||
unit: "hour",
|
||||
amount: 24,
|
||||
timezone: "Europe/Paris"
|
||||
}
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T18:10:00Z")}]);
|
||||
runAndAssert(
|
||||
{
|
||||
$dateAdd: {
|
||||
startDate: ISODate("2020-10-24T18:10:00Z"),
|
||||
unit: "hour",
|
||||
amount: 24,
|
||||
timezone: "Europe/Paris",
|
||||
},
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T18:10:00Z")}],
|
||||
);
|
||||
|
||||
// When adding units of day both the startDate and the result represent 20:10:00 in
|
||||
// Europe/Paris. The two dates have different offsets from UTC due to the change in daylight
|
||||
// savings time.
|
||||
runAndAssert({
|
||||
$dateAdd: {
|
||||
startDate: ISODate("2020-10-24T18:10:00Z"),
|
||||
unit: "day",
|
||||
amount: 1,
|
||||
timezone: "Europe/Paris"
|
||||
}
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T19:10:00Z")}]);
|
||||
runAndAssert(
|
||||
{
|
||||
$dateAdd: {
|
||||
startDate: ISODate("2020-10-24T18:10:00Z"),
|
||||
unit: "day",
|
||||
amount: 1,
|
||||
timezone: "Europe/Paris",
|
||||
},
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T19:10:00Z")}],
|
||||
);
|
||||
|
||||
// The following tests use start date from the document field and all valid values for the
|
||||
// 'unit' argument.
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "year", amount: -1}},
|
||||
[{newDate: ISODate("2019-12-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "year", amount: -1}}, [
|
||||
{newDate: ISODate("2019-12-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "quarter", amount: 1}},
|
||||
[{newDate: ISODate("2021-03-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "quarter", amount: 1}}, [
|
||||
{newDate: ISODate("2021-03-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2}},
|
||||
[{newDate: ISODate("2021-02-28T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2}}, [
|
||||
{newDate: ISODate("2021-02-28T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "week", amount: 1}},
|
||||
[{newDate: ISODate("2021-01-07T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "week", amount: 1}}, [
|
||||
{newDate: ISODate("2021-01-07T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: 1}},
|
||||
[{newDate: ISODate("2021-01-01T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: 1}}, [
|
||||
{newDate: ISODate("2021-01-01T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
|
||||
[{newDate: ISODate("2020-12-31T14:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "hour", amount: 2}}, [
|
||||
{newDate: ISODate("2020-12-31T14:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "minute", amount: -20}},
|
||||
[{newDate: ISODate("2020-12-31T11:50:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "minute", amount: -20}}, [
|
||||
{newDate: ISODate("2020-12-31T11:50:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "millisecond", amount: 1050}},
|
||||
[{newDate: ISODate("2020-12-31T12:10:06.05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "millisecond", amount: 1050}}, [
|
||||
{newDate: ISODate("2020-12-31T12:10:06.05Z")},
|
||||
]);
|
||||
|
||||
// Tests using the document fields for unit and timezone arguments.
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1}},
|
||||
[{newDate: ISODate("2021-01-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1}}, [
|
||||
{newDate: ISODate("2021-01-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2, timezone: "$timezone"}},
|
||||
[{newDate: ISODate("2021-02-28T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2, timezone: "$timezone"}}, [
|
||||
{newDate: ISODate("2021-02-28T12:10:05Z")},
|
||||
]);
|
||||
})();
|
||||
|
||||
(function testDateSubtractWithValidInputs() {
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "year", amount: 2}},
|
||||
[{newDate: ISODate("2018-12-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "year", amount: 2}}, [
|
||||
{newDate: ISODate("2018-12-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "quarter", amount: 2}},
|
||||
[{newDate: ISODate("2020-06-30T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "quarter", amount: 2}}, [
|
||||
{newDate: ISODate("2020-06-30T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "day", amount: 15}},
|
||||
[{newDate: ISODate("2020-12-16T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "day", amount: 15}}, [
|
||||
{newDate: ISODate("2020-12-16T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "hour", amount: 48}},
|
||||
[{newDate: ISODate("2020-12-29T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "hour", amount: 48}}, [
|
||||
{newDate: ISODate("2020-12-29T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "minute", amount: 15}},
|
||||
[{newDate: ISODate("2020-12-31T11:55:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "minute", amount: 15}}, [
|
||||
{newDate: ISODate("2020-12-31T11:55:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "second", amount: 125}},
|
||||
[{newDate: ISODate("2020-12-31T12:08:00Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "second", amount: 125}}, [
|
||||
{newDate: ISODate("2020-12-31T12:08:00Z")},
|
||||
]);
|
||||
|
||||
// Test last day adjustment in UTC.
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3}},
|
||||
[{newDate: ISODate("2020-09-30T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3}}, [
|
||||
{newDate: ISODate("2020-09-30T12:10:05Z")},
|
||||
]);
|
||||
|
||||
// Test last day adjustment and crossing DST time change in a timezone.
|
||||
runAndAssert(
|
||||
{$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3, timezone: "$timezone"}},
|
||||
[{newDate: ISODate("2020-09-30T11:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3, timezone: "$timezone"}}, [
|
||||
{newDate: ISODate("2020-09-30T11:10:05Z")},
|
||||
]);
|
||||
|
||||
// Test last day adjustment in the New York timezone.
|
||||
runAndAssert({
|
||||
$dateSubtract: {
|
||||
startDate: ISODate("2021-01-31T03:00:00Z"),
|
||||
unit: "month",
|
||||
amount: 2,
|
||||
timezone: "America/New_York"
|
||||
}
|
||||
},
|
||||
[{newDate: ISODate("2020-12-01T03:00:00Z")}]);
|
||||
runAndAssert(
|
||||
{
|
||||
$dateSubtract: {
|
||||
startDate: ISODate("2021-01-31T03:00:00Z"),
|
||||
unit: "month",
|
||||
amount: 2,
|
||||
timezone: "America/New_York",
|
||||
},
|
||||
},
|
||||
[{newDate: ISODate("2020-12-01T03:00:00Z")}],
|
||||
);
|
||||
})();
|
||||
|
||||
// Test combinations of $dateAdd and $dateSubtract.
|
||||
(function testDateArithmetics() {
|
||||
runAndAssert({
|
||||
$dateSubtract: {
|
||||
startDate: {$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
|
||||
unit: "hour",
|
||||
amount: 2
|
||||
}
|
||||
},
|
||||
[{newDate: ISODate("2020-12-31T12:10:05Z")}]);
|
||||
runAndAssert(
|
||||
{
|
||||
$dateSubtract: {
|
||||
startDate: {$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
|
||||
unit: "hour",
|
||||
amount: 2,
|
||||
},
|
||||
},
|
||||
[{newDate: ISODate("2020-12-31T12:10:05Z")}],
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
coll.aggregate([{
|
||||
$project: {newDate: {$dateSubtract: {startDate: "$date", unit: "month", amount: 1}}}
|
||||
}])
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {newDate: {$dateSubtract: {startDate: "$date", unit: "month", amount: 1}}},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
coll.aggregate([{
|
||||
$project: {
|
||||
newDate: {
|
||||
$dateAdd:
|
||||
{startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5}
|
||||
}
|
||||
}
|
||||
}])
|
||||
.toArray());
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
newDate: {
|
||||
$dateAdd: {startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
);
|
||||
})();
|
||||
|
||||
// Tests for error codes.
|
||||
@ -202,34 +231,27 @@ function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", units: "day", amount: 1}}, 5166401);
|
||||
|
||||
// Invalid string type of startDate argument.
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "myBirthDate", unit: "year", amount: 10}},
|
||||
5166403);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "myBirthDate", unit: "year", amount: 10}}, 5166403);
|
||||
|
||||
// Invalid numeric type of unit argument.
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: 1, amount: 10}}, 5166404);
|
||||
|
||||
// Invalid value of unit argument.
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "epoch", amount: 10}},
|
||||
ErrorCodes.FailedToParse);
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "epoch", amount: 10}}, ErrorCodes.FailedToParse);
|
||||
|
||||
// Invalid double type of amount argument.
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "year", amount: 1.001}},
|
||||
5166405);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "year", amount: 1.001}}, 5166405);
|
||||
|
||||
// Overflow error of dateAdd operation due to large amount.
|
||||
runAndAssertErrorCode(
|
||||
{$dateSubtract: {startDate: "$date", unit: "month", amount: 12 * 300000000}}, 5166406);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "month", amount: 12 * 300000000}}, 5166406);
|
||||
|
||||
// Invalid 'amount' parameter error of dateAdd operation due to large amount.
|
||||
runAndAssertErrorCode(
|
||||
{$dateSubtract: {startDate: "$date", unit: "month", amount: -30000000000}}, 5976500);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "month", amount: -30000000000}}, 5976500);
|
||||
|
||||
// Invalid 'amount' parameter error of dateSubtract operation: long long min value cannot be
|
||||
// negated.
|
||||
runAndAssertErrorCode(
|
||||
{$dateSubtract: {startDate: "$date", unit: "day", amount: -9223372036854775808}}, 6045000);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "day", amount: -9223372036854775808}}, 6045000);
|
||||
|
||||
// Invalid value of timezone argument.
|
||||
runAndAssertErrorCode(
|
||||
{$dateAdd: {startDate: "$date", unit: "year", amount: 1, timezone: "Unknown"}}, 40485);
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "year", amount: 1, timezone: "Unknown"}}, 40485);
|
||||
})();
|
||||
|
||||
@ -14,103 +14,117 @@ const coll = testDB.collection;
|
||||
assert.commandWorked(testDB.dropDatabase());
|
||||
|
||||
const someDate = new Date("2020-11-01T18:23:36Z");
|
||||
const aggregationPipelineWithDateDiff = [{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff:
|
||||
{startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"}
|
||||
}
|
||||
}
|
||||
}];
|
||||
const aggregationPipelineWithDateDiffAndStartOfWeek = [{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: "$startDate",
|
||||
endDate: "$endDate",
|
||||
unit: "$unit",
|
||||
timezone: "$timeZone",
|
||||
startOfWeek: "$startOfWeek"
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
const aggregationPipelineWithDateDiff = [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const aggregationPipelineWithDateDiffAndStartOfWeek = [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: "$startDate",
|
||||
endDate: "$endDate",
|
||||
unit: "$unit",
|
||||
timezone: "$timeZone",
|
||||
startOfWeek: "$startOfWeek",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const testCases = [
|
||||
{
|
||||
// Parameters are constants, timezone is not specified.
|
||||
pipeline: [{
|
||||
$project: {
|
||||
_id: true,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour"
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: true,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [{_id: 1}],
|
||||
expectedResults: [{_id: 1, date_diff: NumberLong("6")}]
|
||||
expectedResults: [{_id: 1, date_diff: NumberLong("6")}],
|
||||
},
|
||||
{
|
||||
// Parameters are field paths.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour",
|
||||
timeZone: "America/New_York",
|
||||
startOfWeek: "IGNORED" // Ignored when unit is not week.
|
||||
}],
|
||||
expectedResults: [{date_diff: NumberLong("6")}]
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour",
|
||||
timeZone: "America/New_York",
|
||||
startOfWeek: "IGNORED", // Ignored when unit is not week.
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("6")}],
|
||||
},
|
||||
{
|
||||
// Parameters are field paths, 'timezone' is not specified.
|
||||
pipeline: [{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff:
|
||||
{$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}}
|
||||
}
|
||||
}],
|
||||
inputDocuments: [{
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour"
|
||||
}],
|
||||
expectedResults: [{date_diff: NumberLong("6")}]
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour",
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("6")}],
|
||||
},
|
||||
{
|
||||
// 'startDate' and 'endDate' are object ids.
|
||||
pipeline: [{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: "$_id",
|
||||
endDate: "$_id",
|
||||
unit: "millisecond",
|
||||
timezone: "America/New_York"
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: "$_id",
|
||||
endDate: "$_id",
|
||||
unit: "millisecond",
|
||||
timezone: "America/New_York",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [{}],
|
||||
expectedResults: [{date_diff: NumberLong("0")}]
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
// 'startDate' and 'endDate' are timestamps.
|
||||
pipeline: [{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}}
|
||||
}
|
||||
}],
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [{ts: new Timestamp()}],
|
||||
expectedResults: [{date_diff: NumberLong("0")}]
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
// Invalid 'startDate' type.
|
||||
@ -189,46 +203,53 @@ const testCases = [
|
||||
{
|
||||
// Invalid 'timezone' value.
|
||||
pipeline: aggregationPipelineWithDateDiff,
|
||||
inputDocuments:
|
||||
[{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
|
||||
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
|
||||
expectedErrorCode: 40485,
|
||||
},
|
||||
{
|
||||
// Specified 'startOfWeek'.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
|
||||
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
|
||||
unit: "week",
|
||||
timeZone: "GMT",
|
||||
startOfWeek: "MONDAY"
|
||||
}],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
|
||||
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
|
||||
unit: "week",
|
||||
timeZone: "GMT",
|
||||
startOfWeek: "MONDAY",
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("1")}],
|
||||
},
|
||||
{
|
||||
// Specified 'startOfWeek' and timezone.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
startDate: new Date("2021-01-17T05:00:00Z"), // Sunday in New York.
|
||||
endDate: new Date("2021-01-17T04:59:00Z"), // Saturday in New York.
|
||||
unit: "week",
|
||||
timeZone: "America/New_York",
|
||||
startOfWeek: "sunday"
|
||||
}],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2021-01-17T05:00:00Z"), // Sunday in New York.
|
||||
endDate: new Date("2021-01-17T04:59:00Z"), // Saturday in New York.
|
||||
unit: "week",
|
||||
timeZone: "America/New_York",
|
||||
startOfWeek: "sunday",
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("-1")}],
|
||||
},
|
||||
{
|
||||
// Unspecified 'startOfWeek' - defaults to Sunday.
|
||||
pipeline: [{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}}
|
||||
}
|
||||
}],
|
||||
inputDocuments: [{
|
||||
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
|
||||
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
|
||||
}],
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
|
||||
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
@ -246,30 +267,28 @@ const testCases = [
|
||||
{
|
||||
// Invalid 'startOfWeek' type.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [
|
||||
{startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}
|
||||
],
|
||||
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}],
|
||||
expectedErrorCode: 5439015,
|
||||
},
|
||||
{
|
||||
// Invalid 'startOfWeek' type, unit is not the week.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [
|
||||
{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}
|
||||
],
|
||||
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}],
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
// Invalid 'startOfWeek' value.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
startDate: someDate,
|
||||
endDate: someDate,
|
||||
unit: "week",
|
||||
timeZone: "GMT",
|
||||
startOfWeek: "FRIDIE"
|
||||
}],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: someDate,
|
||||
endDate: someDate,
|
||||
unit: "week",
|
||||
timeZone: "GMT",
|
||||
startOfWeek: "FRIDIE",
|
||||
},
|
||||
],
|
||||
expectedErrorCode: 5439016,
|
||||
}
|
||||
},
|
||||
];
|
||||
testCases.forEach(testCase => executeAggregationTestCase(coll, testCase));
|
||||
testCases.forEach((testCase) => executeAggregationTestCase(coll, testCase));
|
||||
|
||||
@ -7,35 +7,33 @@ function test(date, testSynthetics) {
|
||||
c.drop();
|
||||
c.save({date: date});
|
||||
|
||||
var ISOfmt = (date.getUTCMilliseconds() == 0) ? 'ISODate("%Y-%m-%dT%H:%M:%SZ")'
|
||||
: 'ISODate("%Y-%m-%dT%H:%M:%S.%LZ")';
|
||||
var ISOfmt = date.getUTCMilliseconds() == 0 ? 'ISODate("%Y-%m-%dT%H:%M:%SZ")' : 'ISODate("%Y-%m-%dT%H:%M:%S.%LZ")';
|
||||
|
||||
// Can't use aggregate helper or assertErrorCode because we need to handle multiple error types
|
||||
var res = c.runCommand('aggregate', {
|
||||
pipeline: [{
|
||||
$project: {
|
||||
_id: 0,
|
||||
year: {$year: '$date'},
|
||||
month: {$month: '$date'},
|
||||
dayOfMonth: {$dayOfMonth: '$date'},
|
||||
hour: {$hour: '$date'},
|
||||
minute: {$minute: '$date'},
|
||||
second: {$second: '$date'}
|
||||
var res = c.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
year: {$year: "$date"},
|
||||
month: {$month: "$date"},
|
||||
dayOfMonth: {$dayOfMonth: "$date"},
|
||||
hour: {$hour: "$date"},
|
||||
minute: {$minute: "$date"},
|
||||
second: {$second: "$date"},
|
||||
|
||||
// server-6666
|
||||
,
|
||||
millisecond: {$millisecond: '$date'}
|
||||
// server-6666
|
||||
millisecond: {$millisecond: "$date"},
|
||||
|
||||
// server-9289
|
||||
,
|
||||
millisecondPlusTen: {$millisecond: {$add: ['$date', 10]}}
|
||||
// server-9289
|
||||
millisecondPlusTen: {$millisecond: {$add: ["$date", 10]}},
|
||||
|
||||
// server-11118
|
||||
,
|
||||
format: {$dateToString: {format: ISOfmt, date: '$date'}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
// server-11118
|
||||
format: {$dateToString: {format: ISOfmt, date: "$date"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
});
|
||||
|
||||
if (date.valueOf() < 0 && _isWindows() && res.code == 16422) {
|
||||
@ -58,20 +56,22 @@ function test(date, testSynthetics) {
|
||||
assert.eq(res.cursor.firstBatch[0].minute, date.getUTCMinutes(), "minute");
|
||||
assert.eq(res.cursor.firstBatch[0].second, date.getUTCSeconds(), "second");
|
||||
assert.eq(res.cursor.firstBatch[0].millisecond, date.getUTCMilliseconds(), "millisecond");
|
||||
assert.eq(res.cursor.firstBatch[0].millisecondPlusTen,
|
||||
(date.getUTCMilliseconds() + 10) % 1000,
|
||||
"millisecondPlusTen");
|
||||
assert.eq(
|
||||
res.cursor.firstBatch[0].millisecondPlusTen,
|
||||
(date.getUTCMilliseconds() + 10) % 1000,
|
||||
"millisecondPlusTen",
|
||||
);
|
||||
assert.eq(res.cursor.firstBatch[0].format, date.tojson(), "format");
|
||||
assert.eq(res.cursor.firstBatch[0], {
|
||||
year: date.getUTCFullYear(),
|
||||
month: date.getUTCMonth() + 1, // jan == 1
|
||||
month: date.getUTCMonth() + 1, // jan == 1
|
||||
dayOfMonth: date.getUTCDate(),
|
||||
hour: date.getUTCHours(),
|
||||
minute: date.getUTCMinutes(),
|
||||
second: date.getUTCSeconds(),
|
||||
millisecond: date.getUTCMilliseconds(),
|
||||
millisecondPlusTen: ((date.getUTCMilliseconds() + 10) % 1000),
|
||||
format: date.tojson()
|
||||
millisecondPlusTen: (date.getUTCMilliseconds() + 10) % 1000,
|
||||
format: date.tojson(),
|
||||
});
|
||||
|
||||
if (testSynthetics) {
|
||||
@ -79,39 +79,39 @@ function test(date, testSynthetics) {
|
||||
res = c.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
week: {$week: '$date'},
|
||||
dayOfWeek: {$dayOfWeek: '$date'},
|
||||
dayOfYear: {$dayOfYear: '$date'},
|
||||
format: {$dateToString: {format: '%U-%w-%j', date: '$date'}}
|
||||
}
|
||||
week: {$week: "$date"},
|
||||
dayOfWeek: {$dayOfWeek: "$date"},
|
||||
dayOfYear: {$dayOfYear: "$date"},
|
||||
format: {$dateToString: {format: "%U-%w-%j", date: "$date"}},
|
||||
},
|
||||
});
|
||||
|
||||
assert.eq(res.toArray()[0], {week: 0, dayOfWeek: 7, dayOfYear: 2, format: '00-7-002'});
|
||||
assert.eq(res.toArray()[0], {week: 0, dayOfWeek: 7, dayOfYear: 2, format: "00-7-002"});
|
||||
}
|
||||
}
|
||||
|
||||
// Basic test
|
||||
test(ISODate('1960-01-02 03:04:05.006Z'), true);
|
||||
test(ISODate("1960-01-02 03:04:05.006Z"), true);
|
||||
|
||||
// Testing special rounding rules for seconds
|
||||
test(ISODate('1960-01-02 03:04:04.999Z'), false); // second = 4
|
||||
test(ISODate('1960-01-02 03:04:05.000Z'), true); // second = 5
|
||||
test(ISODate('1960-01-02 03:04:05.001Z'), true); // second = 5
|
||||
test(ISODate('1960-01-02 03:04:05.999Z'), true); // second = 5
|
||||
test(ISODate("1960-01-02 03:04:04.999Z"), false); // second = 4
|
||||
test(ISODate("1960-01-02 03:04:05.000Z"), true); // second = 5
|
||||
test(ISODate("1960-01-02 03:04:05.001Z"), true); // second = 5
|
||||
test(ISODate("1960-01-02 03:04:05.999Z"), true); // second = 5
|
||||
|
||||
// Test date before 1900 (negative tm_year values from gmtime)
|
||||
test(ISODate('1860-01-02 03:04:05.006Z'), false);
|
||||
test(ISODate("1860-01-02 03:04:05.006Z"), false);
|
||||
|
||||
// Test with time_t == -1 and 0
|
||||
test(new Date(-1000), false);
|
||||
test(new Date(0), false);
|
||||
|
||||
// Testing dates between 1970 and 2000
|
||||
test(ISODate('1970-01-01 00:00:00.000Z'), false);
|
||||
test(ISODate('1970-01-01 00:00:00.999Z'), false);
|
||||
test(ISODate('1980-05-20 12:53:64.834Z'), false);
|
||||
test(ISODate('1999-12-31 00:00:00.000Z'), false);
|
||||
test(ISODate('1999-12-31 23:59:59.999Z'), false);
|
||||
test(ISODate("1970-01-01 00:00:00.000Z"), false);
|
||||
test(ISODate("1970-01-01 00:00:00.999Z"), false);
|
||||
test(ISODate("1980-05-20 12:53:64.834Z"), false);
|
||||
test(ISODate("1999-12-31 00:00:00.000Z"), false);
|
||||
test(ISODate("1999-12-31 23:59:59.999Z"), false);
|
||||
|
||||
// Test date > 2000 for completeness (using now)
|
||||
test(new Date(), false);
|
||||
|
||||
@ -13,36 +13,40 @@ import "jstests/libs/query/sbe_assert_error_override.js";
|
||||
const coll = db.date_expressions_with_time_zones;
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
// Three sales on 2017-06-16 in UTC.
|
||||
{_id: 0, date: new ISODate("2017-06-16T00:00:00.000Z"), sales: 1},
|
||||
{_id: 1, date: new ISODate("2017-06-16T12:02:21.013Z"), sales: 2},
|
||||
// Six sales on 2017-06-17 in UTC.
|
||||
{_id: 2, date: new ISODate("2017-06-17T00:00:00.000Z"), sales: 2},
|
||||
{_id: 3, date: new ISODate("2017-06-17T12:02:21.013Z"), sales: 2},
|
||||
{_id: 4, date: new ISODate("2017-06-17T15:00:33.101Z"), sales: 2},
|
||||
]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
// Three sales on 2017-06-16 in UTC.
|
||||
{_id: 0, date: new ISODate("2017-06-16T00:00:00.000Z"), sales: 1},
|
||||
{_id: 1, date: new ISODate("2017-06-16T12:02:21.013Z"), sales: 2},
|
||||
// Six sales on 2017-06-17 in UTC.
|
||||
{_id: 2, date: new ISODate("2017-06-17T00:00:00.000Z"), sales: 2},
|
||||
{_id: 3, date: new ISODate("2017-06-17T12:02:21.013Z"), sales: 2},
|
||||
{_id: 4, date: new ISODate("2017-06-17T15:00:33.101Z"), sales: 2},
|
||||
]),
|
||||
);
|
||||
|
||||
// Compute how many sales happened on each day, in UTC.
|
||||
assert.eq(
|
||||
[
|
||||
{_id: {year: 2017, month: 6, day: 16}, totalSales: 3},
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 6}
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 6},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: {$year: "$date"},
|
||||
month: {$month: "$date"},
|
||||
day: {$dayOfMonth: "$date"}
|
||||
day: {$dayOfMonth: "$date"},
|
||||
},
|
||||
totalSales: {$sum: "$sales"}
|
||||
}
|
||||
totalSales: {$sum: "$sales"},
|
||||
},
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Compute how many sales happened on each day, in New York. The sales made at midnight should
|
||||
// move to the previous days.
|
||||
@ -50,44 +54,48 @@ assert.eq(
|
||||
[
|
||||
{_id: {year: 2017, month: 6, day: 15}, totalSales: 1},
|
||||
{_id: {year: 2017, month: 6, day: 16}, totalSales: 4},
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4}
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: {$year: {date: "$date", timezone: "America/New_York"}},
|
||||
month: {$month: {date: "$date", timezone: "America/New_York"}},
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "America/New_York"}}
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "America/New_York"}},
|
||||
},
|
||||
totalSales: {$sum: "$sales"}
|
||||
}
|
||||
totalSales: {$sum: "$sales"},
|
||||
},
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Compute how many sales happened on each day, in Sydney (+10 hours).
|
||||
assert.eq(
|
||||
[
|
||||
{_id: {year: 2017, month: 6, day: 16}, totalSales: 3},
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4},
|
||||
{_id: {year: 2017, month: 6, day: 18}, totalSales: 2}
|
||||
{_id: {year: 2017, month: 6, day: 18}, totalSales: 2},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: {$year: {date: "$date", timezone: "Australia/Sydney"}},
|
||||
month: {$month: {date: "$date", timezone: "Australia/Sydney"}},
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "Australia/Sydney"}}
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "Australia/Sydney"}},
|
||||
},
|
||||
totalSales: {$sum: "$sales"}
|
||||
}
|
||||
totalSales: {$sum: "$sales"},
|
||||
},
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({}));
|
||||
@ -101,36 +109,38 @@ function runDateTimeExpressionWithTimezone(exprName, tz) {
|
||||
|
||||
function testDateTimeExpression(exprName, expectedValues) {
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insert({date: ISODate("2017-01-16T01:02:03.456Z"), timezone: "America/Sao_Paulo"}));
|
||||
assert.eq(expectedValues.idBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.commandWorked(coll.insert({date: ISODate("2017-01-16T01:02:03.456Z"), timezone: "America/Sao_Paulo"}));
|
||||
assert.eq(
|
||||
expectedValues.idBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "America/Sao_Paulo").cursor.firstBatch[0].out);
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out,
|
||||
);
|
||||
assert.eq(
|
||||
expectedValues.idBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "America/Sao_Paulo").cursor.firstBatch[0].out,
|
||||
);
|
||||
|
||||
// Test expression with offset based timezone
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insert({date: ISODate("2017-01-01T01:02:03.456Z"), timezone: "-01:30"}));
|
||||
assert.eq(expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "-01:30").cursor.firstBatch[0].out);
|
||||
assert.commandWorked(coll.insert({date: ISODate("2017-01-01T01:02:03.456Z"), timezone: "-01:30"}));
|
||||
assert.eq(
|
||||
expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out,
|
||||
);
|
||||
assert.eq(
|
||||
expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "-01:30").cursor.firstBatch[0].out,
|
||||
);
|
||||
|
||||
// Test expression when document has no $timezone field
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({date: ISODate("2017-01-16T01:02:03.456Z")}));
|
||||
assert.eq(null,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(expectedValues.noTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName).cursor.firstBatch[0].out);
|
||||
assert.eq(null, runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(expectedValues.noTzExpected, runDateTimeExpressionWithTimezone(exprName).cursor.firstBatch[0].out);
|
||||
|
||||
// Test expression when document has no date field
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({timezone: "America/Sao_Paulo"}));
|
||||
assert.eq(null,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(null, runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
|
||||
// test with invalid timezone identifier
|
||||
assert(coll.drop());
|
||||
@ -146,36 +156,23 @@ function testDateTimeExpression(exprName, expectedValues) {
|
||||
|
||||
// test with invalid date type
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insert({date: "2017-06-16T00:00:00.000Z", timezone: "America/Sao_Paulo"}));
|
||||
assert.commandWorked(coll.insert({date: "2017-06-16T00:00:00.000Z", timezone: "America/Sao_Paulo"}));
|
||||
assert.commandFailedWithCode(runDateTimeExpressionWithTimezone(exprName, "$timezone"), 16006);
|
||||
}
|
||||
|
||||
testDateTimeExpression("$dayOfWeek",
|
||||
{idBasedTzExpected: 1, offsetBasedTzExpected: 7, noTzExpected: 2});
|
||||
testDateTimeExpression("$dayOfMonth",
|
||||
{idBasedTzExpected: 15, offsetBasedTzExpected: 31, noTzExpected: 16});
|
||||
testDateTimeExpression("$dayOfYear",
|
||||
{idBasedTzExpected: 15, offsetBasedTzExpected: 366, noTzExpected: 16});
|
||||
testDateTimeExpression("$year",
|
||||
{idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$month",
|
||||
{idBasedTzExpected: 1, offsetBasedTzExpected: 12, noTzExpected: 1});
|
||||
testDateTimeExpression("$hour",
|
||||
{idBasedTzExpected: 23, offsetBasedTzExpected: 23, noTzExpected: 1});
|
||||
testDateTimeExpression("$minute",
|
||||
{idBasedTzExpected: 2, offsetBasedTzExpected: 32, noTzExpected: 2});
|
||||
testDateTimeExpression("$second",
|
||||
{idBasedTzExpected: 3, offsetBasedTzExpected: 3, noTzExpected: 3});
|
||||
testDateTimeExpression("$millisecond",
|
||||
{idBasedTzExpected: 456, offsetBasedTzExpected: 456, noTzExpected: 456});
|
||||
testDateTimeExpression("$dayOfWeek", {idBasedTzExpected: 1, offsetBasedTzExpected: 7, noTzExpected: 2});
|
||||
testDateTimeExpression("$dayOfMonth", {idBasedTzExpected: 15, offsetBasedTzExpected: 31, noTzExpected: 16});
|
||||
testDateTimeExpression("$dayOfYear", {idBasedTzExpected: 15, offsetBasedTzExpected: 366, noTzExpected: 16});
|
||||
testDateTimeExpression("$year", {idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$month", {idBasedTzExpected: 1, offsetBasedTzExpected: 12, noTzExpected: 1});
|
||||
testDateTimeExpression("$hour", {idBasedTzExpected: 23, offsetBasedTzExpected: 23, noTzExpected: 1});
|
||||
testDateTimeExpression("$minute", {idBasedTzExpected: 2, offsetBasedTzExpected: 32, noTzExpected: 2});
|
||||
testDateTimeExpression("$second", {idBasedTzExpected: 3, offsetBasedTzExpected: 3, noTzExpected: 3});
|
||||
testDateTimeExpression("$millisecond", {idBasedTzExpected: 456, offsetBasedTzExpected: 456, noTzExpected: 456});
|
||||
testDateTimeExpression("$week", {idBasedTzExpected: 3, offsetBasedTzExpected: 52, noTzExpected: 3});
|
||||
testDateTimeExpression("$isoWeekYear",
|
||||
{idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$isoDayOfWeek",
|
||||
{idBasedTzExpected: 7, offsetBasedTzExpected: 6, noTzExpected: 1});
|
||||
testDateTimeExpression("$isoWeek",
|
||||
{idBasedTzExpected: 2, offsetBasedTzExpected: 52, noTzExpected: 3});
|
||||
testDateTimeExpression("$isoWeekYear", {idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$isoDayOfWeek", {idBasedTzExpected: 7, offsetBasedTzExpected: 6, noTzExpected: 1});
|
||||
testDateTimeExpression("$isoWeek", {idBasedTzExpected: 2, offsetBasedTzExpected: 52, noTzExpected: 3});
|
||||
|
||||
// Make sure the data type returned by the date/time expressions is correct
|
||||
function testDateTimeExpressionType(exprName, exprType) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user