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
|
||||
|
||||
@ -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: {
|
||||
@ -17,19 +12,17 @@ export default {
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression: function (node) {
|
||||
if (node.callee.type == "Identifier" &&
|
||||
stopList.some(fn => fn == node.callee.name)) {
|
||||
context.report(
|
||||
{
|
||||
if (node.callee.type == "Identifier" && stopList.some((fn) => fn == node.callee.name)) {
|
||||
context.report({
|
||||
node,
|
||||
message: `Direct use of '${
|
||||
node.callee
|
||||
.name}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn'
|
||||
node.callee.name
|
||||
}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn'
|
||||
|
||||
More about rules configuration: https://eslint.org/docs/latest/use/configure/rules`,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -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: {
|
||||
@ -33,40 +32,28 @@ export default {
|
||||
CallExpression: function (node) {
|
||||
if (node.callee.type == "MemberExpression") {
|
||||
node.callee.name = flattenMemberExpressionName(node.callee);
|
||||
} else if (node.callee.type != "Identifier") return;
|
||||
|
||||
} else if (node.callee.type != "Identifier")
|
||||
return;
|
||||
if (print_fns.every((name) => name != node.callee.name)) return;
|
||||
|
||||
if (print_fns.every((name) => name != node.callee.name))
|
||||
return;
|
||||
node.arguments.forEach((arg) => {
|
||||
if (arg.type != "CallExpression") return;
|
||||
|
||||
node.arguments.forEach(arg => {
|
||||
if (arg.type != "CallExpression")
|
||||
return;
|
||||
if (arg.callee.type != "Identifier") return;
|
||||
|
||||
if (arg.callee.type != "Identifier")
|
||||
return;
|
||||
if (arg.callee.name != "tojson" && arg.callee.name != "tojsononeline") return;
|
||||
|
||||
if (arg.callee.name != "tojson" && arg.callee.name != "tojsononeline")
|
||||
return;
|
||||
|
||||
context.report(
|
||||
{
|
||||
context.report({
|
||||
node,
|
||||
message: `Calling ${arg.callee.name}() as a parameter of ${
|
||||
node.callee
|
||||
.name}(). Consider using toJsonForLog() instead or disable this rule by adding '// eslint-disable-next-line mongodb/no-printing-tojson'`,
|
||||
node.callee.name
|
||||
}(). Consider using toJsonForLog() instead or disable this rule by adding '// eslint-disable-next-line mongodb/no-printing-tojson'`,
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange(
|
||||
[
|
||||
arg.callee.start,
|
||||
arg.callee.end,
|
||||
],
|
||||
"toJsonForLog");
|
||||
}
|
||||
return fixer.replaceTextRange([arg.callee.start, arg.callee.end], "toJsonForLog");
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -1 +1 @@
|
||||
assert.eq(true, false)
|
||||
assert.eq(true, false);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
writeFile(TestData.outputLocation,
|
||||
tojson(db.adminCommand("getCmdLineOpts")["parsed"]["setParameter"]));
|
||||
}());
|
||||
writeFile(TestData.outputLocation, tojson(db.adminCommand("getCmdLineOpts")["parsed"]["setParameter"]));
|
||||
})();
|
||||
|
||||
@ -4,6 +4,5 @@
|
||||
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
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import {describe, it} from "jstests/libs/mochalite.js";
|
||||
|
||||
describe("fruits", () => {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
|
||||
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);
|
||||
|
||||
@ -4,11 +4,13 @@ const adminDB = db.getSiblingDB("admin");
|
||||
|
||||
assert.commandWorked(t.insert({_id: 1, x: 1}));
|
||||
|
||||
assert.commandWorked(adminDB.runCommand({
|
||||
assert.commandWorked(
|
||||
adminDB.runCommand({
|
||||
configureFailPoint: "failCommand",
|
||||
mode: "alwaysOn",
|
||||
data: {
|
||||
errorCode: ErrorCodes.InternalError,
|
||||
failCommands: ["validate"],
|
||||
}
|
||||
}));
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@ -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,9 +12,10 @@ for (const word of ["hello", "world", "world", "hello", "hi"]) {
|
||||
}
|
||||
|
||||
const command = {
|
||||
aggregate: 'accumulator_js',
|
||||
aggregate: "accumulator_js",
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCount: {
|
||||
@ -31,11 +32,12 @@ const command = {
|
||||
},
|
||||
finalize: function (state) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let expectedResults = [
|
||||
@ -65,8 +67,11 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
// Test a finalizer other than the identity function. Finalizers are useful when the intermediate
|
||||
// state needs to be a different format from the final result.
|
||||
res = assert.commandWorked(db.runCommand(Object.merge(command, {
|
||||
pipeline: [{
|
||||
res = assert.commandWorked(
|
||||
db.runCommand(
|
||||
Object.merge(command, {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
avgWordLen: {
|
||||
@ -84,12 +89,15 @@ res = assert.commandWorked(db.runCommand(Object.merge(command, {
|
||||
finalize: function ({count, sum}) {
|
||||
return sum / count;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
lang: "js",
|
||||
},
|
||||
}
|
||||
}],
|
||||
})));
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, avgWordLen: 22 / 5}]), res.cursor);
|
||||
|
||||
// Test that a null word is considered a valid value.
|
||||
@ -102,7 +110,8 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
// This is similar to how most other agg operators work.
|
||||
assert(db.accumulator_js.drop());
|
||||
assert.commandWorked(db.accumulator_js.insert({sentinel: 1}));
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -117,16 +126,18 @@ command.pipeline = [{
|
||||
merge: function (s1, s2) {
|
||||
return s1.concat(s2);
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: [null]}]), res.cursor);
|
||||
|
||||
// Test that initArgs must evaluate to an array.
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -136,15 +147,17 @@ command.pipeline = [{
|
||||
accumulateArgs: [],
|
||||
accumulate: function () {},
|
||||
merge: function () {},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.commandFailedWithCode(db.runCommand(command), 4544711);
|
||||
|
||||
// Test that initArgs is passed to init.
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -160,52 +173,57 @@ command.pipeline = [{
|
||||
merge: function (s1, s2) {
|
||||
return s1;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: "initial_state_set_from_ABC_and_DEF"}]),
|
||||
res.cursor);
|
||||
assert(resultsEq(res.cursor.firstBatch, [{_id: 1, value: "initial_state_set_from_ABC_and_DEF"}]), res.cursor);
|
||||
|
||||
// Test that when initArgs errors, we fail gracefully, and don't call init.
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
$accumulator: {
|
||||
init: function () {
|
||||
throw 'init should not be called';
|
||||
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';
|
||||
throw "accumulate should not be called";
|
||||
},
|
||||
merge: function () {
|
||||
throw 'merge should not be called';
|
||||
throw "merge should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
// ErrorCodes.TypeMismatch means "$add only supports numeric or date types". Code 16554 represented
|
||||
// a type mismatch before 6.1 for this specific check.
|
||||
assert.commandFailedWithCode(db.runCommand(command), [16554, ErrorCodes.TypeMismatch]);
|
||||
|
||||
// Test that initArgs can have a different length per group.
|
||||
assert(db.accumulator_js.drop());
|
||||
assert.commandWorked(db.accumulator_js.insert([
|
||||
{_id: 1, a: ['A', 'B', 'C']},
|
||||
{_id: 2, a: ['A', 'B', 'C']},
|
||||
{_id: 3, a: ['X', 'Y']},
|
||||
{_id: 4, a: ['X', 'Y']},
|
||||
]));
|
||||
command.pipeline = [{
|
||||
assert.commandWorked(
|
||||
db.accumulator_js.insert([
|
||||
{_id: 1, a: ["A", "B", "C"]},
|
||||
{_id: 2, a: ["A", "B", "C"]},
|
||||
{_id: 3, a: ["X", "Y"]},
|
||||
{_id: 4, a: ["X", "Y"]},
|
||||
]),
|
||||
);
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: {a: "$a"},
|
||||
value: {
|
||||
@ -221,19 +239,24 @@ command.pipeline = [{
|
||||
merge: function (s1, s2) {
|
||||
return s1;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
assert(
|
||||
resultsEq(res.cursor.firstBatch,
|
||||
[{_id: {a: ['A', 'B', 'C']}, value: "A,B,C"}, {_id: {a: ['X', 'Y']}, value: "X,Y"}]),
|
||||
res.cursor);
|
||||
resultsEq(res.cursor.firstBatch, [
|
||||
{_id: {a: ["A", "B", "C"]}, value: "A,B,C"},
|
||||
{_id: {a: ["X", "Y"]}, value: "X,Y"},
|
||||
]),
|
||||
res.cursor,
|
||||
);
|
||||
|
||||
// Test that accumulateArgs must evaluate to an array.
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -242,15 +265,17 @@ command.pipeline = [{
|
||||
accumulateArgs: {$const: 5},
|
||||
accumulate: function (state, value) {},
|
||||
merge: function (s1, s2) {},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.commandFailedWithCode(db.runCommand(command), 4544712);
|
||||
|
||||
// Test that accumulateArgs can have more than one element.
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -263,19 +288,19 @@ command.pipeline = [{
|
||||
merge: function (s1, s2) {
|
||||
return s1 || s2;
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [
|
||||
{_id: 1, value: "ABCDEF"},
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [{_id: 1, value: "ABCDEF"}];
|
||||
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
// Test that accumulateArgs can have a different length per document.
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -292,14 +317,23 @@ command.pipeline = [{
|
||||
merge: function (s1, s2) {
|
||||
return s1.concat(s2);
|
||||
},
|
||||
lang: 'js',
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = assert.commandWorked(db.runCommand(command));
|
||||
expectedResults = [
|
||||
{_id: 1, value: [['A', 'B', 'C'], ['A', 'B', 'C'], ['X', 'Y'], ['X', 'Y']]},
|
||||
{
|
||||
_id: 1,
|
||||
value: [
|
||||
["A", "B", "C"],
|
||||
["A", "B", "C"],
|
||||
["X", "Y"],
|
||||
["X", "Y"],
|
||||
],
|
||||
},
|
||||
];
|
||||
assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
|
||||
@ -309,7 +343,8 @@ assert(resultsEq(res.cursor.firstBatch, expectedResults), res.cursor);
|
||||
// to JS as usual.
|
||||
assert(db.accumulator_js.drop());
|
||||
assert.commandWorked(db.accumulator_js.insert({}));
|
||||
command.pipeline = [{
|
||||
command.pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 1,
|
||||
value: {
|
||||
@ -320,25 +355,24 @@ command.pipeline = [{
|
||||
accumulateArgs: [
|
||||
null,
|
||||
"$no_such_field",
|
||||
{$let: {vars: {not_an_object: 5}, in : "$not_an_object.field"}}
|
||||
{$let: {vars: {not_an_object: 5}, in: "$not_an_object.field"}},
|
||||
],
|
||||
accumulate: function (state, ...values) {
|
||||
return {
|
||||
len: values.length,
|
||||
types: values.map(v => typeof v),
|
||||
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]}},
|
||||
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);
|
||||
@ -9,12 +9,14 @@ function runExample(groupKey, accumulatorSpec, aggregateOptions = {}) {
|
||||
const aggregateCmd = {
|
||||
aggregate: coll.getName(),
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: groupKey,
|
||||
accumulatedField: {$accumulator: accumulatorSpec},
|
||||
}
|
||||
}]
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
return coll.runCommand(Object.assign(aggregateCmd, aggregateOptions));
|
||||
}
|
||||
@ -27,16 +29,16 @@ let res = runExample(1, {
|
||||
return "a".repeat(20 * 1024 * 1024);
|
||||
},
|
||||
accumulate: function () {
|
||||
throw 'accumulate should not be called';
|
||||
throw "accumulate should not be called";
|
||||
},
|
||||
accumulateArgs: [],
|
||||
merge: function () {
|
||||
throw 'merge should not be called';
|
||||
throw "merge should not be called";
|
||||
},
|
||||
finalize: function () {
|
||||
throw 'finalize should not be called';
|
||||
throw "finalize should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
});
|
||||
assert.commandFailedWithCode(res, [16493, 10334]);
|
||||
|
||||
@ -49,16 +51,16 @@ res = runExample(1, {
|
||||
return Array.from({length: 20}, () => str);
|
||||
},
|
||||
accumulate: function () {
|
||||
throw 'accumulate should not be called';
|
||||
throw "accumulate should not be called";
|
||||
},
|
||||
accumulateArgs: [],
|
||||
merge: function () {
|
||||
throw 'merge should not be called';
|
||||
throw "merge should not be called";
|
||||
},
|
||||
finalize: function () {
|
||||
throw 'finalize should not be called';
|
||||
throw "finalize should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
});
|
||||
assert.commandFailedWithCode(res, [17260, 10334]);
|
||||
|
||||
@ -80,9 +82,9 @@ res = runExample(1, {
|
||||
return state1.concat(state2);
|
||||
},
|
||||
finalize: function () {
|
||||
throw 'finalize should not be called';
|
||||
throw "finalize should not be called";
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
});
|
||||
assert.commandFailedWithCode(res, [4545000]);
|
||||
|
||||
@ -91,7 +93,8 @@ assert(coll.drop());
|
||||
assert.commandWorked(coll.insert(Array.from({length: 200}, (_, i) => ({_id: i}))));
|
||||
// By grouping on _id, each group contains only 1 document. This means it creates many
|
||||
// AccumulatorState instances.
|
||||
res = runExample("$_id",
|
||||
res = runExample(
|
||||
"$_id",
|
||||
{
|
||||
init: function () {
|
||||
// Each accumulator state is big enough to be expensive, but not big enough
|
||||
@ -108,13 +111,16 @@ res = runExample("$_id",
|
||||
finalize: function (state) {
|
||||
return state.length;
|
||||
},
|
||||
lang: 'js',
|
||||
lang: "js",
|
||||
},
|
||||
{allowDiskUse: false});
|
||||
{allowDiskUse: false},
|
||||
);
|
||||
// If featureFlagShardFilteringDistinctScan is on, we will push this $group down to shards on
|
||||
// sharded collection passthrough suites, and may run out of space during JS execution of init().
|
||||
assert.commandFailedWithCode(
|
||||
res, [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.JSInterpreterFailure]);
|
||||
assert.commandFailedWithCode(res, [
|
||||
ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
ErrorCodes.JSInterpreterFailure,
|
||||
]);
|
||||
|
||||
// Verify that having large number of documents doesn't cause the $accumulator to run out of memory.
|
||||
coll.drop();
|
||||
@ -134,26 +140,32 @@ const largeAccumulator = {
|
||||
},
|
||||
finalize: function (state) {
|
||||
return state.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
res = coll.aggregate([
|
||||
res = coll
|
||||
.aggregate([
|
||||
{$addFields: {a: {$range: [0, 1000000]}}},
|
||||
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
|
||||
{$group: {_id: "$groupBy", count: largeAccumulator}}
|
||||
{$group: {_id: "$groupBy", count: largeAccumulator}},
|
||||
])
|
||||
.toArray();
|
||||
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
|
||||
assert.sameMembers(res, [
|
||||
{_id: 1, count: 1000000},
|
||||
{_id: 2, count: 1000000},
|
||||
]);
|
||||
|
||||
// With $bucket.
|
||||
res =
|
||||
coll.aggregate([
|
||||
res = coll
|
||||
.aggregate([
|
||||
{$addFields: {a: {$range: [0, 1000000]}}},
|
||||
{$unwind: "$a"}, // Create a number of documents to be executed by the accumulator.
|
||||
{
|
||||
$bucket:
|
||||
{groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}}
|
||||
}
|
||||
$bucket: {groupBy: "$groupBy", boundaries: [1, 2, 3], output: {count: largeAccumulator}},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.sameMembers(res, [{_id: 1, count: 1000000}, {_id: 2, count: 1000000}]);
|
||||
assert.sameMembers(res, [
|
||||
{_id: 1, count: 1000000},
|
||||
{_id: 2, count: 1000000},
|
||||
]);
|
||||
|
||||
@ -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}]);
|
||||
@ -15,55 +15,50 @@ coll.insertMany(docs);
|
||||
{
|
||||
// One bucket, all the documents coming out of the $bucketAuto stage should be in the same
|
||||
// order as they came in (i.e. sorted by _id).
|
||||
const res =
|
||||
coll.aggregate([
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}}
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {nums: {$concatArrays: "$arr"}}}},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{
|
||||
const expected = [
|
||||
{
|
||||
"_id": {"min": 0, "max": 9},
|
||||
"nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42, 5, 42, 6, 42, 7, 42, 8, 42, 9, 42]
|
||||
}];
|
||||
assert.eq(
|
||||
res,
|
||||
expected,
|
||||
"$bucketAuto with 1 bucket & $concatArrays does not obey sort order of incoming docs.");
|
||||
"nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42, 5, 42, 6, 42, 7, 42, 8, 42, 9, 42],
|
||||
},
|
||||
];
|
||||
assert.eq(res, expected, "$bucketAuto with 1 bucket & $concatArrays does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Two buckets, the documents in each bucket should be sorted by _id as well.
|
||||
const res =
|
||||
coll.aggregate([
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}}
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {nums: {$concatArrays: "$arr"}}}},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [
|
||||
{"_id": {"min": 0, "max": 5}, "nums": [5, 42, 6, 42, 7, 42, 8, 42, 9, 42]},
|
||||
{"_id": {"min": 5, "max": 9}, "nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42]}
|
||||
{"_id": {"min": 5, "max": 9}, "nums": [0, 42, 1, 42, 2, 42, 3, 42, 4, 42]},
|
||||
];
|
||||
assert.eq(
|
||||
res,
|
||||
expected,
|
||||
"$bucketAuto with 2 buckets & $concatArrays does not obey sort order of incoming docs.");
|
||||
assert.eq(res, expected, "$bucketAuto with 2 buckets & $concatArrays does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure that if we try to $concatArrays something that evaluates to 'missing', no value gets
|
||||
// appended to the resulting array.
|
||||
const res =
|
||||
coll.aggregate([{
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{
|
||||
"$bucketAuto": {
|
||||
"groupBy": "$a",
|
||||
"buckets": 1,
|
||||
"output":
|
||||
{"array": {"$concatArrays": {$getField: {"field": "b", "input": {a: 0}}}}}
|
||||
}
|
||||
}])
|
||||
"output": {"array": {"$concatArrays": {$getField: {"field": "b", "input": {a: 0}}}}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{"_id": {"min": null, "max": null}, "array": []}];
|
||||
assert.eq(res,
|
||||
expected,
|
||||
"$bucketAuto with $concatArrays does not append any missing values as null.");
|
||||
assert.eq(res, expected, "$bucketAuto with $concatArrays does not append any missing values as null.");
|
||||
}
|
||||
|
||||
@ -14,13 +14,11 @@ coll.insertMany(docs);
|
||||
{
|
||||
// One bucket, all the documents coming out of the $bucketAuto stage should be in the same
|
||||
// order as they came in (i.e. sorted by _id).
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}
|
||||
])
|
||||
const res = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 1, output: {docs: {$push: "$$ROOT"}}}}])
|
||||
.toArray();
|
||||
const expected = [{
|
||||
const expected = [
|
||||
{
|
||||
"_id": {"min": 0, "max": 9},
|
||||
"docs": [
|
||||
{"_id": 0, "n": 9},
|
||||
@ -32,21 +30,17 @@ coll.insertMany(docs);
|
||||
{"_id": 6, "n": 3},
|
||||
{"_id": 7, "n": 2},
|
||||
{"_id": 8, "n": 1},
|
||||
{"_id": 9, "n": 0}
|
||||
]
|
||||
}];
|
||||
assert.eq(res,
|
||||
expected,
|
||||
"$bucketAuto with 1 bucket & $push does not obey sort order of incoming docs.");
|
||||
{"_id": 9, "n": 0},
|
||||
],
|
||||
},
|
||||
];
|
||||
assert.eq(res, expected, "$bucketAuto with 1 bucket & $push does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Two buckets, the documents in each bucket should be sorted by _id as well.
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}
|
||||
])
|
||||
const res = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$bucketAuto: {groupBy: "$n", buckets: 2, output: {docs: {$push: "$$ROOT"}}}}])
|
||||
.toArray();
|
||||
const expected = [
|
||||
{
|
||||
@ -56,8 +50,8 @@ coll.insertMany(docs);
|
||||
{"_id": 6, "n": 3},
|
||||
{"_id": 7, "n": 2},
|
||||
{"_id": 8, "n": 1},
|
||||
{"_id": 9, "n": 0}
|
||||
]
|
||||
{"_id": 9, "n": 0},
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id": {"min": 5, "max": 9},
|
||||
@ -66,26 +60,26 @@ coll.insertMany(docs);
|
||||
{"_id": 1, "n": 8},
|
||||
{"_id": 2, "n": 7},
|
||||
{"_id": 3, "n": 6},
|
||||
{"_id": 4, "n": 5}
|
||||
]
|
||||
}
|
||||
{"_id": 4, "n": 5},
|
||||
],
|
||||
},
|
||||
];
|
||||
assert.eq(res,
|
||||
expected,
|
||||
"$bucketAuto with 2 buckets & $push does not obey sort order of incoming docs.");
|
||||
assert.eq(res, expected, "$bucketAuto with 2 buckets & $push does not obey sort order of incoming docs.");
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure that if we try to $push something that evaluates to 'missing', no value gets pushed to
|
||||
// the resulting array.
|
||||
const res =
|
||||
coll.aggregate([{
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{
|
||||
"$bucketAuto": {
|
||||
"groupBy": "$a",
|
||||
"buckets": 1,
|
||||
"output": {"array": {"$push": {$getField: {"field": "b", "input": {a: 0}}}}}
|
||||
}
|
||||
}])
|
||||
"output": {"array": {"$push": {$getField: {"field": "b", "input": {a: 0}}}}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
const expected = [{"_id": {"min": null, "max": null}, "array": []}];
|
||||
assert.eq(res, expected, "$bucketAuto with $push does not push any missing values as null.");
|
||||
|
||||
@ -9,77 +9,88 @@ assert(coll.drop());
|
||||
|
||||
// Test that $concatArrays correctly concatenates arrays, preserves sort order and preserves order
|
||||
// of the elements inside the concatenated arrays.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, author: "Adrian", publisher: "Pub1", books: ["Adrian Book 0", "Adrian Book 1"]},
|
||||
{_id: 1, author: "Militsa", publisher: "Pub1", books: ["Militsa Book 0"]},
|
||||
{_id: 2, author: "Hana", publisher: "Pub1", books: ["Hana Book 0", "Hana Book 1"]},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
let result = coll.aggregate([
|
||||
let result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 2]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(
|
||||
result, [{
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
allBooks:
|
||||
["Adrian Book 0", "Adrian Book 1", "Militsa Book 0", "Hana Book 0", "Hana Book 1"]
|
||||
}]);
|
||||
allBooks: ["Adrian Book 0", "Adrian Book 1", "Militsa Book 0", "Hana Book 0", "Hana Book 1"],
|
||||
},
|
||||
]);
|
||||
|
||||
// Redo the aggregation with the reverse sort to ensure sort order is really preserverd and we're
|
||||
// not just looking at the docs in the order they are found in the collection.
|
||||
result = coll.aggregate([
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 2]}}},
|
||||
{$sort: {_id: -1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(
|
||||
result, [{
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
allBooks:
|
||||
["Hana Book 0", "Hana Book 1", "Militsa Book 0", "Adrian Book 0", "Adrian Book 1"]
|
||||
}]);
|
||||
allBooks: ["Hana Book 0", "Hana Book 1", "Militsa Book 0", "Adrian Book 0", "Adrian Book 1"],
|
||||
},
|
||||
]);
|
||||
|
||||
// $concatArrays should concatenate arrays, and any nested arrays should remain array values.
|
||||
assert.commandWorked(coll.insert({
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 3,
|
||||
author: "Ben",
|
||||
publisher: "Pub2",
|
||||
books:
|
||||
[["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"]
|
||||
}));
|
||||
result = coll.aggregate([
|
||||
books: [["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"],
|
||||
}),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [1, 3]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
allBooks: [
|
||||
"Militsa Book 0",
|
||||
["Ben Series 0 Book 0", "Ben Series 0 Book 1"],
|
||||
["Ben Series 1 Book 0"],
|
||||
"Ben Book 4"
|
||||
]
|
||||
}]);
|
||||
"Ben Book 4",
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// $concatArrays should 'skip over' documents that do not have field.
|
||||
// Importantly, do not create a null element for documents that do not have the referenced field.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 4, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
|
||||
{_id: 5, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 6, author: "Matt", publisher: "Pub3", books: ["Happy!"]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{_id: 6, author: "Matt", publisher: "Pub3", books: ["Happy!"]},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [4, 5, 6]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}
|
||||
{$group: {_id: null, allBooks: {$concatArrays: "$books"}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{_id: null, allBooks: ["Smile :)", "Happy!"]}]);
|
||||
@ -88,62 +99,74 @@ assert.eq(result, [{_id: null, allBooks: ["Smile :)", "Happy!"]}]);
|
||||
const notArrays = [1, "string", {object: "object"}, null];
|
||||
|
||||
for (const notAnArray of notArrays) {
|
||||
assert.commandWorked(coll.insert([{
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{
|
||||
_id: "doesNotMatter",
|
||||
author: "doesNotMatter",
|
||||
publisher: "doesNotMatter",
|
||||
books: notAnArray
|
||||
}]));
|
||||
books: notAnArray,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
assertErrorCode(coll,
|
||||
[{$group: {_id: null, allBooks: {$concatArrays: '$books'}}}],
|
||||
ErrorCodes.TypeMismatch);
|
||||
assertErrorCode(coll, [{$group: {_id: null, allBooks: {$concatArrays: "$books"}}}], ErrorCodes.TypeMismatch);
|
||||
|
||||
assert.commandWorked(coll.deleteOne({_id: "doesNotMatter"}));
|
||||
}
|
||||
|
||||
// Basic test of $concatArrays with grouping.
|
||||
result = coll.aggregate([
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$match: {_id: {$in: [0, 1, 3]}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$publisher', booksByPublisher: {$concatArrays: '$books'}}},
|
||||
{$sort: {_id: 1}}
|
||||
{$group: {_id: "$publisher", booksByPublisher: {$concatArrays: "$books"}}},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [
|
||||
{_id: "Pub1", booksByPublisher: ["Adrian Book 0", "Adrian Book 1", "Militsa Book 0"]},
|
||||
{
|
||||
_id: "Pub2",
|
||||
booksByPublisher:
|
||||
[["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"]
|
||||
booksByPublisher: [["Ben Series 0 Book 0", "Ben Series 0 Book 1"], ["Ben Series 1 Book 0"], "Ben Book 4"],
|
||||
},
|
||||
]);
|
||||
|
||||
// $concatArrays dotted field.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert(
|
||||
[{_id: 1, a: {b: [1, 2, 3]}}, {_id: 2, a: {b: [4, 5, 6]}}, {_id: 3, a: {b: [7, 8, 9]}}]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$concatArrays: '$a.b'}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: {b: [1, 2, 3]}},
|
||||
{_id: 2, a: {b: [4, 5, 6]}},
|
||||
{_id: 3, a: {b: [7, 8, 9]}},
|
||||
]),
|
||||
);
|
||||
result = coll.aggregate([{$sort: {_id: 1}}, {$group: {_id: null, nums: {$concatArrays: "$a.b"}}}]).toArray();
|
||||
assert.eq(result, [{_id: null, nums: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
|
||||
assert(coll.drop());
|
||||
|
||||
// $concatArrays dotted field, array halfway on path.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
|
||||
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$concatArrays: '$a.b'}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{_id: null, nums: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [7, 8, 9]]}]);
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]},
|
||||
]),
|
||||
);
|
||||
result = coll.aggregate([{$sort: {_id: 1}}, {$group: {_id: null, nums: {$concatArrays: "$a.b"}}}]).toArray();
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
nums: [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[10, 11, 12],
|
||||
[7, 8, 9],
|
||||
],
|
||||
},
|
||||
]);
|
||||
assert(coll.drop());
|
||||
|
||||
// Basic correctness test for $concatArrays used in $bucket (see bucketAuto_concatArrays.js for
|
||||
@ -157,16 +180,20 @@ for (let i = 0; i < 10; i++) {
|
||||
}
|
||||
coll.insertMany(docs);
|
||||
|
||||
result = coll.aggregate([
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$bucket: {
|
||||
groupBy: '$_id',
|
||||
groupBy: "$_id",
|
||||
boundaries: [0, 5, 10],
|
||||
output: {nums: {$concatArrays: "$arr"}}
|
||||
}
|
||||
}
|
||||
output: {nums: {$concatArrays: "$arr"}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{"_id": 0, "nums": [0, 1, 2, 3, 4]}, {"_id": 5, "nums": [5, 6, 7, 8, 9]}]);
|
||||
assert.eq(result, [
|
||||
{"_id": 0, "nums": [0, 1, 2, 3, 4]},
|
||||
{"_id": 5, "nums": [5, 6, 7, 8, 9]},
|
||||
]);
|
||||
assert(coll.drop());
|
||||
|
||||
@ -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"}}},
|
||||
])
|
||||
{
|
||||
// Test $first when grouping by a string.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$first: "$sales"}}}])
|
||||
.toArray();
|
||||
|
||||
assert(arrayEq(expectedFirstSales, actualResults),
|
||||
() => "expected " + tojson(expectedFirstSales) + " actual " + tojson(actualResults));
|
||||
assert(
|
||||
arrayEq(expectedFirstSales, actualResults),
|
||||
() => "expected " + tojson(expectedFirstSales) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $first when grouping by an object.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: "$stateObj", sales: {$first: "$sales"}}},
|
||||
])
|
||||
{
|
||||
// Test $first when grouping by an object.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$stateObj", sales: {$first: "$sales"}}}])
|
||||
.toArray();
|
||||
|
||||
const expectedResult =
|
||||
expectedFirstSales.map(doc => ({'_id': {'st': doc['_id']}, sales: doc['sales']}));
|
||||
assert(arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults));
|
||||
const expectedResult = expectedFirstSales.map((doc) => ({"_id": {"st": doc["_id"]}, sales: doc["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $last when grouping by a string.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$last: "$sales"}}},
|
||||
])
|
||||
{
|
||||
// Test $last when grouping by a string.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$last: "$sales"}}}])
|
||||
.toArray();
|
||||
assert(arrayEq(expectedLastSales, actualResults),
|
||||
() => "expected " + tojson(expectedLastSales) + " actual " + tojson(actualResults));
|
||||
assert(
|
||||
arrayEq(expectedLastSales, actualResults),
|
||||
() => "expected " + tojson(expectedLastSales) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $last when grouping by an object.
|
||||
const actualResults = coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: "$stateObj", sales: {$last: "$sales"}}},
|
||||
])
|
||||
{
|
||||
// Test $last when grouping by an object.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$stateObj", sales: {$last: "$sales"}}}])
|
||||
.toArray();
|
||||
|
||||
const expectedResult =
|
||||
expectedLastSales.map(doc => ({'_id': {'st': doc['_id']}, sales: doc['sales']}));
|
||||
assert(arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults));
|
||||
const expectedResult = expectedLastSales.map((doc) => ({"_id": {"st": doc["_id"]}, sales: doc["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, actualResults),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
// Basic correctness test for $first/$last used in $bucketAuto. Though $bucketAuto uses
|
||||
@ -90,59 +95,61 @@ assert.commandWorked(coll.insert(docs));
|
||||
// buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
|
||||
{ // Test $first in $bucketAuto.
|
||||
let actualResults =
|
||||
coll.aggregate([
|
||||
{
|
||||
// Test $first in $bucketAuto.
|
||||
let actualResults = coll
|
||||
.aggregate([
|
||||
{$sort: {sales: 1}},
|
||||
{$bucketAuto: {groupBy: '$state', buckets: 1, output: {sales: {$first: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
{$bucketAuto: {groupBy: "$state", buckets: 1, output: {sales: {$first: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
const expectedResults = [{_id: 'AZ', sales: 0}];
|
||||
assert(orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults));
|
||||
const expectedResults = [{_id: "AZ", sales: 0}];
|
||||
assert(
|
||||
orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
{ // Test $last in $bucketAuto.
|
||||
let actualResults =
|
||||
coll.aggregate([
|
||||
{
|
||||
// Test $last in $bucketAuto.
|
||||
let actualResults = coll
|
||||
.aggregate([
|
||||
{$sort: {sales: 1}},
|
||||
{$bucketAuto: {groupBy: '$state', buckets: 1, output: {sales: {$last: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
{$bucketAuto: {groupBy: "$state", buckets: 1, output: {sales: {$last: "$sales"}}}},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
const expectedResults = [{_id: 'AZ', sales: 190}];
|
||||
assert(orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults));
|
||||
const expectedResults = [{_id: "AZ", sales: 190}];
|
||||
assert(
|
||||
orderedArrayEq(expectedResults, actualResults),
|
||||
() => "expected " + tojson(expectedResults) + " actual " + tojson(actualResults),
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that an index on {_id: 1, sales: -1} will produce the expected results.
|
||||
const idxSpec = {
|
||||
_id: 1,
|
||||
sales: -1
|
||||
sales: -1,
|
||||
};
|
||||
assert.commandWorked(coll.createIndex(idxSpec));
|
||||
|
||||
{ // Test $first with index.
|
||||
const actualResults = coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$first: "$sales"}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
{
|
||||
// Test $first with index.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$first: "$sales"}}}, {$sort: {_id: 1}}], {
|
||||
hint: idxSpec,
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(expectedFirstSales, actualResults);
|
||||
}
|
||||
|
||||
{ // Test $last with index.
|
||||
const actualResults = coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$last: "$sales"}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
{
|
||||
// Test $last with index.
|
||||
const actualResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$last: "$sales"}}}, {$sort: {_id: 1}}], {
|
||||
hint: idxSpec,
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(expectedLastSales, actualResults);
|
||||
}
|
||||
|
||||
@ -8,8 +8,7 @@ import {arrayEq} from "jstests/aggregation/extras/utils.js";
|
||||
const coll = db[jsTestName()];
|
||||
coll.drop();
|
||||
|
||||
const largestInt =
|
||||
NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
|
||||
// Basic correctness tests.
|
||||
@ -22,15 +21,18 @@ let expectedLastThree = [];
|
||||
let expectedAllResults = [];
|
||||
let expectedFirstNWithInitExpr = [];
|
||||
let expectedLastNWithInitExpr = [];
|
||||
for (const states
|
||||
of [{state: 'AZ', sales: 3}, {state: 'CA', sales: 2}, {state: 'NY', sales: kMaxSales}]) {
|
||||
for (const states of [
|
||||
{state: "AZ", sales: 3},
|
||||
{state: "CA", sales: 2},
|
||||
{state: "NY", sales: kMaxSales},
|
||||
]) {
|
||||
let allResults = [];
|
||||
let firstThree = [];
|
||||
let lastThree = [];
|
||||
let firstWithInitExpr = [];
|
||||
let lastWithInitExpr = [];
|
||||
const state = states['state'];
|
||||
const sales = states['sales'];
|
||||
const state = states["state"];
|
||||
const sales = states["sales"];
|
||||
for (let i = 0; i < kMaxSales; ++i) {
|
||||
const salesAmt = i * 10;
|
||||
if (i < sales) {
|
||||
@ -45,10 +47,10 @@ for (const states
|
||||
lastThree.push(salesAmt);
|
||||
}
|
||||
|
||||
if (i == 0 || (state == 'AZ' && i < defaultN)) {
|
||||
if (i == 0 || (state == "AZ" && i < defaultN)) {
|
||||
firstWithInitExpr.push(salesAmt);
|
||||
}
|
||||
if (i + 1 == sales || (state == 'AZ' && i + defaultN >= sales)) {
|
||||
if (i + 1 == sales || (state == "AZ" && i + defaultN >= sales)) {
|
||||
lastWithInitExpr.push(salesAmt);
|
||||
}
|
||||
allResults.push(salesAmt);
|
||||
@ -64,22 +66,20 @@ for (const states
|
||||
assert.commandWorked(coll.insert(docs));
|
||||
|
||||
function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
const actualFirstNResults =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
|
||||
])
|
||||
const actualFirstNResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}}])
|
||||
.toArray();
|
||||
|
||||
// As these are unordered operators, we need to ensure we can deterministically test the values
|
||||
// returned by firstN/lastN. As the output is not guaranteed to be in order, arrayEq is used
|
||||
// instead.
|
||||
assert(arrayEq(expectedFirstNResults, actualFirstNResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " +
|
||||
tojson(actualFirstNResults));
|
||||
assert(
|
||||
arrayEq(expectedFirstNResults, actualFirstNResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " + tojson(actualFirstNResults),
|
||||
);
|
||||
|
||||
const firstNResultsWithInitExpr =
|
||||
coll.aggregate([
|
||||
const firstNResultsWithInitExpr = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
@ -87,23 +87,23 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
sales: {
|
||||
$firstN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
let expectedResult = [];
|
||||
expectedFirstNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
assert(arrayEq(expectedResult, firstNResultsWithInitExpr),
|
||||
() => "expected " + tojson(expectedResult) + " actual " +
|
||||
tojson(firstNResultsWithInitExpr));
|
||||
expectedFirstNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, firstNResultsWithInitExpr),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(firstNResultsWithInitExpr),
|
||||
);
|
||||
|
||||
const firstNResultsWithInitExprAndVariableGroupId =
|
||||
coll.aggregate([
|
||||
const firstNResultsWithInitExprAndVariableGroupId = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
@ -111,33 +111,31 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
sales: {
|
||||
$firstN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
expectedResult = [];
|
||||
expectedFirstNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
assert(arrayEq(expectedResult, firstNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " +
|
||||
tojson(firstNResultsWithInitExprAndVariableGroupId));
|
||||
expectedFirstNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, firstNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(firstNResultsWithInitExprAndVariableGroupId),
|
||||
);
|
||||
|
||||
const actualLastNResults =
|
||||
coll.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$lastN: {input: "$sales", n: n}}}},
|
||||
])
|
||||
const actualLastNResults = coll
|
||||
.aggregate([{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$lastN: {input: "$sales", n: n}}}}])
|
||||
.toArray();
|
||||
assert(
|
||||
arrayEq(expectedLastNResults, actualLastNResults),
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNResults));
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNResults),
|
||||
);
|
||||
|
||||
const lastNResultsWithInitExpr =
|
||||
coll.aggregate([
|
||||
const lastNResultsWithInitExpr = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
@ -145,23 +143,23 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
sales: {
|
||||
$lastN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
expectedResult = [];
|
||||
expectedLastNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
expectedLastNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, lastNResultsWithInitExpr),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExpr));
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExpr),
|
||||
);
|
||||
|
||||
const lastNResultsWithInitExprAndVariableGroupId =
|
||||
coll.aggregate([
|
||||
const lastNResultsWithInitExprAndVariableGroupId = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
$group: {
|
||||
@ -169,20 +167,20 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
sales: {
|
||||
$lastN: {
|
||||
input: "$sales",
|
||||
n: {$cond: {if: {$eq: ["$st", 'AZ']}, then: defaultN, else: 1}}
|
||||
}
|
||||
}
|
||||
}
|
||||
n: {$cond: {if: {$eq: ["$st", "AZ"]}, then: defaultN, else: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
expectedResult = [];
|
||||
expectedLastNWithInitExpr.forEach(
|
||||
i => expectedResult.push({'_id': {'st': i['_id']}, sales: i['sales']}));
|
||||
assert(arrayEq(expectedResult, lastNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " +
|
||||
tojson(lastNResultsWithInitExprAndVariableGroupId));
|
||||
expectedLastNWithInitExpr.forEach((i) => expectedResult.push({"_id": {"st": i["_id"]}, sales: i["sales"]}));
|
||||
assert(
|
||||
arrayEq(expectedResult, lastNResultsWithInitExprAndVariableGroupId),
|
||||
() => "expected " + tojson(expectedResult) + " actual " + tojson(lastNResultsWithInitExprAndVariableGroupId),
|
||||
);
|
||||
|
||||
function reorderBucketResults(bucketResults) {
|
||||
// Using a computed projection will put the fields out of order. As such, we re-order them
|
||||
@ -199,66 +197,66 @@ function runFirstLastN(n, expectedFirstNResults, expectedLastNResults) {
|
||||
// to compare the $bucketAuto results to the expected $group results (because there are more
|
||||
// buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
let actualFirstNBucketAutoResults =
|
||||
coll.aggregate([
|
||||
let actualFirstNBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{$sort: {state: 1, sales: 1}},
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {$firstN: {input: "$sales", n: n}}}
|
||||
}
|
||||
output: {sales: {$firstN: {input: "$sales", n: n}}},
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
reorderBucketResults(actualFirstNBucketAutoResults);
|
||||
assert(arrayEq(expectedFirstNResults, actualFirstNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " +
|
||||
tojson(actualFirstNBucketAutoResults));
|
||||
assert(
|
||||
arrayEq(expectedFirstNResults, actualFirstNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedFirstNResults) + " actual " + tojson(actualFirstNBucketAutoResults),
|
||||
);
|
||||
|
||||
let actualLastNBucketAutoResults =
|
||||
coll.aggregate([
|
||||
let actualLastNBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{$sort: {state: 1, sales: 1}},
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {$lastN: {input: "$sales", n: n}}}
|
||||
}
|
||||
output: {sales: {$lastN: {input: "$sales", n: n}}},
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}}
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
])
|
||||
.toArray();
|
||||
reorderBucketResults(actualLastNBucketAutoResults);
|
||||
assert(arrayEq(expectedLastNResults, actualLastNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " +
|
||||
tojson(actualLastNBucketAutoResults));
|
||||
assert(
|
||||
arrayEq(expectedLastNResults, actualLastNBucketAutoResults),
|
||||
() => "expected " + tojson(expectedLastNResults) + " actual " + tojson(actualLastNBucketAutoResults),
|
||||
);
|
||||
|
||||
// Verify that an index on {_id: 1, sales: -1} will produce the expected results.
|
||||
const idxSpec = {_id: 1, sales: -1};
|
||||
assert.commandWorked(coll.createIndex(idxSpec));
|
||||
|
||||
const indexedFirstNResults =
|
||||
coll.aggregate(
|
||||
const indexedFirstNResults = coll
|
||||
.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$firstN: {input: "$sales", n: n}}}},
|
||||
{$group: {_id: "$state", sales: {$firstN: {input: "$sales", n: n}}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
{hint: idxSpec},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(expectedFirstNResults, indexedFirstNResults);
|
||||
|
||||
const indexedLastNResults =
|
||||
coll.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: '$state', sales: {$lastN: {input: "$sales", n: n}}}},
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
{hint: idxSpec})
|
||||
const indexedLastNResults = coll
|
||||
.aggregate(
|
||||
[{$sort: {_id: 1}}, {$group: {_id: "$state", sales: {$lastN: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}],
|
||||
{hint: idxSpec},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(expectedLastNResults, indexedLastNResults);
|
||||
}
|
||||
@ -269,56 +267,67 @@ runFirstLastN(defaultN, expectedFirstThree, expectedLastThree);
|
||||
runFirstLastN(largestInt, expectedAllResults, expectedAllResults);
|
||||
|
||||
// Reject non-integral values of n.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: 'string'}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: "string"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787902);
|
||||
5787902,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: 3.2}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: 3.2}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903);
|
||||
5787903,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales', n: -1}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: -1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787908);
|
||||
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: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales", n: largestIntPlus1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903);
|
||||
5787903,
|
||||
);
|
||||
|
||||
// Reject invalid specifications.
|
||||
|
||||
// Extra fields
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: {'st': '$state'},
|
||||
sales: {$firstN: {input: '$sales', n: 2, randomField: "randomArg"}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
_id: {"st": "$state"},
|
||||
sales: {$firstN: {input: "$sales", n: 2, randomField: "randomArg"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
5787901);
|
||||
5787901,
|
||||
);
|
||||
|
||||
// Missing arguments.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, sales: {$firstN: {input: '$sales'}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, sales: {$firstN: {input: "$sales"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787906);
|
||||
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 = [{
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 9961600, aggFunction);
|
||||
}
|
||||
|
||||
function assertNoError(aggFunction, aggFunctionArgument) {
|
||||
const pipeline = [{
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
|
||||
}
|
||||
|
||||
|
||||
@ -28,13 +28,15 @@ const docs = [
|
||||
assert.commandWorked(coll.insertMany(docs));
|
||||
|
||||
function assertNoError(aggFunction, aggFunctionArgument) {
|
||||
const pipeline = [{
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$groupKey",
|
||||
agg: {[aggFunction]: aggFunctionArgument},
|
||||
$doingMerge: true,
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.eq(coll.aggregate(pipeline).toArray().length, 2);
|
||||
}
|
||||
|
||||
|
||||
@ -17,23 +17,25 @@ function reduce(key, values) {
|
||||
return Array.sum(values);
|
||||
}
|
||||
|
||||
let groupPipe = [{
|
||||
let groupPipe = [
|
||||
{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCount: {
|
||||
$_internalJsReduce: {
|
||||
data: {k: "$word", v: "$val"},
|
||||
eval: reduce,
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let command = {
|
||||
aggregate: 'js_reduce',
|
||||
aggregate: "js_reduce",
|
||||
cursor: {},
|
||||
pipeline: groupPipe,
|
||||
allowDiskUse: true // Set allowDiskUse to true to force the expression to run on a shard in the
|
||||
allowDiskUse: true, // Set allowDiskUse to true to force the expression to run on a shard in the
|
||||
// passthrough suites, where javascript execution is supported.
|
||||
};
|
||||
|
||||
@ -100,17 +102,17 @@ assert.commandFailedWithCode(db.runCommand(command), 31242);
|
||||
|
||||
groupPipe[0].$group.wordCount.$_internalJsReduce = {
|
||||
notEval: 1,
|
||||
notData: 1
|
||||
notData: 1,
|
||||
};
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31243);
|
||||
|
||||
groupPipe[0].$group.wordCount.$_internalJsReduce = {
|
||||
eval: reduce,
|
||||
data: {v: 1}
|
||||
data: {v: 1},
|
||||
};
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31251);
|
||||
groupPipe[0].$group.wordCount.$_internalJsReduce.data = {
|
||||
key: 1,
|
||||
value: 1
|
||||
value: 1,
|
||||
};
|
||||
assert.commandFailedWithCode(db.runCommand(command), 31251);
|
||||
@ -27,19 +27,20 @@ function reduce(key, values) {
|
||||
const command = {
|
||||
aggregate: coll.getName(),
|
||||
cursor: {},
|
||||
runtimeConstants:
|
||||
{localNow: new Date(), clusterTime: new Timestamp(0, 0), jsScope: {modulus: modulus}},
|
||||
pipeline: [{
|
||||
runtimeConstants: {localNow: new Date(), clusterTime: new Timestamp(0, 0), jsScope: {modulus: modulus}},
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: "$word",
|
||||
wordCountMod: {
|
||||
$_internalJsReduce: {
|
||||
data: {k: "$word", v: "$val"},
|
||||
eval: reduce,
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fromRouter: true,
|
||||
};
|
||||
|
||||
|
||||
@ -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 largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
|
||||
// Some big number that will allow us to test big without running this testcase into next decade.
|
||||
const bigN = 10000;
|
||||
|
||||
const states = [
|
||||
{state: 'CA', sales: 10},
|
||||
{state: 'NY', sales: 7},
|
||||
{state: 'TX', sales: 4},
|
||||
{state: 'WY', sales: bigN}
|
||||
{state: "CA", sales: 10},
|
||||
{state: "NY", sales: 7},
|
||||
{state: "TX", sales: 4},
|
||||
{state: "WY", sales: bigN},
|
||||
];
|
||||
let expectedMinNResults = [];
|
||||
let expectedMaxNResults = [];
|
||||
@ -31,8 +30,8 @@ let expectedBigNMinNResults = [];
|
||||
let expectedBigNMaxNResults = [];
|
||||
|
||||
for (const stateDoc of states) {
|
||||
const state = stateDoc['state'];
|
||||
const sales = stateDoc['sales'];
|
||||
const state = stateDoc["state"];
|
||||
const sales = stateDoc["sales"];
|
||||
let minArr = [];
|
||||
let maxArr = [];
|
||||
let minArrForLargestInt = [];
|
||||
@ -80,11 +79,8 @@ assert.commandWorked(coll.insert(docs));
|
||||
// Note that the output documents are sorted by '_id' so that we can compare actual groups against
|
||||
// expected groups (we cannot perform unordered comparison because order matters for $minN/$maxN).
|
||||
function runAndCompareMinMaxN(nFunction, n, expectedResults) {
|
||||
const actualResults =
|
||||
coll.aggregate([
|
||||
{$group: {_id: '$state', sales: {[nFunction]: {input: '$sales', n: n}}}},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
const actualResults = coll
|
||||
.aggregate([{$group: {_id: "$state", sales: {[nFunction]: {input: "$sales", n: n}}}}, {$sort: {_id: 1}}])
|
||||
.toArray();
|
||||
assert.eq(expectedResults, actualResults);
|
||||
|
||||
@ -94,14 +90,14 @@ function runAndCompareMinMaxN(nFunction, n, expectedResults) {
|
||||
// to compare the $bucketAuto results to the expected $group results (because there are more
|
||||
// buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
let actualBucketAutoResults =
|
||||
coll.aggregate([
|
||||
let actualBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output: {sales: {[nFunction]: {input: '$sales', n: n}}}
|
||||
}
|
||||
output: {sales: {[nFunction]: {input: "$sales", n: n}}},
|
||||
},
|
||||
},
|
||||
{$project: {_id: "$_id.min", sales: 1}},
|
||||
{$sort: {_id: 1}},
|
||||
@ -131,23 +127,25 @@ runAndCompareMinMaxN("$maxN", bigN - 1, expectedBigNMaxNResults);
|
||||
|
||||
// Verify that we can dynamically compute 'n' based on the group key for $group.
|
||||
const groupKeyNExpr = {
|
||||
$cond: {if: {$eq: ['$st', 'CA']}, then: 10, else: 4}
|
||||
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4},
|
||||
};
|
||||
const dynamicMinNResults =
|
||||
coll.aggregate([{
|
||||
$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
|
||||
}])
|
||||
const dynamicMinNResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: groupKeyNExpr}}},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
// Verify that the 'CA' group has 10 results, while all others have only 4.
|
||||
for (const result of dynamicMinNResults) {
|
||||
assert(result.hasOwnProperty('_id'), tojson(result));
|
||||
const groupKey = result['_id'];
|
||||
assert(groupKey.hasOwnProperty('st'), tojson(groupKey));
|
||||
const state = groupKey['st'];
|
||||
assert(result.hasOwnProperty('minSales'), tojson(result));
|
||||
const salesArray = result['minSales'];
|
||||
if (state === 'CA') {
|
||||
assert(result.hasOwnProperty("_id"), tojson(result));
|
||||
const groupKey = result["_id"];
|
||||
assert(groupKey.hasOwnProperty("st"), tojson(groupKey));
|
||||
const state = groupKey["st"];
|
||||
assert(result.hasOwnProperty("minSales"), tojson(result));
|
||||
const salesArray = result["minSales"];
|
||||
if (state === "CA") {
|
||||
assert.eq(salesArray.length, 10, tojson(salesArray));
|
||||
} else {
|
||||
assert.eq(salesArray.length, 4, tojson(salesArray));
|
||||
@ -157,75 +155,94 @@ for (const result of dynamicMinNResults) {
|
||||
// Error cases
|
||||
|
||||
// Cannot reference the group key in $minN when using $bucketAuto.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 2,
|
||||
output: {minSales: {$minN: {input: '$sales', n: groupKeyNExpr}}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
output: {minSales: {$minN: {input: "$sales", n: groupKeyNExpr}}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
4544714);
|
||||
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: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: "string"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787902);
|
||||
5787902,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 3.2}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: 3.2}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903);
|
||||
5787903,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: -1}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: -1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787908);
|
||||
5787908,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: 0}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: 0}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787908);
|
||||
5787908,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales', n: largestIntPlus1}}}}
|
||||
],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales", n: largestIntPlus1}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903);
|
||||
5787903,
|
||||
);
|
||||
|
||||
// Reject invalid specifications.
|
||||
|
||||
// Missing arguments.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {input: '$sales'}}}}],
|
||||
cursor: {}
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {input: "$sales"}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787906);
|
||||
5787906,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand(
|
||||
"aggregate",
|
||||
{pipeline: [{$group: {_id: {'st': '$state'}, minSales: {$minN: {n: 2}}}}], cursor: {}}),
|
||||
5787907);
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, minSales: {$minN: {n: 2}}}}],
|
||||
cursor: {},
|
||||
}),
|
||||
5787907,
|
||||
);
|
||||
|
||||
// Extra field.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: {'st': '$state'},
|
||||
minSales: {$minN: {input: '$sales', n: 2, randomField: "randomArg"}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
_id: {"st": "$state"},
|
||||
minSales: {$minN: {input: "$sales", n: 2, randomField: "randomArg"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
5787901);
|
||||
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",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -113,7 +112,7 @@ testWithMultipleGroups({
|
||||
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"
|
||||
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",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -114,7 +113,7 @@ testWithMultipleGroups({
|
||||
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"
|
||||
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",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -121,7 +121,7 @@ testWithMultipleGroups({
|
||||
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"
|
||||
msg: "Multiple groups",
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -29,27 +29,26 @@ function assertValidSyntax({pSpec, letSpec, msg}) {
|
||||
/**
|
||||
* Test missing or unexpected fields in $percentile spec.
|
||||
*/
|
||||
assertInvalidSyntax(
|
||||
{pSpec: {$percentile: 0.5}, msg: "Should fail if $percentile is not an object"});
|
||||
assertInvalidSyntax({pSpec: {$percentile: 0.5}, msg: "Should fail if $percentile is not an object"});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if $percentile is missing 'p' field"
|
||||
msg: "Should fail if $percentile is missing 'p' field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5], method: "approximate"}},
|
||||
msg: "Should fail if $percentile is missing 'input' field"
|
||||
msg: "Should fail if $percentile is missing 'input' field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5], input: "$x"}},
|
||||
msg: "Should fail if $percentile is missing 'method' field"
|
||||
msg: "Should fail if $percentile is missing 'method' field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5], input: "$x", method: "approximate", extras: 42}},
|
||||
msg: "Should fail if $percentile contains an unexpected field"
|
||||
msg: "Should fail if $percentile contains an unexpected field",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -57,23 +56,22 @@ assertInvalidSyntax({
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: 0.5, input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if 'p' field in $percentile isn't array"
|
||||
msg: "Should fail if 'p' field in $percentile isn't array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [], input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if 'p' field in $percentile is an empty array"
|
||||
msg: "Should fail if 'p' field in $percentile is an empty array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, "foo"], input: "$x", method: "approximate"}},
|
||||
msg: "Should fail if 'p' field in $percentile is an array with a non-numeric element"
|
||||
msg: "Should fail if 'p' field in $percentile is an array with a non-numeric element",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 10], input: "$x", method: "approximate"}},
|
||||
msg:
|
||||
"Should fail if 'p' field in $percentile is an array with any value outside of [0, 1] range"
|
||||
msg: "Should fail if 'p' field in $percentile is an array with any value outside of [0, 1] range",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -81,32 +79,31 @@ assertInvalidSyntax({
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: ["$x"], input: "$x", method: "approximate"}},
|
||||
msg: "'p' should not accept non-const expressions"
|
||||
msg: "'p' should not accept non-const expressions",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: {$add: [0.1, 0.5]}, input: "$x", method: "approximate"}},
|
||||
msg: "'p' should not accept expressions that evaluate to a non-array"
|
||||
msg: "'p' should not accept expressions that evaluate to a non-array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {
|
||||
$percentile:
|
||||
{p: {$concatArrays: [[0.01, 0.1], ["foo"]]}, input: "$x", method: "approximate"}
|
||||
$percentile: {p: {$concatArrays: [[0.01, 0.1], ["foo"]]}, input: "$x", method: "approximate"},
|
||||
},
|
||||
msg: "'p' should not accept expressions that evaluate to an array with non-numeric elements"
|
||||
msg: "'p' should not accept expressions that evaluate to an array with non-numeric elements",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
|
||||
letSpec: {pvals: 0.5},
|
||||
msg: "'p' should not accept variables that evaluate to a non-array"
|
||||
msg: "'p' should not accept variables that evaluate to a non-array",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
|
||||
letSpec: {pvals: [0.5, "foo"]},
|
||||
msg: "'p' should not accept variables that evaluate to an array with non-numeric elements"
|
||||
msg: "'p' should not accept variables that evaluate to an array with non-numeric elements",
|
||||
});
|
||||
|
||||
/**
|
||||
@ -114,37 +111,36 @@ assertInvalidSyntax({
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: 42}},
|
||||
msg: "$percentile should fail if 'method' field isn't a string"
|
||||
msg: "$percentile should fail if 'method' field isn't a string",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "fancy"}},
|
||||
msg: "$percentile should fail if 'method' isn't one of _predefined_ strings"
|
||||
msg: "$percentile should fail if 'method' isn't one of _predefined_ strings",
|
||||
});
|
||||
|
||||
if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "discrete"}},
|
||||
msg: "Should work with discrete 'method'"
|
||||
msg: "Should work with discrete 'method'",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.InternalErrorNotSupported,
|
||||
msg: "Should work with continuous 'method'"
|
||||
msg: "Should work with continuous 'method'",
|
||||
});
|
||||
|
||||
} else {
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "discrete"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$percentile should fail because discrete 'method' isn't supported yet"
|
||||
msg: "$percentile should fail because discrete 'method' isn't supported yet",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.7], input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$percentile should fail because continuous 'method' isn't supported yet"
|
||||
msg: "$percentile should fail because continuous 'method' isn't supported yet",
|
||||
});
|
||||
}
|
||||
|
||||
@ -153,50 +149,48 @@ if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
*/
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {p: [0.5], input: "$x", method: "approximate"}},
|
||||
msg: "$median should fail if 'p' is defined"
|
||||
msg: "$median should fail if 'p' is defined",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {method: "approximate"}},
|
||||
msg: "$median should fail if 'input' field is missing"
|
||||
msg: "$median should fail if 'input' field is missing",
|
||||
});
|
||||
|
||||
assertInvalidSyntax(
|
||||
{pSpec: {$median: {input: "$x"}}, msg: "Median should fail if 'method' field is missing"});
|
||||
assertInvalidSyntax({pSpec: {$median: {input: "$x"}}, msg: "Median should fail if 'method' field is missing"});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "approximate", extras: 42}},
|
||||
msg: "$median should fail if there is an unexpected field"
|
||||
msg: "$median should fail if there is an unexpected field",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "fancy"}},
|
||||
msg: "$median should fail if 'method' isn't one of the _predefined_ strings"
|
||||
msg: "$median should fail if 'method' isn't one of the _predefined_ strings",
|
||||
});
|
||||
|
||||
if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
assertValidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "discrete"}},
|
||||
msg: "Should work with discrete 'method'"
|
||||
msg: "Should work with discrete 'method'",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.InternalErrorNotSupported,
|
||||
msg: "Should work with continuous 'method'"
|
||||
msg: "Should work with continuous 'method'",
|
||||
});
|
||||
|
||||
} else {
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "discrete"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$median should fail because discrete 'method' isn't supported yet"
|
||||
msg: "$median should fail because discrete 'method' isn't supported yet",
|
||||
});
|
||||
|
||||
assertInvalidSyntax({
|
||||
pSpec: {$median: {input: "$x", method: "continuous"}},
|
||||
errorCode: ErrorCodes.BadValue,
|
||||
msg: "$median should fail because continuous 'method' isn't supported yet"
|
||||
msg: "$median should fail because continuous 'method' isn't supported yet",
|
||||
});
|
||||
}
|
||||
|
||||
@ -207,58 +201,65 @@ if (FeatureFlagUtil.isPresentAndEnabled(db, "AccuratePercentiles")) {
|
||||
*/
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.0, 0.0001, 0.5, 0.995, 1.0], input: "$x", method: "approximate"}},
|
||||
msg: "Should be able to specify an array of percentiles"
|
||||
msg: "Should be able to specify an array of percentiles",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.9], input: {$divide: ["$x", 2]}, method: "approximate"}},
|
||||
msg: "Should be able to specify 'input' as an expression"
|
||||
msg: "Should be able to specify 'input' as an expression",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.9], input: "x", method: "approximate"}},
|
||||
msg: "Non-numeric inputs should be gracefully ignored"
|
||||
msg: "Non-numeric inputs should be gracefully ignored",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [0.5, 0.9], input: {$add: [2, "$x"]}, method: "approximate"}},
|
||||
msg: "'input' should be able to use expressions"
|
||||
msg: "'input' should be able to use expressions",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {
|
||||
$percentile: {p: [0.5, 0.9], input: {$concatArrays: [[2], ["$x"]]}, method: "approximate"}
|
||||
$percentile: {p: [0.5, 0.9], input: {$concatArrays: [[2], ["$x"]]}, method: "approximate"},
|
||||
},
|
||||
msg: "'input' should be able to use expressions even if the result of their eval is non-numeric"
|
||||
msg: "'input' should be able to use expressions even if the result of their eval is non-numeric",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {
|
||||
$percentile:
|
||||
{p: {$concatArrays: [[0.01, 0.1], [0.9, 0.99]]}, input: "$x", method: "approximate"}
|
||||
$percentile: {
|
||||
p: {
|
||||
$concatArrays: [
|
||||
[0.01, 0.1],
|
||||
[0.9, 0.99],
|
||||
],
|
||||
},
|
||||
msg: "'p' should be able to use expressions that evaluate to an array"
|
||||
input: "$x",
|
||||
method: "approximate",
|
||||
},
|
||||
},
|
||||
msg: "'p' should be able to use expressions that evaluate to an array",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: [{$add: [0.1, 0.5]}], input: "$x", method: "approximate"}},
|
||||
msg: "'p' should be able to use expressions for the array elements"
|
||||
msg: "'p' should be able to use expressions for the array elements",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: "$$pvals", input: "$x", method: "approximate"}},
|
||||
letSpec: {pvals: [0.5, 0.9]},
|
||||
msg: "'p' should be able to use variables for the array"
|
||||
msg: "'p' should be able to use variables for the array",
|
||||
});
|
||||
|
||||
assertValidSyntax({
|
||||
pSpec: {$percentile: {p: ["$$p1", "$$p2"], input: "$x", method: "approximate"}},
|
||||
letSpec: {p1: 0.5, p2: 0.9},
|
||||
msg: "'p' should be able to use variables for the array elements"
|
||||
msg: "'p' should be able to use variables for the array elements",
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests for valid $median.
|
||||
*/
|
||||
assertValidSyntax(
|
||||
{pSpec: {$median: {input: "$x", method: "approximate"}}, msg: "Simple base case for $median."});
|
||||
assertValidSyntax({pSpec: {$median: {input: "$x", method: "approximate"}}, msg: "Simple base case for $median."});
|
||||
|
||||
@ -10,11 +10,17 @@ assert(coll.drop());
|
||||
// Test that $setUnion produces a single array containing all the unique values from the input
|
||||
// arrays. As $setUnion does not provide guarantees on ordering, the result is sorted for easy
|
||||
// comparison.
|
||||
assert.commandWorked(coll.insert([{_id: 0, nums: [1, 2, 3]}, {_id: 1, nums: [4, 5, 6]}]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, nums: [1, 2, 3]},
|
||||
{_id: 1, nums: [4, 5, 6]},
|
||||
]),
|
||||
);
|
||||
|
||||
let result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$nums'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
let result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$nums"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
@ -24,12 +30,18 @@ assert(coll.drop());
|
||||
|
||||
// Test that $setUnion deduplicates the values. That is, each unique value will only appear once in
|
||||
// the ouput of the accumulation.
|
||||
assert.commandWorked(coll.insert(
|
||||
[{_id: 0, nums: [1, 2, 3]}, {_id: 1, nums: [2, 3, 4]}, {_id: 2, nums: [3, 4, 5, 6]}]));
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, nums: [1, 2, 3]},
|
||||
{_id: 1, nums: [2, 3, 4]},
|
||||
{_id: 2, nums: [3, 4, 5, 6]},
|
||||
]),
|
||||
);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$nums'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$nums"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
@ -41,9 +53,10 @@ assert(coll.drop());
|
||||
// value will only appear once in the ouput of the accumulation.
|
||||
assert.commandWorked(coll.insert([{_id: 0, nums: [1, 1, 2, 2, 3, 3]}]));
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$nums'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$nums"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
@ -52,15 +65,18 @@ assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3]}]);
|
||||
assert(coll.drop());
|
||||
|
||||
// Nested arrays should still be arrays in the result of $setUnion.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, vals: [["nested"], 1, 2]},
|
||||
{_id: 1, vals: [3, 4, ["nested"]]},
|
||||
{_id: 2, vals: [4, ["nested", "extra"]]}
|
||||
]));
|
||||
{_id: 2, vals: [4, ["nested", "extra"]]},
|
||||
]),
|
||||
);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$vals'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$vals"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
@ -70,33 +86,47 @@ assert(coll.drop());
|
||||
|
||||
// $setUnion should deduplicate objects as well. Note that documents which differ in field order are
|
||||
// considered unique.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, vals: [{a: 1, b: 2}]},
|
||||
{_id: 1, vals: [{b: 2, a: 1}]},
|
||||
{_id: 2, vals: [{a: 1, b: 2}]},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
result = coll.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: '$vals'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$n', sortBy: 1}}}}
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: null, n: {$setUnion: "$vals"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$n", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [{a: 1, b: 2}, {b: 2, a: 1}]}]);
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
setUnionArr: [
|
||||
{a: 1, b: 2},
|
||||
{b: 2, a: 1},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
// $setUnion should 'skip over' documents that do not have the array field. Importantly, do not
|
||||
// insert null for documents that do not have the referenced field.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, author: "Nick", publisher: "Pub3", books: ["Smile :)"]},
|
||||
{_id: 2, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 3, author: "Matt", publisher: "Pub3", books: ["Happy!"]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{_id: 3, author: "Matt", publisher: "Pub3", books: ["Happy!"]},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, allBooks: {$setUnion: '$books'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$allBooks', sortBy: 1}}}}
|
||||
{$group: {_id: null, allBooks: {$setUnion: "$books"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$allBooks", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
@ -105,12 +135,18 @@ assert.eq(result, [{_id: null, setUnionArr: ["Happy!", "Smile :)"]}]);
|
||||
assert(coll.drop());
|
||||
|
||||
// $setUnion dotted field.
|
||||
assert.commandWorked(coll.insert(
|
||||
[{_id: 1, a: {b: [1, 2, 3]}}, {_id: 2, a: {b: [3, 4, 5, 6]}}, {_id: 3, a: {b: [7, 8, 9]}}]));
|
||||
result = coll.aggregate([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: {b: [1, 2, 3]}},
|
||||
{_id: 2, a: {b: [3, 4, 5, 6]}},
|
||||
{_id: 3, a: {b: [7, 8, 9]}},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$setUnion: '$a.b'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$nums', sortBy: 1}}}}
|
||||
{$group: {_id: null, nums: {$setUnion: "$a.b"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$nums", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
|
||||
@ -118,19 +154,32 @@ assert.eq(result, [{_id: null, setUnionArr: [1, 2, 3, 4, 5, 6, 7, 8, 9]}]);
|
||||
assert(coll.drop());
|
||||
|
||||
// $setUnion dotted field, array halfway on path.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, a: [{b: [1, 2, 3]}, {b: [4, 5, 6]}]},
|
||||
{_id: 2, a: [{b: [7, 8, 9]}, {b: [10, 11, 12]}]},
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]}
|
||||
]));
|
||||
result = coll.aggregate([
|
||||
{_id: 3, a: [{b: [7, 8, 9]}]},
|
||||
]),
|
||||
);
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$sort: {_id: 1}},
|
||||
{$group: {_id: null, nums: {$setUnion: '$a.b'}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$nums', sortBy: 1}}}}
|
||||
{$group: {_id: null, nums: {$setUnion: "$a.b"}}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$nums", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(result, [{_id: null, setUnionArr: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]}]);
|
||||
assert.eq(result, [
|
||||
{
|
||||
_id: null,
|
||||
setUnionArr: [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[10, 11, 12],
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
@ -140,8 +189,7 @@ const notArrays = [1, "string", {object: "object"}, null];
|
||||
for (const notAnArray of notArrays) {
|
||||
assert.commandWorked(coll.insert([{_id: "doesNotMatter", vals: notAnArray}]));
|
||||
|
||||
assertErrorCode(
|
||||
coll, [{$group: {_id: null, v: {$setUnion: '$vals'}}}], ErrorCodes.TypeMismatch);
|
||||
assertErrorCode(coll, [{$group: {_id: null, v: {$setUnion: "$vals"}}}], ErrorCodes.TypeMismatch);
|
||||
|
||||
assert.commandWorked(coll.deleteOne({_id: "doesNotMatter"}));
|
||||
}
|
||||
@ -149,24 +197,27 @@ for (const notAnArray of notArrays) {
|
||||
assert(coll.drop());
|
||||
|
||||
// Basic test of $setUnion with grouping.
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 1, author: "Kyra", publisher: "Pub1", books: ["Book 1"]},
|
||||
{_id: 2, author: "Nick", publisher: "Pub3", books: ["Book 2"]},
|
||||
{_id: 3, author: "Santiago", publisher: "Pub3"},
|
||||
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]}
|
||||
]));
|
||||
{_id: 4, author: "Matt", publisher: "Pub3", books: ["Book 3"]},
|
||||
]),
|
||||
);
|
||||
|
||||
result =
|
||||
coll.aggregate([
|
||||
{$group: {_id: '$publisher', booksByPublisher: {$setUnion: '$books'}}},
|
||||
result = coll
|
||||
.aggregate([
|
||||
{$group: {_id: "$publisher", booksByPublisher: {$setUnion: "$books"}}},
|
||||
{$sort: {_id: 1}},
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: '$booksByPublisher', sortBy: 1}}}}
|
||||
{$project: {_id: 1, setUnionArr: {$sortArray: {input: "$booksByPublisher", sortBy: 1}}}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
assert.eq(
|
||||
result,
|
||||
[{_id: "Pub1", setUnionArr: ["Book 1"]}, {_id: "Pub3", setUnionArr: ["Book 2", "Book 3"]}]);
|
||||
assert.eq(result, [
|
||||
{_id: "Pub1", setUnionArr: ["Book 1"]},
|
||||
{_id: "Pub3", setUnionArr: ["Book 2", "Book 3"]},
|
||||
]);
|
||||
|
||||
// Basic correctness tests for $setUnion used in $bucket and $bucketAuto. Though $bucket and
|
||||
// $bucketAuto use accumulators in the same way that $group does, the tests below verifies that
|
||||
@ -179,20 +230,23 @@ for (let i = 0; i < 10; i++) {
|
||||
coll.insertMany(docs);
|
||||
|
||||
// $bucket
|
||||
result =
|
||||
coll.aggregate([{
|
||||
$bucket: {groupBy: '$_id', boundaries: [0, 5, 10], output: {nums: {$setUnion: "$arr"}}}
|
||||
}])
|
||||
result = coll
|
||||
.aggregate([
|
||||
{
|
||||
$bucket: {groupBy: "$_id", boundaries: [0, 5, 10], output: {nums: {$setUnion: "$arr"}}},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(result, [{"_id": 0, "nums": [42]}, {"_id": 5, "nums": [42]}]);
|
||||
assert.eq(result, [
|
||||
{"_id": 0, "nums": [42]},
|
||||
{"_id": 5, "nums": [42]},
|
||||
]);
|
||||
|
||||
// $bucketAuto
|
||||
result =
|
||||
coll.aggregate(
|
||||
[{$bucketAuto: {groupBy: '$_id', buckets: 2, output: {nums: {$setUnion: "$arr"}}}}])
|
||||
.toArray();
|
||||
assert.eq(
|
||||
result,
|
||||
[{"_id": {"min": 0, "max": 5}, "nums": [42]}, {"_id": {"min": 5, "max": 9}, "nums": [42]}]);
|
||||
result = coll.aggregate([{$bucketAuto: {groupBy: "$_id", buckets: 2, output: {nums: {$setUnion: "$arr"}}}}]).toArray();
|
||||
assert.eq(result, [
|
||||
{"_id": {"min": 0, "max": 5}, "nums": [42]},
|
||||
{"_id": {"min": 5, "max": 9}, "nums": [42]},
|
||||
]);
|
||||
|
||||
assert(coll.drop());
|
||||
|
||||
@ -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 largestInt = NumberDecimal("9223372036854775807"); // This is max int64 which is supported as N.
|
||||
const largestIntPlus1 = NumberDecimal("9223372036854775808"); // Adding 1 puts it over the edge.
|
||||
|
||||
// Makes a string for a unique sales associate name that looks like 'Jim the 4 from CA'.
|
||||
const associateName = (i, state) => ["Jim", "Pam", "Dwight", "Phyllis"][i % 4] + " the " +
|
||||
parseInt(i / 4) + " from " + state;
|
||||
const associateName = (i, state) =>
|
||||
["Jim", "Pam", "Dwight", "Phyllis"][i % 4] + " the " + parseInt(i / 4) + " from " + state;
|
||||
|
||||
// Basic correctness tests.
|
||||
let docs = [];
|
||||
const defaultN = 4;
|
||||
const states = [{state: "CA", sales: 10}, {state: "NY", sales: 7}, {state: "TX", sales: 4}];
|
||||
const states = [
|
||||
{state: "CA", sales: 10},
|
||||
{state: "NY", sales: 7},
|
||||
{state: "TX", sales: 4},
|
||||
];
|
||||
let expectedBottomNAscResults = [];
|
||||
let expectedTopNAscResults = [];
|
||||
let expectedBottomNDescResults = [];
|
||||
@ -65,15 +68,15 @@ function buildTopNBottomNSpec(op, sortSpec, outputSpec, nValue) {
|
||||
* Helper that verifies that 'op' and 'sortSpec' produce 'expectedResults'.
|
||||
*/
|
||||
function assertExpected(op, sortSpec, expectedResults) {
|
||||
const actual =
|
||||
coll.aggregate([
|
||||
const actual = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$state",
|
||||
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)
|
||||
}
|
||||
associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN),
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(expectedResults, actual);
|
||||
@ -84,15 +87,14 @@ function assertExpected(op, sortSpec, expectedResults) {
|
||||
// allows us to compare the $bucketAuto results to the expected $group results (because there
|
||||
// are more buckets than groups, it will always be the case that the min value of each bucket
|
||||
// corresponds to the group key).
|
||||
let actualBucketAutoResults =
|
||||
coll.aggregate([
|
||||
let actualBucketAutoResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: '$state',
|
||||
groupBy: "$state",
|
||||
buckets: 10 * 1000,
|
||||
output:
|
||||
{associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)}
|
||||
}
|
||||
output: {associates: buildTopNBottomNSpec(op, sortSpec, "$associate", defaultN)},
|
||||
},
|
||||
},
|
||||
{$project: {_id: "$_id.min", associates: 1}},
|
||||
{$sort: {_id: 1}},
|
||||
@ -116,19 +118,18 @@ assertExpected("$bottomN", {sales: -1}, expectedBottomNDescResults);
|
||||
assertExpected("$topN", {sales: -1}, expectedTopNDescResults);
|
||||
|
||||
// Verify that we can compute multiple topN/bottomN groupings in the same $group.
|
||||
const combinedGroup =
|
||||
coll.aggregate([
|
||||
const combinedGroup = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$state",
|
||||
bottomAsc: buildTopNBottomNSpec("$bottomN", {sales: 1}, "$associate", defaultN),
|
||||
bottomDesc:
|
||||
buildTopNBottomNSpec("$bottomN", {sales: -1}, "$associate", defaultN),
|
||||
bottomDesc: buildTopNBottomNSpec("$bottomN", {sales: -1}, "$associate", defaultN),
|
||||
topAsc: buildTopNBottomNSpec("$topN", {sales: 1}, "$associate", defaultN),
|
||||
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN)
|
||||
}
|
||||
topDesc: buildTopNBottomNSpec("$topN", {sales: -1}, "$associate", defaultN),
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
@ -143,25 +144,24 @@ for (const doc of combinedGroup) {
|
||||
topDesc.push({_id: doc["_id"], associates: doc["topDesc"]});
|
||||
}
|
||||
|
||||
assert.eq([bottomAsc, bottomDesc, topAsc, topDesc], [
|
||||
expectedBottomNAscResults,
|
||||
expectedBottomNDescResults,
|
||||
expectedTopNAscResults,
|
||||
expectedTopNDescResults
|
||||
]);
|
||||
assert.eq(
|
||||
[bottomAsc, bottomDesc, topAsc, topDesc],
|
||||
[expectedBottomNAscResults, expectedBottomNDescResults, expectedTopNAscResults, expectedTopNDescResults],
|
||||
);
|
||||
|
||||
// Verify that we can dynamically compute 'n' based on the group key for $group.
|
||||
const groupKeyNExpr = {
|
||||
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4}
|
||||
$cond: {if: {$eq: ["$st", "CA"]}, then: 10, else: 4},
|
||||
};
|
||||
const dynamicBottomNResults =
|
||||
coll.aggregate([{
|
||||
const dynamicBottomNResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {"st": "$state"},
|
||||
bottomAssociates:
|
||||
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
|
||||
}
|
||||
}])
|
||||
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
// Verify that the 'CA' group has 10 results, while all others have only 4.
|
||||
@ -181,12 +181,13 @@ for (const result of dynamicBottomNResults) {
|
||||
|
||||
// When output evaluates to missing for the single version, it should be promoted to null like in
|
||||
// $first.
|
||||
const outputMissing = coll.aggregate({
|
||||
const outputMissing = coll
|
||||
.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
bottom: {$bottom: {output: "$b", sortBy: {sales: 1}}},
|
||||
top: {$top: {output: "$b", sortBy: {sales: 1}}}
|
||||
}
|
||||
top: {$top: {output: "$b", sortBy: {sales: 1}}},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(null, outputMissing[0]["top"]);
|
||||
@ -195,32 +196,39 @@ assert.eq(null, outputMissing[0]["bottom"]);
|
||||
// Error cases.
|
||||
|
||||
// Cannot reference the group key in $bottomN when using $bucketAuto.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$bucketAuto: {
|
||||
groupBy: "$state",
|
||||
buckets: 2,
|
||||
output: {
|
||||
bottomAssociates:
|
||||
{$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}}
|
||||
}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
bottomAssociates: {$bottomN: {output: "$associate", n: groupKeyNExpr, sortBy: {sales: 1}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
4544714);
|
||||
4544714,
|
||||
);
|
||||
|
||||
// Verify that 'n' cannot be greater than the largest signed 64 bit int.
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$group: {
|
||||
_id: {'st': '$state'},
|
||||
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
_id: {"st": "$state"},
|
||||
sales: {$topN: {output: "$associate", n: largestIntPlus1, sortBy: {sales: 1}}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
}),
|
||||
5787903);
|
||||
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,28 +255,31 @@ assert.commandWorked(coll.insert(games));
|
||||
|
||||
const gameSpec = {
|
||||
player: "$player",
|
||||
score: "$score"
|
||||
score: "$score",
|
||||
};
|
||||
for (const nVal of [defaultN, largestInt]) {
|
||||
const gameResults =
|
||||
coll.aggregate([{
|
||||
const gameResults = coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: "$game",
|
||||
bottomAsc: buildTopNBottomNSpec("$bottomN", {score: 1}, gameSpec, nVal),
|
||||
topAsc: buildTopNBottomNSpec("$topN", {score: 1}, gameSpec, nVal),
|
||||
topDesc: buildTopNBottomNSpec("$topN", {score: -1}, gameSpec, nVal),
|
||||
bottomDesc: buildTopNBottomNSpec("$bottomN", {score: -1}, gameSpec, nVal),
|
||||
}
|
||||
}])
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
|
||||
for (const doc of gameResults) {
|
||||
let assertResultsInOrder = function (index, fieldName, arr, isAsc) {
|
||||
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) {
|
||||
@ -277,10 +288,11 @@ for (const nVal of [defaultN, largestInt]) {
|
||||
// Verify that 'nVal' is greater or equal to the number of results returned.
|
||||
// Note that we upconvert to NumberDecimal to account for 'largestInt' being a
|
||||
// NumberDecimal.
|
||||
assert.gte(NumberDecimal(nVal),
|
||||
assert.gte(
|
||||
NumberDecimal(nVal),
|
||||
NumberDecimal(arr.length),
|
||||
nVal + " is not GTE array length of " + tojson(arr) + " for field " +
|
||||
fieldName);
|
||||
nVal + " is not GTE array length of " + tojson(arr) + " for field " + fieldName,
|
||||
);
|
||||
for (let i = 1; i < arr.length; ++i) {
|
||||
assertResultsInOrder(i, fieldName, arr, isAsc);
|
||||
}
|
||||
@ -294,12 +306,14 @@ for (const nVal of [defaultN, largestInt]) {
|
||||
|
||||
const rejectInvalidSpec = (op, assign, errCode, delProps = []) => {
|
||||
let spec = Object.assign({}, {output: "$associate", n: 2, sortBy: {sales: 1}}, assign);
|
||||
delProps.forEach(delProp => delete spec[delProp]);
|
||||
assert.commandFailedWithCode(coll.runCommand("aggregate", {
|
||||
delProps.forEach((delProp) => delete spec[delProp]);
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand("aggregate", {
|
||||
pipeline: [{$group: {_id: {"st": "$state"}, bottomAssociates: {[op]: spec}}}],
|
||||
cursor: {}
|
||||
cursor: {},
|
||||
}),
|
||||
errCode);
|
||||
errCode,
|
||||
);
|
||||
};
|
||||
|
||||
// Reject non-integral/negative values of n.
|
||||
@ -326,9 +340,8 @@ rejectInvalidSpec("$bottom", {}, 5788002);
|
||||
// Sort on embedded field.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: {b: i}}))));
|
||||
const embeddedResult =
|
||||
coll.aggregate(
|
||||
{$group: {_id: "", result: {$bottomN: {n: 3, output: "$a.b", sortBy: {"a.b": 1}}}}})
|
||||
const embeddedResult = coll
|
||||
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a.b", sortBy: {"a.b": 1}}}}})
|
||||
.toArray();
|
||||
assert.eq([2, 3, 4], embeddedResult[0].result);
|
||||
|
||||
@ -336,16 +349,16 @@ assert.eq([2, 3, 4], embeddedResult[0].result);
|
||||
assert(coll.drop());
|
||||
const makeArray = (i) => [i, i + 1, i + 2];
|
||||
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: makeArray(i)}))));
|
||||
const nestedResult =
|
||||
coll.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
const nestedResult = coll
|
||||
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
.toArray();
|
||||
assert.eq([makeArray(2), makeArray(3), makeArray(4)], nestedResult[0].result);
|
||||
|
||||
// Sort on doubly nested array.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany([4, 2, 3, 1].map((i) => ({a: [makeArray(i)]}))));
|
||||
const doublyNestedResult =
|
||||
coll.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
const doublyNestedResult = coll
|
||||
.aggregate({$group: {_id: "", result: {$bottomN: {n: 3, output: "$a", sortBy: {"a": 1}}}}})
|
||||
.toArray();
|
||||
assert.eq([[makeArray(2)], [makeArray(3)], [makeArray(4)]], doublyNestedResult[0].result);
|
||||
|
||||
@ -354,54 +367,55 @@ coll.drop();
|
||||
const as = [1, 2, 3];
|
||||
const bs = [1, 2, 3];
|
||||
const crossProduct = (arr1, arr2) =>
|
||||
arr1.map(a => arr2.map(b => ({a, b}))).reduce((docs, inner) => docs.concat(inner));
|
||||
arr1.map((a) => arr2.map((b) => ({a, b}))).reduce((docs, inner) => docs.concat(inner));
|
||||
const fullAscending = crossProduct(as, bs);
|
||||
const aAscendingBDescending = crossProduct(as, bs.reverse());
|
||||
|
||||
assert.commandWorked(coll.insertMany(fullAscending));
|
||||
const actualFullAscending =
|
||||
coll.aggregate({
|
||||
const actualFullAscending = coll
|
||||
.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}}
|
||||
}
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: 1}}},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(fullAscending, actualFullAscending[0]["sorted"]);
|
||||
|
||||
const actualAAscendingBDescending =
|
||||
coll.aggregate({
|
||||
const actualAAscendingBDescending = coll
|
||||
.aggregate({
|
||||
$group: {
|
||||
_id: "",
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}}
|
||||
}
|
||||
sorted: {$bottomN: {n: 9, output: {a: "$a", b: "$b"}, sortBy: {a: 1, b: -1}}},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
assert.eq(aAscendingBDescending, actualAAscendingBDescending[0]["sorted"]);
|
||||
|
||||
// $meta sort specification.
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany(
|
||||
["apples apples pears", "pears pears", "apples apples apples", "apples doughnuts"].map(
|
||||
text => ({text}))));
|
||||
assert.commandWorked(
|
||||
coll.insertMany(
|
||||
["apples apples pears", "pears pears", "apples apples apples", "apples doughnuts"].map((text) => ({text})),
|
||||
),
|
||||
);
|
||||
assert.commandWorked(coll.createIndex({text: "text"}));
|
||||
const sortStageResult =
|
||||
coll.aggregate(
|
||||
[{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
|
||||
const sortStageResult = coll
|
||||
.aggregate([{$match: {$text: {$search: "apples pears"}}}, {$sort: {text: {$meta: "textScore"}}}])
|
||||
.toArray()
|
||||
.map(doc => doc["text"]);
|
||||
.map((doc) => doc["text"]);
|
||||
const testOperatorText = (op) => {
|
||||
const opNResult =
|
||||
coll.aggregate([
|
||||
const opNResult = coll
|
||||
.aggregate([
|
||||
{$match: {$text: {$search: "apples pears"}}},
|
||||
{
|
||||
$group: {
|
||||
_id: "",
|
||||
result: {
|
||||
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
[op]: {n: 4, output: "$text", sortBy: {"a.text": {$meta: "textScore"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(opNResult.length, 1);
|
||||
@ -417,9 +431,8 @@ testOperatorText("$topN");
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insertMany([{a: 1}, {a: 2}, {a: 3}]));
|
||||
const testConstantOutputAndSort = (op) => {
|
||||
const results =
|
||||
coll.aggregate(
|
||||
[{$group: {_id: null, result: {[op]: {n: 3, output: "abc", sortBy: {a: 1}}}}}])
|
||||
const results = coll
|
||||
.aggregate([{$group: {_id: null, result: {[op]: {n: 3, output: "abc", sortBy: {a: 1}}}}}])
|
||||
.toArray();
|
||||
assert.eq(results.length, 1, results);
|
||||
assert.docEq(results[0], {_id: null, result: ["abc", "abc", "abc"]}, results);
|
||||
|
||||
@ -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 ','
|
||||
|
||||
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,12 +84,12 @@ 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 () {
|
||||
makeCursor(res).itcount();
|
||||
@ -100,7 +97,7 @@ if (res.ok) {
|
||||
}
|
||||
|
||||
// error if collection dropped after first batch
|
||||
cursor = aggCursor([{$unwind: '$bigArray'}], 0);
|
||||
cursor = aggCursor([{$unwind: "$bigArray"}], 0);
|
||||
t.drop();
|
||||
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({
|
||||
assert.commandFailed(
|
||||
db.runCommand({
|
||||
aggregate: t.getName(),
|
||||
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: '$a'}}}}]
|
||||
}));
|
||||
pipeline: [{$match: {}}, {$group: {_id: null, arr: {$push: {a: "$a"}}}}],
|
||||
}),
|
||||
);
|
||||
|
||||
// Make sure the server is still up.
|
||||
assert.commandWorked(db.runCommand('ping'));
|
||||
assert.commandWorked(db.runCommand("ping"));
|
||||
|
||||
@ -20,23 +20,24 @@ coll.drop();
|
||||
assert.commandWorked(coll.insertOne({date: new Timestamp(1341337661, 1)}));
|
||||
assert.commandWorked(coll.insertOne({date: new Date(1341337661000)}));
|
||||
// Aggregate checking various combinations of the constant and the field
|
||||
let agg_timestamp = coll.aggregate({
|
||||
let agg_timestamp = coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
dayOfMonth: {$dayOfMonth: '$date'},
|
||||
dayOfWeek: {$dayOfWeek: '$date'},
|
||||
dayOfYear: {$dayOfYear: '$date'},
|
||||
hour: {$hour: '$date'},
|
||||
minute: {$minute: '$date'},
|
||||
month: {$month: '$date'},
|
||||
second: {$second: '$date'},
|
||||
week: {$week: '$date'},
|
||||
year: {$year: '$date'}
|
||||
}
|
||||
dayOfMonth: {$dayOfMonth: "$date"},
|
||||
dayOfWeek: {$dayOfWeek: "$date"},
|
||||
dayOfYear: {$dayOfYear: "$date"},
|
||||
hour: {$hour: "$date"},
|
||||
minute: {$minute: "$date"},
|
||||
month: {$month: "$date"},
|
||||
second: {$second: "$date"},
|
||||
week: {$week: "$date"},
|
||||
year: {$year: "$date"},
|
||||
},
|
||||
})
|
||||
.toArray();
|
||||
// Assert the two entries are equal
|
||||
assert.eq(agg_timestamp[0], agg_timestamp[1], 'agg_timestamp failed');
|
||||
assert.eq(agg_timestamp[0], agg_timestamp[1], "agg_timestamp failed");
|
||||
|
||||
// Clear db for timestamp to date compare test
|
||||
// For historical reasons the compare the same if they are the same 64-bit representation.
|
||||
@ -49,31 +50,30 @@ agg_timestamp = coll.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
// comparison is different code path based on order (same as in bson)
|
||||
ts_date: {$eq: ['$time', '$date']},
|
||||
date_ts: {$eq: ['$date', '$time']}
|
||||
}
|
||||
ts_date: {$eq: ["$time", "$date"]},
|
||||
date_ts: {$eq: ["$date", "$time"]},
|
||||
},
|
||||
});
|
||||
assert.eq(agg_timestamp.toArray(),
|
||||
[{ts_date: false, date_ts: false}, {ts_date: false, date_ts: false}]);
|
||||
assert.eq(agg_timestamp.toArray(), [
|
||||
{ts_date: false, date_ts: false},
|
||||
{ts_date: false, date_ts: false},
|
||||
]);
|
||||
|
||||
// Clear db for timestamp comparison tests
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insertOne({time: new Timestamp(1341337661, 1), time2: new Timestamp(1341337661, 2)}));
|
||||
assert.commandWorked(coll.insertOne({time: new Timestamp(1341337661, 1), time2: new Timestamp(1341337661, 2)}));
|
||||
agg_timestamp = coll.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
cmp: {$cmp: ['$time', '$time2']},
|
||||
eq: {$eq: ['$time', '$time2']},
|
||||
gt: {$gt: ['$time', '$time2']},
|
||||
gte: {$gte: ['$time', '$time2']},
|
||||
lt: {$lt: ['$time', '$time2']},
|
||||
lte: {$lte: ['$time', '$time2']},
|
||||
ne: {$ne: ['$time', '$time2']}
|
||||
}
|
||||
cmp: {$cmp: ["$time", "$time2"]},
|
||||
eq: {$eq: ["$time", "$time2"]},
|
||||
gt: {$gt: ["$time", "$time2"]},
|
||||
gte: {$gte: ["$time", "$time2"]},
|
||||
lt: {$lt: ["$time", "$time2"]},
|
||||
lte: {$lte: ["$time", "$time2"]},
|
||||
ne: {$ne: ["$time", "$time2"]},
|
||||
},
|
||||
});
|
||||
var agg_timestampresult =
|
||||
[{cmp: -1, eq: false, gt: false, gte: false, lt: true, lte: true, ne: true}];
|
||||
var agg_timestampresult = [{cmp: -1, eq: false, gt: false, gte: false, lt: true, lte: true, ne: true}];
|
||||
// Assert the results are as expected
|
||||
assert.eq(
|
||||
agg_timestamp.toArray(), agg_timestampresult, 'agg_timestamp failed comparing two timestamps');
|
||||
assert.eq(agg_timestamp.toArray(), agg_timestampresult, "agg_timestamp failed comparing two timestamps");
|
||||
|
||||
@ -20,14 +20,16 @@ otherColl.drop();
|
||||
assert.commandWorked(otherColl.insert({_id: "id0", x: 1}));
|
||||
assert.commandWorked(otherColl.insert({_id: "id1", x: 2}));
|
||||
|
||||
assert.commandWorked(coll.insert({
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 0,
|
||||
link: new DBRef(otherColl.getName(), "id0", db.getName()),
|
||||
linkArray: [
|
||||
new DBRef(otherColl.getName(), "id0", db.getName()),
|
||||
new DBRef(otherColl.getName(), "id1", db.getName())
|
||||
]
|
||||
}));
|
||||
new DBRef(otherColl.getName(), "id1", db.getName()),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
function projectOnlyPipeline(projection) {
|
||||
const aggRes = coll.aggregate({$project: projection}).toArray();
|
||||
@ -38,34 +40,36 @@ function projectOnlyPipeline(projection) {
|
||||
|
||||
// Refer to a DBRef sub-field in a projection.
|
||||
assert.eq(projectOnlyPipeline({refVal: "$link.$ref"}), [{_id: 0, refVal: otherColl.getName()}]);
|
||||
assert.eq(projectOnlyPipeline({refVal: "$linkArray.$ref"}),
|
||||
[{_id: 0, refVal: [otherColl.getName(), otherColl.getName()]}]);
|
||||
assert.eq(projectOnlyPipeline({refVal: "$linkArray.$ref"}), [
|
||||
{_id: 0, refVal: [otherColl.getName(), otherColl.getName()]},
|
||||
]);
|
||||
|
||||
assert.eq(projectOnlyPipeline({idVal: "$link.$id"}), [{_id: 0, idVal: "id0"}]);
|
||||
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$id"}), [{_id: 0, idVal: ["id0", "id1"]}]);
|
||||
|
||||
assert.eq(projectOnlyPipeline({idVal: "$link.$db"}), [{_id: 0, idVal: db.getName()}]);
|
||||
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$db"}),
|
||||
[{_id: 0, idVal: [db.getName(), db.getName()]}]);
|
||||
assert.eq(projectOnlyPipeline({idVal: "$linkArray.$db"}), [{_id: 0, idVal: [db.getName(), db.getName()]}]);
|
||||
|
||||
// Use a DBRef sub-field in an expression.
|
||||
assert.eq(projectOnlyPipeline({idLen: {$strLenCP: "$link.$id"}}), [{_id: 0, idLen: "id0".length}]);
|
||||
|
||||
// Project away DBRef values.
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 0}, linkArray: 0}),
|
||||
[{_id: 0, link: {$id: "id0", $db: db.getName()}}]);
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 0}, linkArray: 0}), [{_id: 0, link: {$id: "id0", $db: db.getName()}}]);
|
||||
|
||||
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [{
|
||||
assert.eq(projectOnlyPipeline({link: 0, linkArray: {$id: 0}}), [
|
||||
{
|
||||
_id: 0,
|
||||
linkArray: [
|
||||
{$ref: otherColl.getName(), $db: db.getName()},
|
||||
{$ref: otherColl.getName(), $db: db.getName()}
|
||||
]
|
||||
}]);
|
||||
{$ref: otherColl.getName(), $db: db.getName()},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// Assigning to a DBRef field.
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 1, $id: 1, $db: "someOtherDB"}}),
|
||||
[{_id: 0, link: new DBRef(otherColl.getName(), "id0", "someOtherDB")}]);
|
||||
assert.eq(projectOnlyPipeline({link: {$ref: 1, $id: 1, $db: "someOtherDB"}}), [
|
||||
{_id: 0, link: new DBRef(otherColl.getName(), "id0", "someOtherDB")},
|
||||
]);
|
||||
|
||||
// While not a 'feature' we advertise, it is allowed to assign to top-level DBRef fields.
|
||||
assert.eq(projectOnlyPipeline({$ref: "$link.$ref"}), [{_id: 0, $ref: otherColl.getName()}]);
|
||||
@ -75,46 +79,61 @@ assert.eq(projectOnlyPipeline({$ref: "$link.$ref"}), [{_id: 0, $ref: otherColl.g
|
||||
assert.throwsWithCode(() => coll.aggregate({$project: {x: "$$ref"}}).toArray(), 17276);
|
||||
|
||||
// It can be accessed through $$ROOT, however.
|
||||
assert.eq(coll.aggregate([
|
||||
assert.eq(
|
||||
coll
|
||||
.aggregate([
|
||||
// Rather than go through the trouble of inserting a document with a top-level
|
||||
// $-prefixed field, create one in an intermediate $project stage.
|
||||
{$project: {"$ref": "hello world"}},
|
||||
// Make sure that no optimization coalesces the above projection stage with the
|
||||
// below one.
|
||||
{$_internalInhibitOptimization: {}},
|
||||
{$project: {x: "$$ROOT.$ref"}}
|
||||
{$project: {x: "$$ROOT.$ref"}},
|
||||
])
|
||||
.toArray(),
|
||||
[{_id: 0, x: "hello world"}]);
|
||||
[{_id: 0, x: "hello world"}],
|
||||
);
|
||||
|
||||
// Do a count (using $group) on a DBRef field.
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: 1}}}).toArray(),
|
||||
[{_id: db.getName(), count: 1}]);
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: 1}}}).toArray(), [{_id: db.getName(), count: 1}]);
|
||||
|
||||
// Refer to a DBRef field in an accumulator.
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: {$size: "$linkArray.$ref"}}}})
|
||||
.toArray(),
|
||||
[{_id: db.getName(), count: 2}]);
|
||||
assert.eq(coll.aggregate({$group: {_id: "$link.$db", count: {$sum: {$size: "$linkArray.$ref"}}}}).toArray(), [
|
||||
{_id: db.getName(), count: 2},
|
||||
]);
|
||||
|
||||
// Use $lookup with a DBRef.
|
||||
|
||||
// Equality match version.
|
||||
const lookupEqualityPipeline = [{$lookup: {from: otherColl.getName(),
|
||||
localField: "link.$id",
|
||||
foreignField: "_id",
|
||||
as: "joinedField"}},
|
||||
{$project: {link: 0, linkArray: 0}}];
|
||||
assert.eq(coll.aggregate(lookupEqualityPipeline).toArray(),
|
||||
[{_id: 0, joinedField: [{_id: "id0", x: 1}]}]);
|
||||
const lookupEqualityPipeline = [
|
||||
{$lookup: {from: otherColl.getName(), localField: "link.$id", foreignField: "_id", as: "joinedField"}},
|
||||
{$project: {link: 0, linkArray: 0}},
|
||||
];
|
||||
assert.eq(coll.aggregate(lookupEqualityPipeline).toArray(), [{_id: 0, joinedField: [{_id: "id0", x: 1}]}]);
|
||||
|
||||
// Foreign pipeline.
|
||||
const lookupSubPipePipeline = [{$lookup: {from: otherColl.getName(),
|
||||
const lookupSubPipePipeline = [
|
||||
{
|
||||
$lookup: {
|
||||
from: otherColl.getName(),
|
||||
let: {idsWanted: "$linkArray.$id"},
|
||||
pipeline: [{$match: {$expr: {$in: ["$_id", "$$idsWanted"]}}}],
|
||||
as: "joinedField"}},
|
||||
{$project: {link: 0, linkArray: 0}}];
|
||||
assert(anyEq(coll.aggregate(lookupSubPipePipeline).toArray(),
|
||||
[{_id: 0, joinedField: [{_id: "id0", x: 1}, {_id: "id1", x: 2}]}]));
|
||||
as: "joinedField",
|
||||
},
|
||||
},
|
||||
{$project: {link: 0, linkArray: 0}},
|
||||
];
|
||||
assert(
|
||||
anyEq(coll.aggregate(lookupSubPipePipeline).toArray(), [
|
||||
{
|
||||
_id: 0,
|
||||
joinedField: [
|
||||
{_id: "id0", x: 1},
|
||||
{_id: "id1", x: 2},
|
||||
],
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
(function testGraphLookup() {
|
||||
// $graphLookup using DBRef.
|
||||
@ -122,28 +141,34 @@ assert(anyEq(coll.aggregate(lookupSubPipePipeline).toArray(),
|
||||
graphLookupColl.drop();
|
||||
|
||||
// id0 -> id1 -> id2 -> id0
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id0", link: new DBRef(graphLookupColl.getName(), "id1", db.getName())}));
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id1", link: new DBRef(graphLookupColl.getName(), "id2", db.getName())}));
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id2", link: new DBRef(graphLookupColl.getName(), "id0", db.getName())}));
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id0", link: new DBRef(graphLookupColl.getName(), "id1", db.getName())}),
|
||||
);
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id1", link: new DBRef(graphLookupColl.getName(), "id2", db.getName())}),
|
||||
);
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id2", link: new DBRef(graphLookupColl.getName(), "id0", db.getName())}),
|
||||
);
|
||||
|
||||
// id3 -> id4
|
||||
assert.commandWorked(graphLookupColl.insert(
|
||||
{_id: "id3", link: new DBRef(graphLookupColl.getName(), "id4", db.getName())}));
|
||||
assert.commandWorked(
|
||||
graphLookupColl.insert({_id: "id3", link: new DBRef(graphLookupColl.getName(), "id4", db.getName())}),
|
||||
);
|
||||
assert.commandWorked(graphLookupColl.insert({_id: "id4", link: null}));
|
||||
|
||||
const graphLookupPipeline = [{
|
||||
const graphLookupPipeline = [
|
||||
{
|
||||
$graphLookup: {
|
||||
from: graphLookupColl.getName(),
|
||||
startWith: "$link.$id",
|
||||
connectFromField: "link.$id",
|
||||
connectToField: "_id",
|
||||
as: "connectedDocuments"
|
||||
}
|
||||
as: "connectedDocuments",
|
||||
},
|
||||
{$sort: {_id: 1}}];
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
];
|
||||
|
||||
const res = graphLookupColl.aggregate(graphLookupPipeline).toArray();
|
||||
// id0, id1, and id2 are all connected.
|
||||
@ -175,7 +200,7 @@ assert.commandWorked(coll.createIndex({"link.$ref": 1}, {unique: true}));
|
||||
thirdColl
|
||||
.aggregate([
|
||||
{$project: {"link.$ref": otherColl.getName(), "link.$id": "id0", sentinel: "foo"}},
|
||||
{$merge: {into: coll.getName(), on: "link.$ref", whenMatched: "replace"}}
|
||||
{$merge: {into: coll.getName(), on: "link.$ref", whenMatched: "replace"}},
|
||||
])
|
||||
.itcount();
|
||||
|
||||
@ -190,13 +215,14 @@ thirdColl
|
||||
$merge: {
|
||||
into: coll.getName(),
|
||||
on: "link.$ref",
|
||||
whenMatched: [{
|
||||
$project:
|
||||
{"link.$ref": "otherRef", "link.$id": "otherId", "link.$db": "otherDB"}
|
||||
}],
|
||||
whenNotMatched: "discard"
|
||||
}
|
||||
}
|
||||
whenMatched: [
|
||||
{
|
||||
$project: {"link.$ref": "otherRef", "link.$id": "otherId", "link.$db": "otherDB"},
|
||||
},
|
||||
],
|
||||
whenNotMatched: "discard",
|
||||
},
|
||||
},
|
||||
])
|
||||
.itcount();
|
||||
assert.eq(coll.find().toArray()[0], {_id: 0, link: new DBRef("otherRef", "otherId", "otherDB")});
|
||||
@ -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());
|
||||
@ -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}}}]);
|
||||
}
|
||||
|
||||
@ -72,14 +72,16 @@ function testLargeBucket() {
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
boundaries.push(i);
|
||||
}
|
||||
runAgg([{
|
||||
runAgg([
|
||||
{
|
||||
$bucket: {
|
||||
groupBy: "$a",
|
||||
boundaries: boundaries,
|
||||
default: "default",
|
||||
output: {"count": {$sum: 1}}
|
||||
}
|
||||
}]);
|
||||
output: {"count": {$sum: 1}},
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
// Construct a {$project: {a0: 1, a1: 1, ...}}.
|
||||
@ -111,7 +113,7 @@ function testLargeAndOrPredicates() {
|
||||
runAgg([{$match: largeMatch}]);
|
||||
|
||||
function intStream(n) {
|
||||
return range(n).map(i => NumberInt(i));
|
||||
return range(n).map((i) => NumberInt(i));
|
||||
}
|
||||
|
||||
const andOrFilters = [
|
||||
@ -149,7 +151,7 @@ function testLargeAndOrPredicates() {
|
||||
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;
|
||||
@ -66,24 +66,21 @@ assert.gt(coll.stats().size, memoryLimitMB * 1024 * 1024);
|
||||
function test({pipeline, expectedCodes, canSpillToDisk}) {
|
||||
// Test that 'allowDiskUse: false' does indeed prevent spilling to disk.
|
||||
assert.commandFailedWithCode(
|
||||
db.runCommand(
|
||||
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
|
||||
expectedCodes);
|
||||
db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: false}),
|
||||
expectedCodes,
|
||||
);
|
||||
|
||||
// If this command supports spilling to disk, ensure that it will succeed when disk use is
|
||||
// allowed.
|
||||
const res = db.runCommand(
|
||||
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true});
|
||||
const res = db.runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true});
|
||||
if (canSpillToDisk) {
|
||||
assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(),
|
||||
coll.count()); // all tests output one doc per input doc
|
||||
assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(), coll.count()); // all tests output one doc per input doc
|
||||
|
||||
if (isSbeGroupLookupPushdownEnabled) {
|
||||
const explain = db.runCommand({
|
||||
explain:
|
||||
{aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true}
|
||||
explain: {aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true},
|
||||
});
|
||||
const hashAggGroups = getSbePlanStages(explain, 'group');
|
||||
const hashAggGroups = getSbePlanStages(explain, "group");
|
||||
if (hashAggGroups.length > 0) {
|
||||
assert.eq(hashAggGroups.length, 1, explain);
|
||||
const hashAggGroup = hashAggGroups[0];
|
||||
@ -101,7 +98,7 @@ function setHashAggParameters(memoryLimit, atLeast) {
|
||||
cmdObj: {
|
||||
setParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill: memoryLimit,
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.gt(memLimitCommandResArr.length, 0, "Setting memory limit on primaries failed.");
|
||||
const oldMemoryLimit = assert.commandWorked(memLimitCommandResArr[0]).was;
|
||||
@ -111,7 +108,7 @@ function setHashAggParameters(memoryLimit, atLeast) {
|
||||
cmdObj: {
|
||||
setParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashAggMemoryCheckPerAdvanceAtLeast: atLeast,
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.gt(atLeastCommandResArr.length, 0, "Setting atLeast limit on primaries failed.");
|
||||
const oldAtLeast = assert.commandWorked(atLeastCommandResArr[0]).was;
|
||||
@ -129,10 +126,10 @@ function testWithHashAggMemoryLimit({pipeline, expectedCodes, canSpillToDisk, me
|
||||
}
|
||||
|
||||
testWithHashAggMemoryLimit({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true,
|
||||
memoryLimit: 1024
|
||||
memoryLimit: 1024,
|
||||
});
|
||||
|
||||
// Sorting with _id would use index which doesn't require external sort, so sort by 'random'
|
||||
@ -140,73 +137,70 @@ testWithHashAggMemoryLimit({
|
||||
test({
|
||||
pipeline: [{$sort: {random: 1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$sort: {bigStr: 1}}], // big key and value
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
// Test that sort + large limit won't crash the server (SERVER-10136)
|
||||
test({
|
||||
pipeline: [{$sort: {bigStr: 1}}, {$limit: 1000 * 1000 * 1000}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
// Test combining two external sorts in both same and different orders.
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {_id: 1}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {_id: 1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {_id: -1}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {_id: -1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigStr: {$min: '$bigStr'}}}, {$sort: {random: 1}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigStr: {$min: "$bigStr"}}}, {$sort: {random: 1}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline: [{$sort: {random: 1}}, {$group: {_id: '$_id', bigStr: {$first: '$bigStr'}}}],
|
||||
pipeline: [{$sort: {random: 1}}, {$group: {_id: "$_id", bigStr: {$first: "$bigStr"}}}],
|
||||
expectedCodes: ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed,
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
|
||||
// Test accumulating all values into one array. On debug builds we will spill to disk for $group and
|
||||
// so may hit the group error code before we hit ExceededMemoryLimit.
|
||||
test({
|
||||
pipeline: [{$group: {_id: null, bigArray: {$push: '$bigStr'}}}],
|
||||
expectedCodes:
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false
|
||||
pipeline: [{$group: {_id: null, bigArray: {$push: "$bigStr"}}}],
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false,
|
||||
});
|
||||
|
||||
test({
|
||||
pipeline:
|
||||
[{$group: {_id: null, bigArray: {$addToSet: {$concat: ['$bigStr', {$toString: "$_id"}]}}}}],
|
||||
expectedCodes:
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false
|
||||
pipeline: [{$group: {_id: null, bigArray: {$addToSet: {$concat: ["$bigStr", {$toString: "$_id"}]}}}}],
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false,
|
||||
});
|
||||
|
||||
for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) {
|
||||
for (const op of ["$firstN", "$lastN", "$minN", "$maxN", "$topN", "$bottomN"]) {
|
||||
jsTestLog("Testing op " + op);
|
||||
let spec = {n: 100000000};
|
||||
if (op === '$topN' || op === '$bottomN') {
|
||||
spec['sortBy'] = {random: 1};
|
||||
spec['output'] = '$bigStr';
|
||||
if (op === "$topN" || op === "$bottomN") {
|
||||
spec["sortBy"] = {random: 1};
|
||||
spec["output"] = "$bigStr";
|
||||
} else {
|
||||
// $firstN/$lastN/$minN/$maxN accept 'input'.
|
||||
spec['input'] = '$bigStr';
|
||||
spec["input"] = "$bigStr";
|
||||
}
|
||||
|
||||
// By grouping all of the entries in the same group, it is the case that we will either
|
||||
@ -216,18 +210,17 @@ for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) {
|
||||
// reduce the memory consumption of our group in this case.
|
||||
test({
|
||||
pipeline: [{$group: {_id: null, bigArray: {[op]: spec}}}],
|
||||
expectedCodes:
|
||||
[ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed, ErrorCodes.ExceededMemoryLimit],
|
||||
canSpillToDisk: false,
|
||||
});
|
||||
|
||||
// Because each group uses less than the configured limit, but cumulatively they exceed
|
||||
// the limit for $group, we only check for 'QueryExceededMemoryLimitNoDiskUseAllowed'
|
||||
// when disk use is disabled.
|
||||
test({
|
||||
pipeline: [{$group: {_id: '$_id', bigArray: {[op]: spec}}}],
|
||||
pipeline: [{$group: {_id: "$_id", bigArray: {[op]: spec}}}],
|
||||
expectedCodes: [ErrorCodes.QueryExceededMemoryLimitNoDiskUseAllowed],
|
||||
canSpillToDisk: true
|
||||
canSpillToDisk: true,
|
||||
});
|
||||
}
|
||||
// don't leave large collection laying around
|
||||
@ -245,25 +238,23 @@ for (let i = 0; i < numGroups; ++i) {
|
||||
_id: counter++,
|
||||
a: i,
|
||||
b: 100 * i + j,
|
||||
c: 100 * i + j % 5,
|
||||
c: 100 * i + (j % 5),
|
||||
obj: {a: i, b: j},
|
||||
random: Math.random()
|
||||
random: Math.random(),
|
||||
};
|
||||
assert.commandWorked(coll.insert(doc));
|
||||
}
|
||||
}
|
||||
|
||||
function setHashGroupMemoryParameters(memoryLimit) {
|
||||
return setMemoryParamHelper(
|
||||
"internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", memoryLimit);
|
||||
return setMemoryParamHelper("internalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill", memoryLimit);
|
||||
}
|
||||
|
||||
// Runs a group query containing the given 'accumulator' after sorting the data by the given
|
||||
// 'sortInputBy' field. Then verifies that the query results are equal to 'expectedOutput'. If SBE
|
||||
// is enabled, also runs explain and checks that the execution stats show that spilling occurred.
|
||||
function testAccumulator({accumulator, sortInputBy, expectedOutput, ignoreArrayOrder = false}) {
|
||||
const pipeline =
|
||||
[{$sort: {[sortInputBy]: 1}}, {$group: {_id: "$a", acc: accumulator}}, {$sort: {_id: 1}}];
|
||||
const pipeline = [{$sort: {[sortInputBy]: 1}}, {$group: {_id: "$a", acc: accumulator}}, {$sort: {_id: 1}}];
|
||||
const results = coll.aggregate(pipeline).toArray();
|
||||
|
||||
if (ignoreArrayOrder) {
|
||||
@ -289,9 +280,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 100},
|
||||
{_id: 2, acc: 200},
|
||||
{_id: 3, acc: 300},
|
||||
{_id: 4, acc: 400}
|
||||
]
|
||||
|
||||
{_id: 4, acc: 400},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -302,8 +292,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 109},
|
||||
{_id: 2, acc: 209},
|
||||
{_id: 3, acc: 309},
|
||||
{_id: 4, acc: 409}
|
||||
]
|
||||
{_id: 4, acc: 409},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -314,8 +304,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 100},
|
||||
{_id: 2, acc: 200},
|
||||
{_id: 3, acc: 300},
|
||||
{_id: 4, acc: 400}
|
||||
]
|
||||
{_id: 4, acc: 400},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -326,8 +316,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 109},
|
||||
{_id: 2, acc: 209},
|
||||
{_id: 3, acc: 309},
|
||||
{_id: 4, acc: 409}
|
||||
]
|
||||
{_id: 4, acc: 409},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -338,8 +328,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 1045},
|
||||
{_id: 2, acc: 2045},
|
||||
{_id: 3, acc: 3045},
|
||||
{_id: 4, acc: 4045}
|
||||
]
|
||||
{_id: 4, acc: 4045},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -350,8 +340,8 @@ function testSpillingForVariousAccumulators() {
|
||||
{_id: 1, acc: 104.5},
|
||||
{_id: 2, acc: 204.5},
|
||||
{_id: 3, acc: 304.5},
|
||||
{_id: 4, acc: 404.5}
|
||||
]
|
||||
{_id: 4, acc: 404.5},
|
||||
],
|
||||
});
|
||||
|
||||
testAccumulator({
|
||||
@ -388,7 +378,7 @@ 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}},
|
||||
],
|
||||
});
|
||||
}
|
||||
@ -418,7 +408,9 @@ function setupCollections(localRecords, foreignRecords, foreignField) {
|
||||
|
||||
function setHashLookupParameters(memoryLimit) {
|
||||
return setMemoryParamHelper(
|
||||
"internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill", memoryLimit);
|
||||
"internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill",
|
||||
memoryLimit,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -433,23 +425,26 @@ function runTest_MultipleLocalForeignRecords({
|
||||
foreignRecords,
|
||||
foreignField,
|
||||
idsExpectedToMatch,
|
||||
spillsToDisk
|
||||
spillsToDisk,
|
||||
}) {
|
||||
setupCollections(localRecords, foreignRecords, foreignField);
|
||||
const pipeline = [{
|
||||
const pipeline = [
|
||||
{
|
||||
$lookup: {
|
||||
from: foreignColl.getName(),
|
||||
localField: localField,
|
||||
foreignField: foreignField,
|
||||
as: "matched"
|
||||
}}];
|
||||
as: "matched",
|
||||
},
|
||||
},
|
||||
];
|
||||
const results = localColl.aggregate(pipeline, {allowDiskUse: true}).toArray();
|
||||
const explain = localColl.explain('executionStats').aggregate(pipeline, {allowDiskUse: true});
|
||||
const explain = localColl.explain("executionStats").aggregate(pipeline, {allowDiskUse: true});
|
||||
const pipelineWasSplit = !!explain.splitPipeline;
|
||||
|
||||
// If sharding is enabled, or the pipeline was split, then '$lookup' is not pushed down to SBE.
|
||||
if (isSbeGroupLookupPushdownEnabled && !sharded && !pipelineWasSplit) {
|
||||
const hLookups = getSbePlanStages(explain, 'hash_lookup');
|
||||
const hLookups = getSbePlanStages(explain, "hash_lookup");
|
||||
assert.eq(hLookups.length, 1, explain);
|
||||
const hLookup = hLookups[0];
|
||||
assert(hLookup, explain);
|
||||
@ -465,12 +460,12 @@ function runTest_MultipleLocalForeignRecords({
|
||||
|
||||
// Extract matched foreign ids from the "matched" field.
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const matchedIds = results[i].matched.map(x => (x._id));
|
||||
const matchedIds = results[i].matched.map((x) => x._id);
|
||||
// Order of the elements within the arrays is not significant for 'assertArrayEq'.
|
||||
assertArrayEq({
|
||||
actual: matchedIds,
|
||||
expected: idsExpectedToMatch[i],
|
||||
extraErrorMsg: " **TEST** " + testDescription + " " + tojson(explain)
|
||||
extraErrorMsg: " **TEST** " + testDescription + " " + tojson(explain),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -494,7 +489,7 @@ function runHashLookupSpill({memoryLimit, spillsToDisk}) {
|
||||
foreignRecords: docs,
|
||||
foreignField: "a",
|
||||
idsExpectedToMatch: [[1, 2, 4]],
|
||||
spillsToDisk: spillsToDisk
|
||||
spillsToDisk: spillsToDisk,
|
||||
});
|
||||
})();
|
||||
|
||||
@ -526,20 +521,19 @@ function runHashLookupSpill({memoryLimit, spillsToDisk}) {
|
||||
foreignRecords: foreignDocs,
|
||||
foreignField: "b",
|
||||
idsExpectedToMatch: [[], [8], [9, 10], [11, 12, 13], [11, 12, 13], []],
|
||||
spillsToDisk: spillsToDisk
|
||||
spillsToDisk: spillsToDisk,
|
||||
});
|
||||
})();
|
||||
|
||||
return oldSettings;
|
||||
}
|
||||
|
||||
const oldMemSettings =
|
||||
assert
|
||||
.commandWorked(db.adminCommand({
|
||||
const oldMemSettings = assert.commandWorked(
|
||||
db.adminCommand({
|
||||
getParameter: 1,
|
||||
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1
|
||||
}))
|
||||
.internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
|
||||
internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1,
|
||||
}),
|
||||
).internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill;
|
||||
|
||||
(function runAllDiskTest() {
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
||||
@ -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,8 +138,7 @@ 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() {
|
||||
|
||||
@ -21,10 +21,12 @@ assert.commandWorked(sourceColl.insert({_id: 1}));
|
||||
// Verifies that running the execution explains do not error, perform any writes, or create the
|
||||
// target collection.
|
||||
function assertExecutionExplainOk(writingStage, verbosity) {
|
||||
assert.commandWorked(db.runCommand({
|
||||
assert.commandWorked(
|
||||
db.runCommand({
|
||||
explain: {aggregate: sourceColl.getName(), pipeline: [writingStage], cursor: {}},
|
||||
verbosity: verbosity
|
||||
}));
|
||||
verbosity: verbosity,
|
||||
}),
|
||||
);
|
||||
assert.eq(targetColl.find().itcount(), 0);
|
||||
// Verify that the collection was not created.
|
||||
const collectionList = db.getCollectionInfos({name: targetColl.getName()});
|
||||
@ -34,8 +36,7 @@ function assertExecutionExplainOk(writingStage, verbosity) {
|
||||
// Test that $out can be explained with 'queryPlanner' explain verbosity and does not perform
|
||||
// any writes.
|
||||
let explain = sourceColl.explain("queryPlanner").aggregate([{$out: targetColl.getName()}]);
|
||||
let explainedPipeline =
|
||||
getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
|
||||
let explainedPipeline = getExplainPipelineFromAggregationResult(explain, {inhibitOptimization: false});
|
||||
assert.eq(1, explainedPipeline.length);
|
||||
assert(explainedPipeline[0].$out);
|
||||
let outExplain = explainedPipeline[0];
|
||||
@ -55,8 +56,8 @@ withEachMergeMode(function({whenMatchedMode, whenNotMatchedMode}) {
|
||||
$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([{
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{
|
||||
_id: 0,
|
||||
veryBigPositiveLong: NumberLong("9223372036854775806"),
|
||||
veryBigPositiveDouble: 9223372036854775806,
|
||||
veryBigPositiveDecimal: NumberDecimal("9223372036854775806")
|
||||
}]));
|
||||
veryBigPositiveDecimal: NumberDecimal("9223372036854775806"),
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
let pipeline = [{$project: {res: {$add: [new Date(10), "$veryBigPositiveLong"]}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow");
|
||||
|
||||
@ -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,7 +9,8 @@ function getResultOfExpression(expr) {
|
||||
return resultArray[0].computed;
|
||||
}
|
||||
|
||||
assert.commandWorked(coll.insert({
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 0,
|
||||
decimalVal: NumberDecimal("819.5359123621083"),
|
||||
doubleVal: 819.536,
|
||||
@ -21,110 +22,115 @@ assert.commandWorked(coll.insert({
|
||||
overflowInt64: NumberLong("9223372036854775807"),
|
||||
nanDouble: NaN,
|
||||
nanDecimal: NumberDecimal("NaN"),
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
// Adding a Decimal128 value to a date literal.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", ISODate("2019-01-30T07:30:10.137Z")]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [ISODate("2019-01-30T07:30:10.137Z"), "$decimalVal"]}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", ISODate("2019-01-30T07:30:10.137Z")]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [ISODate("2019-01-30T07:30:10.137Z"), "$decimalVal"]}),
|
||||
);
|
||||
|
||||
// Adding a Decimal128 literal to a date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", NumberDecimal("819.5359123621083")]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [NumberDecimal("819.5359123621083"), "$dateVal"]}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", NumberDecimal("819.5359123621083")]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: [NumberDecimal("819.5359123621083"), "$dateVal"]}),
|
||||
);
|
||||
|
||||
// Adding a Decimal128 value to a date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$decimalVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$decimalVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$decimalVal", "$dateVal"]}));
|
||||
|
||||
// Adding a double value to a date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$doubleVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$doubleVal", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$doubleVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$doubleVal", "$dateVal"]}));
|
||||
|
||||
// Adding an int64_t value to date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$int64Val", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int64Val", "$dateVal"]}));
|
||||
|
||||
// Adding an int32_t value to date value.
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$int32Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"),
|
||||
getResultOfExpression({$add: ["$int32Val", "$dateVal"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$dateVal", "$int32Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int32Val", "$dateVal"]}));
|
||||
|
||||
// Addition with a date and multiple values of differing data types.
|
||||
assert.eq(ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]}));
|
||||
assert.eq(ISODate("2019-01-30T07:30:12.596Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$doubleVal", "$int64Val", "$dateVal"]}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:12.597Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]}),
|
||||
);
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:12.596Z"),
|
||||
getResultOfExpression({$add: ["$decimalVal", "$doubleVal", "$int64Val", "$dateVal"]}),
|
||||
);
|
||||
// The result of an addition must remain in the range of int64_t in order to convert back to a Date;
|
||||
// an overflow into the domain of double-precision floating point numbers triggers a query-fatal
|
||||
// error.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble"]}), ErrorCodes.Overflow);
|
||||
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowInt64"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowInt64"]}), ErrorCodes.Overflow);
|
||||
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$int64Val", "$overflowDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// An overflow into the domain of Decimal128 results in an overflow exception.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}), ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$overflowDouble", "$overflowDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// Adding a double-typed NaN to a date value.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}), ErrorCodes.Overflow);
|
||||
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}), ErrorCodes.Overflow);
|
||||
|
||||
// An NaN Decimal128 added to date results in an overflow exception.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}), ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}), ErrorCodes.Overflow);
|
||||
|
||||
// Addition with a date, a double-typed NaN, and a third value.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}),
|
||||
ErrorCodes.Overflow);
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}),
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// Addition with a date, and both types of NaN.
|
||||
assert.throwsWithCode(
|
||||
() => getResultOfExpression({$add: ["$dateVal", "$nanDouble", "$nanDecimal"]}),
|
||||
ErrorCodes.Overflow);
|
||||
ErrorCodes.Overflow,
|
||||
);
|
||||
|
||||
// Throw error when there're two or more date in $add.
|
||||
assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", 1, "$dateVal"]}), 4974202);
|
||||
|
||||
// Test very large long and verify that we're maintaining the precision of long arithmetic.
|
||||
// 2397083434877565865 and 239708343487756586 both cast to the same double value from longs
|
||||
assert.eq(ISODate("2019-01-30T07:30:10.958Z"), getResultOfExpression({
|
||||
$add: [
|
||||
"$dateVal",
|
||||
NumberLong("2397083434877565865"),
|
||||
"$doubleVal",
|
||||
NumberLong("-2397083434877565864")
|
||||
]
|
||||
}));
|
||||
assert.eq(
|
||||
ISODate("2019-01-30T07:30:10.958Z"),
|
||||
getResultOfExpression({
|
||||
$add: ["$dateVal", NumberLong("2397083434877565865"), "$doubleVal", NumberLong("-2397083434877565864")],
|
||||
}),
|
||||
);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -5,7 +5,8 @@ import "jstests/libs/query/sbe_assert_error_override.js";
|
||||
|
||||
const coll = db.any_element_true;
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({
|
||||
assert.commandWorked(
|
||||
coll.insert({
|
||||
_id: 0,
|
||||
allTrue: [true, true],
|
||||
someTrue: [true, false],
|
||||
@ -15,8 +16,9 @@ assert.commandWorked(coll.insert({
|
||||
undefinedInput: [undefined],
|
||||
undefinedTrue: [undefined, true],
|
||||
nullTrue: [null, true],
|
||||
empty: []
|
||||
}));
|
||||
empty: [],
|
||||
}),
|
||||
);
|
||||
|
||||
function testOp(expression, expected) {
|
||||
const results = coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray();
|
||||
@ -27,8 +29,7 @@ function testOp(expression, expected) {
|
||||
}
|
||||
|
||||
function assertThrows(expression) {
|
||||
const error =
|
||||
assert.throws(() => coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray());
|
||||
const error = assert.throws(() => coll.aggregate([{$project: {_id: 0, result: expression}}]).toArray());
|
||||
assert.commandFailedWithCode(error, 5159200);
|
||||
}
|
||||
|
||||
|
||||
@ -3,25 +3,25 @@
|
||||
const coll = db.arith_overflow;
|
||||
|
||||
function runTest(operator, expectedResults) {
|
||||
const result =
|
||||
coll.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
|
||||
const result = coll
|
||||
.aggregate([{$project: {res: {[operator]: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}])
|
||||
.toArray()
|
||||
.map(r => r.res);
|
||||
.map((r) => r.res);
|
||||
assert.eq(result, expectedResults);
|
||||
}
|
||||
|
||||
// $add
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e+9), rhs: NumberInt(2e+9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e+18), rhs: NumberLong(9e+18)}));
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e9), rhs: NumberInt(2e9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e18), rhs: NumberLong(9e18)}));
|
||||
|
||||
runTest("$add", [NumberLong(4e+9), 1.8e+19]);
|
||||
runTest("$add", [NumberLong(4e9), 1.8e19]);
|
||||
|
||||
// $subtract
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e+9), rhs: NumberInt(-2e+9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e+18), rhs: NumberLong(-9e+18)}));
|
||||
assert.commandWorked(coll.insert({_id: 0, lhs: NumberInt(2e9), rhs: NumberInt(-2e9)}));
|
||||
assert.commandWorked(coll.insert({_id: 1, lhs: NumberLong(9e18), rhs: NumberLong(-9e18)}));
|
||||
|
||||
runTest("$subtract", [NumberLong(4e+9), 1.8e+19]);
|
||||
runTest("$subtract", [NumberLong(4e9), 1.8e19]);
|
||||
// $multiply uses same arguments
|
||||
runTest("$multiply", [NumberLong(-4e+18), -8.1e+37]);
|
||||
runTest("$multiply", [NumberLong(-4e18), -8.1e37]);
|
||||
|
||||
@ -6,17 +6,14 @@
|
||||
* 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 () {
|
||||
@ -37,8 +34,8 @@ const $x = "$x"; // fieldpath to "block" constant folding
|
||||
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
|
||||
const buildExpressionFromArguments = (arr, op) => {
|
||||
if (Array.isArray(arr)) {
|
||||
return {[op]: arr.map(elt => buildExpressionFromArguments(elt, op))};
|
||||
} else if (typeof arr === 'string' || arr instanceof String) {
|
||||
return {[op]: arr.map((elt) => buildExpressionFromArguments(elt, op))};
|
||||
} else if (typeof arr === "string" || arr instanceof String) {
|
||||
return arr;
|
||||
} else {
|
||||
return {$const: arr};
|
||||
@ -64,54 +61,69 @@ function assertConstantFoldingResults(input, addOutput, 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.");
|
||||
[[1, 2], 3, 4, 5],
|
||||
15,
|
||||
120,
|
||||
"Nested operations with all constants should be folded away.",
|
||||
);
|
||||
|
||||
// Left-associative test cases.
|
||||
assertConstantFoldingResults([1, 2, $x],
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, $x],
|
||||
[3, $x],
|
||||
[2, $x],
|
||||
"Constants should fold left-to-right before the first non-constant.");
|
||||
"Constants should fold left-to-right before the first non-constant.",
|
||||
);
|
||||
assertConstantFoldingResults(
|
||||
[$x, 1, 2],
|
||||
[$x, 1, 2],
|
||||
[$x, 1, 2],
|
||||
"Constants should not fold left-to-right after the first non-constant.");
|
||||
assertConstantFoldingResults(
|
||||
[1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
|
||||
"Constants should not fold left-to-right after the first non-constant.",
|
||||
);
|
||||
assertConstantFoldingResults([1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
|
||||
|
||||
assertConstantFoldingResults([5, 2, $x, 3, 4],
|
||||
assertConstantFoldingResults(
|
||||
[5, 2, $x, 3, 4],
|
||||
[7, $x, 3, 4],
|
||||
[10, $x, 3, 4],
|
||||
"Constants should fold up until a non-constant.");
|
||||
"Constants should fold up until a non-constant.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults([$x, 1, 2, 3],
|
||||
assertConstantFoldingResults(
|
||||
[$x, 1, 2, 3],
|
||||
[$x, 1, 2, 3],
|
||||
"Non-constant at start of operand list blocks folding constants.");
|
||||
[$x, 1, 2, 3],
|
||||
"Non-constant at start of operand list blocks folding constants.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults([[1, 2, $x], 3, 4, $x, 5],
|
||||
assertConstantFoldingResults(
|
||||
[[1, 2, $x], 3, 4, $x, 5],
|
||||
[[3, $x], 3, 4, $x, 5],
|
||||
[[2, $x], 3, 4, $x, 5],
|
||||
"Nested operation folds as expected.");
|
||||
"Nested operation folds as expected.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, [1, 2, $x], 3, 4, $x, 5],
|
||||
[3, [3, $x], 3, 4, $x, 5],
|
||||
[2, [2, $x], 3, 4, $x, 5],
|
||||
"Nested operation folds along with outer operation following left-associative rules.");
|
||||
"Nested operation folds along with outer operation following left-associative rules.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, [1, 2, $x, 5, 6], 3, 4, 5],
|
||||
[3, [3, $x, 5, 6], 3, 4, 5],
|
||||
[2, [2, $x, 5, 6], 3, 4, 5],
|
||||
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.");
|
||||
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.",
|
||||
);
|
||||
|
||||
assertConstantFoldingResults(
|
||||
[1, 2, $x, 4, [1, 2, $x, 5, 6], 3, 4, 5],
|
||||
[3, $x, 4, [3, $x, 5, 6], 3, 4, 5],
|
||||
[2, $x, 4, [2, $x, 5, 6], 3, 4, 5],
|
||||
"Nested operation folds along and even when fieldpath exists before it.");
|
||||
}());
|
||||
"Nested operation folds along and even when fieldpath exists before it.",
|
||||
);
|
||||
})();
|
||||
|
||||
// Mixing $add and $multiply
|
||||
(function () {
|
||||
@ -126,13 +138,13 @@ const assertFoldedResult = (expr, expected, message) => {
|
||||
const wrapLits = (arr) => {
|
||||
if (Array.isArray(arr)) {
|
||||
return arr.map(wrapLits);
|
||||
} else if (typeof arr === 'object') {
|
||||
} else if (typeof arr === "object") {
|
||||
let out = {};
|
||||
Object.keys(arr).forEach(k => {
|
||||
Object.keys(arr).forEach((k) => {
|
||||
out[k] = wrapLits(arr[k]);
|
||||
});
|
||||
return out;
|
||||
} else if (typeof arr === 'string' || arr instanceof String) {
|
||||
} else if (typeof arr === "string" || arr instanceof String) {
|
||||
return arr;
|
||||
} else {
|
||||
return {$const: arr};
|
||||
@ -143,32 +155,42 @@ const assertFoldedResult = (expr, expected, message) => {
|
||||
assert.eq(processedPipeline[0].$group._id, wrapLits(expected), message);
|
||||
};
|
||||
|
||||
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
{$add: [3, {$multiply: [12, "$x", 5, 6]}, 6, 7]},
|
||||
"Multiply inside add will fold as much as it can.");
|
||||
"Multiply inside add will fold as much as it can.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
|
||||
{$multiply: [2, {$add: [7, "$x", 5, 6]}, 6, 7]},
|
||||
"Add inside multiply will fold as much as it can.");
|
||||
"Add inside multiply will fold as much as it can.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
{$add: [369, "$x", 7, 8]},
|
||||
"Multiply without fieldpath will fold away and add will continue folding.");
|
||||
"Multiply without fieldpath will fold away and add will continue folding.",
|
||||
);
|
||||
|
||||
assertFoldedResult({$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
||||
{$multiply: [216, "$x", 7, 8]},
|
||||
"Add without fieldpath will fold away and multiply will continue folding.");
|
||||
"Add without fieldpath will fold away and multiply will continue folding.",
|
||||
);
|
||||
|
||||
assertFoldedResult(
|
||||
{$add: [1, 2, "$x", {$multiply: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
||||
{$add: [3, "$x", {$multiply: [12, "$x", 5, 6]}, 6, 7, 8]},
|
||||
"Constant folding nested $multiply proceeds even after outer $add stops folding.");
|
||||
"Constant folding nested $multiply proceeds even after outer $add stops folding.",
|
||||
);
|
||||
|
||||
assertFoldedResult(
|
||||
{$multiply: [1, 2, "$x", {$add: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
||||
{$multiply: [2, "$x", {$add: [7, "$x", 5, 6]}, 6, 7, 8]},
|
||||
"Constant folding nested $add proceeds even after outer multiply stops folding.");
|
||||
}());
|
||||
"Constant folding nested $add proceeds even after outer multiply stops folding.",
|
||||
);
|
||||
})();
|
||||
|
||||
// Regression tests for BFs related to SERVER-63099.
|
||||
(function () {
|
||||
@ -183,35 +205,40 @@ const makePipeline = (id) => [{$group: {_id: id, sum: {$sum: 1}}}];
|
||||
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]}))
|
||||
actual: coll
|
||||
.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
|
||||
.toArray()
|
||||
.map(idToString),
|
||||
expected: [
|
||||
"915242528741.9469524422272990976000",
|
||||
"905721242210.0453137831269007622941",
|
||||
]
|
||||
expected: ["915242528741.9469524422272990976000", "905721242210.0453137831269007622941"],
|
||||
});
|
||||
|
||||
// BF-24945
|
||||
coll.drop();
|
||||
coll.insert({x: 0, y: 4.1});
|
||||
assert(numberDecimalsEqual(
|
||||
assert(
|
||||
numberDecimalsEqual(
|
||||
coll
|
||||
.aggregate(makePipeline(
|
||||
{$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$x", "$y"]}))
|
||||
.toArray()[0]
|
||||
._id,
|
||||
NumberDecimal(0)));
|
||||
.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"]
|
||||
}))
|
||||
actual: coll
|
||||
.aggregate(
|
||||
makePipeline({
|
||||
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$y", "$x"],
|
||||
}),
|
||||
)
|
||||
.toArray()
|
||||
.map(idToString),
|
||||
expected: ["NaN"]
|
||||
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);
|
||||
|
||||
@ -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,7 +6,8 @@ import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
|
||||
const coll = db.expression_binarySize;
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, x: ""},
|
||||
{_id: 1, x: "abc"},
|
||||
{_id: 2, x: "ab\0c"},
|
||||
@ -15,12 +16,11 @@ assert.commandWorked(coll.insert([
|
||||
{_id: 5, x: BinData(0, "1234")},
|
||||
{_id: 6, x: null},
|
||||
{_id: 7},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
const result =
|
||||
coll.aggregate([{$sort: {_id: 1}}, {$addFields: {s: {$binarySize: "$x"}}}]).toArray();
|
||||
const result = coll.aggregate([{$sort: {_id: 1}}, {$addFields: {s: {$binarySize: "$x"}}}]).toArray();
|
||||
assert.eq(result, [
|
||||
|
||||
{_id: 0, x: "", s: 0},
|
||||
{_id: 1, x: "abc", s: 3},
|
||||
// Javascript strings and BSON strings can contain '\0', so both of these have length 4.
|
||||
|
||||
@ -9,19 +9,22 @@ const collName = jsTestName();
|
||||
const coll = db[collName];
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, a: NumberInt(0), b: NumberInt(127), c: [NumberInt(0), NumberInt(127)]},
|
||||
{_id: 1, a: NumberInt(1), b: NumberInt(2), c: [NumberInt(1), NumberInt(2)]},
|
||||
{_id: 2, a: NumberInt(2), b: NumberInt(3), c: [NumberInt(2), NumberInt(3)]},
|
||||
{_id: 3, a: NumberInt(3), b: NumberInt(5), c: [NumberInt(3), NumberInt(5)]},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
function runAndAssert(expression, expectedResult) {
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
|
||||
actual: coll
|
||||
.aggregate([{$project: {r: {[expression]: ["$a", "$b"]}}}])
|
||||
.toArray()
|
||||
.map(doc => doc.r),
|
||||
expected: expectedResult
|
||||
.map((doc) => doc.r),
|
||||
expected: expectedResult,
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,60 +33,74 @@ runAndAssert("$bitOr", [127, 3, 3, 7]);
|
||||
runAndAssert("$bitXor", [127, 3, 1, 6]);
|
||||
|
||||
for (const operator of ["$bitAnd", "$bitOr", "$bitXor"]) {
|
||||
for (const operand
|
||||
of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
for (const operand of [Number(12.0), NumberDecimal("12"), "$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {[operator]: ["$a", operand]},
|
||||
}
|
||||
}]
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
ErrorCodes.TypeMismatch);
|
||||
ErrorCodes.TypeMismatch,
|
||||
);
|
||||
}
|
||||
for (const argument of ["$c", ["$c"], [[NumberInt(1), NumberInt(2)]]]) {
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {[operator]: argument},
|
||||
}
|
||||
}]
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
ErrorCodes.TypeMismatch);
|
||||
ErrorCodes.TypeMismatch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate([
|
||||
{$project: {r: {$bitNot: "$a"}}},
|
||||
])
|
||||
actual: coll
|
||||
.aggregate([{$project: {r: {$bitNot: "$a"}}}])
|
||||
.toArray()
|
||||
.map(doc => doc.r),
|
||||
expected: [-1, -2, -3, -4]
|
||||
.map((doc) => doc.r),
|
||||
expected: [-1, -2, -3, -4],
|
||||
});
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {$bitNot: 12.5},
|
||||
}
|
||||
}]
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
ErrorCodes.TypeMismatch);
|
||||
ErrorCodes.TypeMismatch,
|
||||
);
|
||||
|
||||
assert.commandFailedWithCode(coll.runCommand({
|
||||
assert.commandFailedWithCode(
|
||||
coll.runCommand({
|
||||
aggregate: collName,
|
||||
cursor: {},
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
r: {$bitNot: ["$a", "$b"]},
|
||||
}
|
||||
}]
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
16020); // Error for incorrect number of arguments.
|
||||
16020,
|
||||
); // Error for incorrect number of arguments.
|
||||
|
||||
@ -3,8 +3,7 @@ coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1}));
|
||||
|
||||
function checkBsonSize() {
|
||||
assert.eq(Object.bsonsize(coll.findOne()),
|
||||
coll.aggregate([{$project: {s: {$bsonSize: "$$CURRENT"}}}]).next().s);
|
||||
assert.eq(Object.bsonsize(coll.findOne()), coll.aggregate([{$project: {s: {$bsonSize: "$$CURRENT"}}}]).next().s);
|
||||
}
|
||||
|
||||
checkBsonSize();
|
||||
@ -15,13 +14,13 @@ checkBsonSize();
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: {subdoc: 12345}}}));
|
||||
checkBsonSize();
|
||||
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(7)}}));
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(7)}}));
|
||||
checkBsonSize();
|
||||
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(500)}}));
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(500)}}));
|
||||
checkBsonSize();
|
||||
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: 'x'.repeat(16 * 1e6)}}));
|
||||
assert.commandWorked(coll.update({_id: 1}, {$push: {xs: "x".repeat(16 * 1e6)}}));
|
||||
checkBsonSize();
|
||||
|
||||
// embedded arrays
|
||||
@ -34,9 +33,11 @@ checkBsonSize();
|
||||
|
||||
// bsonSize's argument must be a document
|
||||
function checkExpectsDocument(badInput) {
|
||||
assert.throws(() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
|
||||
assert.throws(
|
||||
() => coll.aggregate([{$project: {x: {$bsonSize: {$literal: badInput}}}}]),
|
||||
[],
|
||||
"$bsonSize requires a document input");
|
||||
"$bsonSize requires a document input",
|
||||
);
|
||||
}
|
||||
checkExpectsDocument(123);
|
||||
checkExpectsDocument("abc");
|
||||
|
||||
@ -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,19 +64,56 @@ testExpressionWithCollation(coll, {$indexOfCP: ["12abcB", "B"]}, 5, caseInsensit
|
||||
testExpressionWithCollation(coll, {$strcasecmp: ["100", "2"]}, -1, numericOrdering);
|
||||
|
||||
// Test that $setEquals respects the collation.
|
||||
testExpressionWithCollation(coll, {$setEquals: [["a", "B"], ["b", "A"]]}, true, caseInsensitive);
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$setEquals: [
|
||||
["a", "B"],
|
||||
["b", "A"],
|
||||
],
|
||||
},
|
||||
true,
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $setIntersection respects the collation.
|
||||
results =
|
||||
coll.aggregate([{$project: {out: {$setIntersection: [["a", "B", "c"], ["d", "b", "A"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$setIntersection: [
|
||||
["a", "B", "c"],
|
||||
["d", "b", "A"],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(2, results[0].out.length);
|
||||
|
||||
// Test that $setUnion respects the collation.
|
||||
results = coll.aggregate([{$project: {out: {$setUnion: [["a", "B", "c"], ["d", "b", "A"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$setUnion: [
|
||||
["a", "B", "c"],
|
||||
["d", "b", "A"],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(4, results[0].out.length);
|
||||
@ -87,20 +123,39 @@ assert.eq(4, results[0].out.length);
|
||||
assert(coll.drop);
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, upper: "A", lower: "a"}));
|
||||
var results1 = coll.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
var results1 = coll
|
||||
.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
var results2 = coll.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}],
|
||||
{collation: caseInsensitive})
|
||||
var results2 = coll
|
||||
.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(results1, results2);
|
||||
|
||||
// Test that $setDifference respects the collation.
|
||||
testExpressionWithCollation(coll, {$setDifference: [["a", "B"], ["b", "A"]]}, [], caseInsensitive);
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$setDifference: [
|
||||
["a", "B"],
|
||||
["b", "A"],
|
||||
],
|
||||
},
|
||||
[],
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $setIsSubset respects the collation.
|
||||
testExpressionWithCollation(
|
||||
coll, {$setIsSubset: [["a", "B"], ["b", "A", "c"]]}, true, caseInsensitive);
|
||||
coll,
|
||||
{
|
||||
$setIsSubset: [
|
||||
["a", "B"],
|
||||
["b", "A", "c"],
|
||||
],
|
||||
},
|
||||
true,
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $split doesn't respect the collation.
|
||||
testExpressionWithCollation(coll, {$split: ["abc", "B"]}, ["abc"], caseInsensitive);
|
||||
@ -108,8 +163,8 @@ testExpressionWithCollation(coll, {$split: ["abc", "B"]}, ["abc"], caseInsensiti
|
||||
// Test that an $and which can be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A"}));
|
||||
results = coll.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["b", "B"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["b", "B"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
@ -117,8 +172,8 @@ assert.eq(true, results[0].out);
|
||||
// Test that an $and which cannot be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A", str2: "B"}));
|
||||
results = coll.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["$str2", "b"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$and: [{$eq: ["$str", "a"]}, {$eq: ["$str2", "b"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
@ -126,8 +181,8 @@ assert.eq(true, results[0].out);
|
||||
// Test that an $or which can be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A"}));
|
||||
results = coll.aggregate([{$project: {out: {$or: [{$eq: ["$str", "a"]}, {$eq: ["b", "c"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$or: [{$eq: ["$str", "a"]}, {$eq: ["b", "c"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
@ -135,40 +190,44 @@ assert.eq(true, results[0].out);
|
||||
// Test that an $or which cannot be optimized out respects the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, str: "A", str2: "B"}));
|
||||
results = coll.aggregate([{$project: {out: {$or: [{$eq: ["$str", "c"]}, {$eq: ["$str2", "b"]}]}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate([{$project: {out: {$or: [{$eq: ["$str", "c"]}, {$eq: ["$str2", "b"]}]}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0].out);
|
||||
|
||||
// Test that $filter's subexpressions respect the collation.
|
||||
testExpressionWithCollation(coll,
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$filter: {
|
||||
input: {
|
||||
$cond: {
|
||||
if: {$eq: ["FOO", "foo"]},
|
||||
then: ["a", "b", "A", "c", "C", "d"],
|
||||
else: null
|
||||
}
|
||||
else: null,
|
||||
},
|
||||
},
|
||||
as: "str",
|
||||
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]}
|
||||
}
|
||||
cond: {$or: [{$eq: ["$$str", "a"]}, {$eq: ["$$str", "c"]}]},
|
||||
},
|
||||
},
|
||||
["a", "A", "c", "C"],
|
||||
caseInsensitive);
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $let's subexpressions respect the collation.
|
||||
testExpressionWithCollation(coll,
|
||||
testExpressionWithCollation(
|
||||
coll,
|
||||
{
|
||||
$let: {
|
||||
vars: {str: {$cond: [{$eq: ["A", "a"]}, "b", "c"]}},
|
||||
in : {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]}
|
||||
}
|
||||
in: {$cond: [{$eq: ["$$str", "B"]}, "d", "e"]},
|
||||
},
|
||||
},
|
||||
"d",
|
||||
caseInsensitive);
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $map's subexpressions respect the collation.
|
||||
testExpressionWithCollation(
|
||||
@ -177,17 +236,18 @@ testExpressionWithCollation(
|
||||
$map: {
|
||||
input: {$cond: [{$eq: ["A", "a"]}, ["aa", "a", "AA", "b"], null]},
|
||||
as: "val",
|
||||
in : {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]}
|
||||
}
|
||||
in: {$and: [{$eq: ["$$val", "aA"]}, {$eq: ["$$val", "Aa"]}]},
|
||||
},
|
||||
},
|
||||
[true, false, true, false],
|
||||
caseInsensitive);
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $group stage's _id expressions respect the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1}));
|
||||
results = coll.aggregate([{$group: {_id: {a: {$eq: ["a", "A"]}, b: {$eq: ["b", "B"]}}}}],
|
||||
{collation: caseInsensitive})
|
||||
results = coll
|
||||
.aggregate([{$group: {_id: {a: {$eq: ["a", "A"]}, b: {$eq: ["b", "B"]}}}}], {collation: caseInsensitive})
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq(true, results[0]._id.a);
|
||||
@ -201,19 +261,21 @@ testExpressionWithCollation(
|
||||
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}]
|
||||
}
|
||||
}
|
||||
$cond: [{$eq: ["a", "A"]}, {sum: {$add: ["$$value.sum", "$$this"]}}, {sum: 0}],
|
||||
},
|
||||
},
|
||||
},
|
||||
{sum: 7},
|
||||
caseInsensitive);
|
||||
caseInsensitive,
|
||||
);
|
||||
|
||||
// Test that $switch's subexpressions respect the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 1, a: "A"}));
|
||||
assert.commandWorked(coll.insert({_id: 2, b: "B"}));
|
||||
assert.commandWorked(coll.insert({_id: 3, c: "C"}));
|
||||
results = coll.aggregate(
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{$sort: {_id: 1}},
|
||||
{
|
||||
@ -222,15 +284,16 @@ results = coll.aggregate(
|
||||
$switch: {
|
||||
branches: [
|
||||
{case: {$eq: ["$a", "a"]}, then: "foo"},
|
||||
{case: {$eq: ["$b", "b"]}, then: "bar"}
|
||||
{case: {$eq: ["$b", "b"]}, then: "bar"},
|
||||
],
|
||||
default: "baz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive})
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(3, results.length);
|
||||
assert.eq("foo", results[0].out);
|
||||
@ -240,21 +303,33 @@ assert.eq("baz", results[2].out);
|
||||
// Test that a $zip's subexpressions respect the collation.
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0, evens: [0, 2, 4], odds: [1, 3]}));
|
||||
results = coll.aggregate([{
|
||||
results = coll
|
||||
.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
out: {
|
||||
$zip: {
|
||||
inputs: [
|
||||
{$cond: [{$eq: ["A", "a"]}, "$evens", "$odds"]},
|
||||
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]}
|
||||
{$cond: [{$eq: ["B", "b"]}, "$odds", "$evens"]},
|
||||
],
|
||||
defaults: [0, {$cond: [{$eq: ["C", "c"]}, 5, 7]}],
|
||||
useLongestLength: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
{collation: caseInsensitive})
|
||||
useLongestLength: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{collation: caseInsensitive},
|
||||
)
|
||||
.toArray();
|
||||
assert.eq(1, results.length);
|
||||
assert.eq([[0, 1], [2, 3], [4, 5]], results[0].out);
|
||||
assert.eq(
|
||||
[
|
||||
[0, 1],
|
||||
[2, 3],
|
||||
[4, 5],
|
||||
],
|
||||
results[0].out,
|
||||
);
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
const coll = db[jsTest.name()];
|
||||
coll.drop();
|
||||
const numbers = [1.0, NumberInt("1"), NumberLong("1"), NumberDecimal("1.0")];
|
||||
const specials = [{val: NaN, path: "$nan"}, {val: Infinity, path: "$inf"}];
|
||||
const specials = [
|
||||
{val: NaN, path: "$nan"},
|
||||
{val: Infinity, path: "$inf"},
|
||||
];
|
||||
|
||||
assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
|
||||
|
||||
@ -11,15 +14,9 @@ assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
|
||||
(function testCommutativityWithConstArguments() {
|
||||
specials.forEach((special) => {
|
||||
numbers.forEach((num) => {
|
||||
const expected = [
|
||||
{a: (num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val)}
|
||||
];
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [special.val, num]}, _id: 0}}])
|
||||
.toArray());
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [num, special.val]}, _id: 0}}])
|
||||
.toArray());
|
||||
const expected = [{a: num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val}];
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [special.val, num]}, _id: 0}}]).toArray());
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [num, special.val]}, _id: 0}}]).toArray());
|
||||
});
|
||||
});
|
||||
})();
|
||||
@ -27,15 +24,9 @@ assert.commandWorked(coll.insert({inf: Infinity, nan: NaN}));
|
||||
(function testCommutativityWithNonConstArgument() {
|
||||
specials.forEach((special) => {
|
||||
numbers.forEach((num) => {
|
||||
const expected = [
|
||||
{a: (num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val)}
|
||||
];
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [special.path, num]}, _id: 0}}])
|
||||
.toArray());
|
||||
assert.eq(expected,
|
||||
coll.aggregate([{$project: {a: {[op]: [num, special.path]}, _id: 0}}])
|
||||
.toArray());
|
||||
const expected = [{a: num instanceof NumberDecimal ? NumberDecimal(special.val) : special.val}];
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [special.path, num]}, _id: 0}}]).toArray());
|
||||
assert.eq(expected, coll.aggregate([{$project: {a: {[op]: [num, special.path]}, _id: 0}}]).toArray());
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@ -19,7 +19,8 @@ import {checkSbeFullyEnabled} from "jstests/libs/query/sbe_util.js";
|
||||
const coll = db.projection_expr_concat_arrays;
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insertOne({
|
||||
assert.commandWorked(
|
||||
coll.insertOne({
|
||||
int_arr: [1, 2, 3, 4],
|
||||
dbl_arr: [10.0, 20.1, 20.4, 50.5],
|
||||
nested_arr: [["an", "array"], "arr", [[], [[], "a", "b"]]],
|
||||
@ -33,13 +34,14 @@ assert.commandWorked(coll.insertOne({
|
||||
str_val: "a string",
|
||||
dbl_val: 2.0,
|
||||
int_val: 1,
|
||||
obj_val: {a: 1, b: "two"}
|
||||
}));
|
||||
obj_val: {a: 1, b: "two"},
|
||||
}),
|
||||
);
|
||||
|
||||
function runAndAssert(operands, expectedResult) {
|
||||
assertArrayEq({
|
||||
actual: coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).map(doc => doc.f),
|
||||
expected: expectedResult
|
||||
actual: coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).map((doc) => doc.f),
|
||||
expected: expectedResult,
|
||||
});
|
||||
}
|
||||
function runAndAssertNull(operands) {
|
||||
@ -47,8 +49,7 @@ function runAndAssertNull(operands) {
|
||||
}
|
||||
|
||||
function runAndAssertThrows(operands) {
|
||||
const error =
|
||||
assert.throws(() => coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).toArray());
|
||||
const error = assert.throws(() => coll.aggregate([{$project: {f: {$concatArrays: operands}}}]).toArray());
|
||||
assert.commandFailedWithCode(error, 28664);
|
||||
}
|
||||
|
||||
@ -65,37 +66,54 @@ runAndAssert([[0], "$int_arr", [5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7]]);
|
||||
runAndAssert(["$int_arr", "$str_arr"], [[1, 2, 3, 4, "a", "b", "c"]]);
|
||||
runAndAssert(
|
||||
["$obj_arr", "$obj_arr", "$null_arr"],
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, null, null, null]]);
|
||||
runAndAssert(["$int_arr", "$str_arr", "$nested_arr"],
|
||||
[[1, 2, 3, 4, "a", "b", "c", ["an", "array"], "arr", [[], [[], "a", "b"]]]]);
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, null, null, null]],
|
||||
);
|
||||
runAndAssert(
|
||||
["$int_arr", "$str_arr", "$nested_arr"],
|
||||
[[1, 2, 3, 4, "a", "b", "c", ["an", "array"], "arr", [[], [[], "a", "b"]]]],
|
||||
);
|
||||
runAndAssert(["$int_arr", "$obj_arr"], [[1, 2, 3, 4, {a: 1, b: 2}, {c: 3}, {d: 4, e: 5}]]);
|
||||
runAndAssert(["$obj_arr"], [[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}]]);
|
||||
runAndAssert(["$obj_arr", [{o: 123, b: 1}, {y: "o", d: "a"}]],
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {o: 123, b: 1}, {y: "o", d: "a"}]]);
|
||||
runAndAssert(
|
||||
[
|
||||
"$obj_arr",
|
||||
[
|
||||
{o: 123, b: 1},
|
||||
{y: "o", d: "a"},
|
||||
],
|
||||
],
|
||||
[[{a: 1, b: 2}, {c: 3}, {d: 4, e: 5}, {o: 123, b: 1}, {y: "o", d: "a"}]],
|
||||
);
|
||||
|
||||
// Confirm that arrays containing null can be concatenated.
|
||||
runAndAssert(["$null_arr"], [[null, null, null]]);
|
||||
runAndAssert([[null], "$null_arr"], [[null, null, null, null]]);
|
||||
runAndAssert("$one_null_arr", [[null]]);
|
||||
runAndAssert(["$null_arr", "$one_null_arr", "$int_arr", "$null_arr"],
|
||||
[[null, null, null, null, 1, 2, 3, 4, null, null, null]]);
|
||||
runAndAssert(
|
||||
["$null_arr", "$one_null_arr", "$int_arr", "$null_arr"],
|
||||
[[null, null, null, null, 1, 2, 3, 4, null, null, null]],
|
||||
);
|
||||
|
||||
// Test operands that form more complex expressions.
|
||||
runAndAssert([{$concatArrays: "$int_arr"}], [[1, 2, 3, 4]]);
|
||||
runAndAssert([{$concatArrays: "$int_arr"}, {$concatArrays: {$concatArrays: "$str_arr"}}],
|
||||
[[1, 2, 3, 4, "a", "b", "c"]]);
|
||||
runAndAssert(["$str_arr", {$filter: {input: "$int_arr",
|
||||
as: "num",
|
||||
cond: { $and: [
|
||||
{ $gte: [ "$$num", 2 ] },
|
||||
{ $lte: [ "$$num", 3 ] }
|
||||
] }}}, "$int_arr"],
|
||||
[["a", "b", "c", 2, 3, 1, 2, 3, 4]]);
|
||||
runAndAssert(
|
||||
[{$concatArrays: "$int_arr"}, {$concatArrays: {$concatArrays: "$str_arr"}}],
|
||||
[[1, 2, 3, 4, "a", "b", "c"]],
|
||||
);
|
||||
runAndAssert(
|
||||
[
|
||||
"$str_arr",
|
||||
{$filter: {input: "$int_arr", as: "num", cond: {$and: [{$gte: ["$$num", 2]}, {$lte: ["$$num", 3]}]}}},
|
||||
"$int_arr",
|
||||
],
|
||||
[["a", "b", "c", 2, 3, 1, 2, 3, 4]],
|
||||
);
|
||||
|
||||
// Confirm that empty arrays can be concatenated with variables.
|
||||
runAndAssert(
|
||||
["$str_arr", {$filter: {input: [], cond: {$isArray: [{$concatArrays: [[], "$$this"]}]}}}],
|
||||
[["a", "b", "c"]]);
|
||||
[["a", "b", "c"]],
|
||||
);
|
||||
|
||||
// Concatenation with no arguments results in the empty array.
|
||||
runAndAssert([], [[]]);
|
||||
@ -113,11 +131,7 @@ runAndAssertNull(["$not_a_field"]);
|
||||
runAndAssertNull(["$null_val"]);
|
||||
runAndAssertNull(["$not_a_field", "$null_val"]);
|
||||
runAndAssertNull(["$null_val", "$not_a_field"]);
|
||||
runAndAssertNull([
|
||||
{$concatArrays: "$int_arr"},
|
||||
null,
|
||||
{$concatArrays: {$concatArrays: ["$obj_arr", "$str_arr"]}}
|
||||
]);
|
||||
runAndAssertNull([{$concatArrays: "$int_arr"}, null, {$concatArrays: {$concatArrays: ["$obj_arr", "$str_arr"]}}]);
|
||||
|
||||
// Confirm edge case where if null precedes non-array input, null is returned.
|
||||
runAndAssertNull(["$int_arr", "$null_val", "$int_val"]);
|
||||
@ -153,7 +167,8 @@ runAndAssertThrows(["$int_arr", 32]);
|
||||
assert(coll.drop());
|
||||
|
||||
// Test case where find returns multiple documents.
|
||||
assert.commandWorked(coll.insertMany([
|
||||
assert.commandWorked(
|
||||
coll.insertMany([
|
||||
{arr1: [42, 35.0, 197865432], arr2: ["albatross", "abbacus", "alien"]},
|
||||
{arr1: [1], arr2: ["albatross", "abbacus", "alien"]},
|
||||
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: []},
|
||||
@ -161,22 +176,29 @@ assert.commandWorked(coll.insertMany([
|
||||
{arr1: [], arr2: []},
|
||||
{arr1: [1, 2, 3, 4, 5, 6, 11, 12, 23], arr2: null},
|
||||
{some_field: "foo"},
|
||||
]));
|
||||
runAndAssert(["$arr1", "$arr2"], [
|
||||
]),
|
||||
);
|
||||
runAndAssert(
|
||||
["$arr1", "$arr2"],
|
||||
[
|
||||
[42, 35.0, 197865432, "albatross", "abbacus", "alien"],
|
||||
[1, "albatross", "abbacus", "alien"],
|
||||
[1, 2, 3, 4, 5, 6, 11, 12, 23],
|
||||
["foo", "bar"],
|
||||
[],
|
||||
null,
|
||||
null
|
||||
]);
|
||||
runAndAssert(["$arr1", [1, 2, 3], "$arr2"], [
|
||||
null,
|
||||
],
|
||||
);
|
||||
runAndAssert(
|
||||
["$arr1", [1, 2, 3], "$arr2"],
|
||||
[
|
||||
[42, 35.0, 197865432, 1, 2, 3, "albatross", "abbacus", "alien"],
|
||||
[1, 1, 2, 3, "albatross", "abbacus", "alien"],
|
||||
[1, 2, 3, 4, 5, 6, 11, 12, 23, 1, 2, 3],
|
||||
["foo", 1, 2, 3, "bar"],
|
||||
[1, 2, 3],
|
||||
null,
|
||||
null
|
||||
]);
|
||||
null,
|
||||
],
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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([
|
||||
assert.commandWorked(coll.insert({noonSense: "am", mealCombined: "no"}));
|
||||
assert.commandWorked(coll.insert({noonSense: "am", mealCombined: "yes"}));
|
||||
assert.commandWorked(coll.insert({noonSense: "pm", mealCombined: "yes"}));
|
||||
assert.commandWorked(coll.insert({noonSense: "pm", mealCombined: "no"}));
|
||||
assert.eq(
|
||||
["breakfast", "brunch", "dinner", "linner"],
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
meal: {
|
||||
$cond: [
|
||||
{$eq: ['$noonSense', 'am']},
|
||||
{$cond: [{$eq: ['$mealCombined', 'yes']}, 'brunch', 'breakfast']},
|
||||
{$cond: [{$eq: ['$mealCombined', 'yes']}, 'linner', 'dinner']}
|
||||
]
|
||||
}
|
||||
}
|
||||
{$eq: ["$noonSense", "am"]},
|
||||
{$cond: [{$eq: ["$mealCombined", "yes"]}, "brunch", "breakfast"]},
|
||||
{$cond: [{$eq: ["$mealCombined", "yes"]}, "linner", "dinner"]},
|
||||
],
|
||||
},
|
||||
{$sort: {meal: 1}}
|
||||
},
|
||||
},
|
||||
{$sort: {meal: 1}},
|
||||
])
|
||||
.map(doc => doc.meal));
|
||||
.map((doc) => doc.meal),
|
||||
);
|
||||
|
||||
@ -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)},
|
||||
@ -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 = [{
|
||||
let pipeline = [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}}
|
||||
}
|
||||
}];
|
||||
output: {$convert: {to: "binData", input: "$longInput", byteOrder: "big"}},
|
||||
},
|
||||
},
|
||||
];
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0x00000000000000c8", 8 byte long
|
||||
docs: [{longInput: NumberLong(200), expected: BinData(0, "AAAAAAAAAMg=")}]
|
||||
docs: [{longInput: NumberLong(200), expected: BinData(0, "AAAAAAAAAMg=")}],
|
||||
});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToInt() {
|
||||
let pipeline = [{
|
||||
let pipeline = [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}}
|
||||
}
|
||||
}];
|
||||
output: {$convert: {to: "binData", input: "$IntInput", byteOrder: "big"}},
|
||||
},
|
||||
},
|
||||
];
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xfffffe89", 4 byte int
|
||||
docs: [{IntInput: NumberInt(-375), expected: BinData(0, "///+iQ==")}]
|
||||
docs: [{IntInput: NumberInt(-375), expected: BinData(0, "///+iQ==")}],
|
||||
});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToDouble() {
|
||||
let pipeline = [{
|
||||
let pipeline = [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
expected: 1,
|
||||
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}}
|
||||
}
|
||||
}];
|
||||
output: {$convert: {to: "binData", input: "$DoubleInput", byteOrder: "big"}},
|
||||
},
|
||||
},
|
||||
];
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xC004CCCCCCCCCCCD", 8 byte double precision double
|
||||
docs: [{DoubleInput: -2.6, expected: BinData(0, "wATMzMzMzM0=")}]
|
||||
docs: [{DoubleInput: -2.6, expected: BinData(0, "wATMzMzMzM0=")}],
|
||||
});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToIntShortCut() {
|
||||
let pipeline = [{$project: {_id: 0, expected: 1, output: {$toInt: "$binDataInput"}}}];
|
||||
// Hex: "0x=02", 1 byte integer
|
||||
testConvertNumeric(
|
||||
{pipeline: pipeline, docs: [{binDataInput: BinData(0, "Ag=="), expected: NumberInt(2)}]});
|
||||
testConvertNumeric({pipeline: pipeline, docs: [{binDataInput: BinData(0, "Ag=="), expected: NumberInt(2)}]});
|
||||
})();
|
||||
|
||||
(function testConvertBindataToLongShortCut() {
|
||||
@ -289,7 +293,7 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xA2020000", 4 byte long
|
||||
docs: [{binDataInput: BinData(6, "ogIAAA=="), expected: NumberLong(674)}]
|
||||
docs: [{binDataInput: BinData(6, "ogIAAA=="), expected: NumberLong(674)}],
|
||||
});
|
||||
})();
|
||||
|
||||
@ -298,6 +302,6 @@ function testConvertNumeric({pipeline: convertPipeline, docs: documents}) {
|
||||
testConvertNumeric({
|
||||
pipeline: pipeline,
|
||||
// Hex: "0xCDCCCCCCCCCC04C0", 4 byte long
|
||||
docs: [{binDataInput: BinData(0, "zczMzMzMBMA="), expected: -2.6}]
|
||||
docs: [{binDataInput: BinData(0, "zczMzMzMBMA="), expected: -2.6}],
|
||||
});
|
||||
})();
|
||||
|
||||
@ -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: {
|
||||
{
|
||||
$lookup: {
|
||||
from: collName,
|
||||
let: {
|
||||
docId: "$_id"
|
||||
docId: "$_id",
|
||||
},
|
||||
pipeline: [{$addFields: {uuid: {$createUUID: {}}}}],
|
||||
as: "result",
|
||||
},
|
||||
},
|
||||
pipeline: [
|
||||
{$addFields: {uuid: {$createUUID: {}}}}
|
||||
],
|
||||
as: "result"
|
||||
}},
|
||||
];
|
||||
|
||||
const resultArray = coll.aggregate(pipeline).toArray();
|
||||
assert.eq(resultArray.length, docCount);
|
||||
const s = resultArray.flatMap(doc => doc.result).map(doc => doc.uuid);
|
||||
const s = resultArray.flatMap((doc) => doc.result).map((doc) => doc.uuid);
|
||||
const set = new Set(s);
|
||||
assert.eq(set.size, docCount * docCount);
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ function basicTest() {
|
||||
// Evaluate a $group with another $currentDate expression.
|
||||
{$group: {_id: "$time1", time2: {$first: {$currentDate: {}}}}},
|
||||
{$project: {time1: "$_id", time2: 1}},
|
||||
{$project: {_id: 0}}
|
||||
{$project: {_id: 0}},
|
||||
];
|
||||
|
||||
const resultArray = coll.aggregate(pipeline).toArray();
|
||||
@ -55,13 +55,15 @@ try {
|
||||
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluation", {ms: 2}),
|
||||
primaryNodeOnly: false,
|
||||
});
|
||||
failPoints.push(...FixtureHelpers.mapOnEachShardNode({
|
||||
failPoints.push(
|
||||
...FixtureHelpers.mapOnEachShardNode({
|
||||
db: db.getSiblingDB("admin"),
|
||||
func: (db) => configureFailPoint(db, "sleepBeforeCurrentDateEvaluationSBE", {ms: 2}),
|
||||
primaryNodeOnly: false,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
basicTest();
|
||||
} finally {
|
||||
failPoints.forEach(failPoint => failPoint.off());
|
||||
failPoints.forEach((failPoint) => failPoint.off());
|
||||
}
|
||||
|
||||
@ -26,18 +26,16 @@ function runAndAssertResultOrErrorCode(date, result, errorCode) {
|
||||
function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
|
||||
executeAggregationTestCase(coll, {
|
||||
pipeline: [{$project: {_id: 0, newDate: dateArithmeticsSpec}}],
|
||||
inputDocuments: [
|
||||
{_id: 1, date: ISODate("2020-12-31T12:10:05"), unit: "month", timezone: "Europe/Paris"}
|
||||
],
|
||||
inputDocuments: [{_id: 1, date: ISODate("2020-12-31T12:10:05"), unit: "month", timezone: "Europe/Paris"}],
|
||||
expectedErrorCode: expectedErrorCode,
|
||||
expectedResults: expectedResult
|
||||
|
||||
expectedResults: expectedResult,
|
||||
});
|
||||
}
|
||||
|
||||
(function testDateAddWithValidInputs() {
|
||||
runAndAssert({$dateAdd: {startDate: ISODate("2020-11-30T12:10:05Z"), unit: "day", amount: 1}},
|
||||
[{newDate: ISODate("2020-12-01T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: ISODate("2020-11-30T12:10:05Z"), unit: "day", amount: 1}}, [
|
||||
{newDate: ISODate("2020-12-01T12:10:05Z")},
|
||||
]);
|
||||
|
||||
// Test that adding with a null argument (non-existing field) results in null.
|
||||
runAndAssert({$dateAdd: {startDate: "$dateSent", unit: "day", amount: 1}}, [{newDate: null}]);
|
||||
@ -46,148 +44,179 @@ function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: null}}, [{newDate: null}]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1, timezone: null}},
|
||||
[{newDate: null}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1, timezone: null}}, [{newDate: null}]);
|
||||
|
||||
// Test combination of null and invalid arguments.
|
||||
runAndAssertResultOrErrorCode({$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
|
||||
runAndAssertResultOrErrorCode(
|
||||
{$dateAdd: {startDate: "$dateSent", unit: "workday", amount: 1}},
|
||||
[{newDate: null}],
|
||||
ErrorCodes.FailedToParse);
|
||||
ErrorCodes.FailedToParse,
|
||||
);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "New year day", unit: "$timeunit", amount: 1}},
|
||||
[{newDate: null}]);
|
||||
runAndAssert({$dateAdd: {startDate: "New year day", unit: "$timeunit", amount: 1}}, [{newDate: null}]);
|
||||
|
||||
runAndAssertResultOrErrorCode(
|
||||
{$dateAdd: {startDate: "$date", unit: "workday", amount: "$amount", timezone: "Unknown"}},
|
||||
[{newDate: null}],
|
||||
ErrorCodes.FailedToParse);
|
||||
ErrorCodes.FailedToParse,
|
||||
);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1.5, timezone: null}},
|
||||
[{newDate: null}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1.5, timezone: null}}, [{newDate: null}]);
|
||||
|
||||
// Tests when startDate and result date cross the DST time change in a timezone.
|
||||
runAndAssert({
|
||||
runAndAssert(
|
||||
{
|
||||
$dateAdd: {
|
||||
startDate: ISODate("2020-10-24T18:10:00Z"),
|
||||
unit: "hour",
|
||||
amount: 24,
|
||||
timezone: "Europe/Paris"
|
||||
}
|
||||
timezone: "Europe/Paris",
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T18:10:00Z")}]);
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T18:10:00Z")}],
|
||||
);
|
||||
|
||||
// When adding units of day both the startDate and the result represent 20:10:00 in
|
||||
// Europe/Paris. The two dates have different offsets from UTC due to the change in daylight
|
||||
// savings time.
|
||||
runAndAssert({
|
||||
runAndAssert(
|
||||
{
|
||||
$dateAdd: {
|
||||
startDate: ISODate("2020-10-24T18:10:00Z"),
|
||||
unit: "day",
|
||||
amount: 1,
|
||||
timezone: "Europe/Paris"
|
||||
}
|
||||
timezone: "Europe/Paris",
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T19:10:00Z")}]);
|
||||
},
|
||||
[{newDate: ISODate("2020-10-25T19:10:00Z")}],
|
||||
);
|
||||
|
||||
// The following tests use start date from the document field and all valid values for the
|
||||
// 'unit' argument.
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "year", amount: -1}},
|
||||
[{newDate: ISODate("2019-12-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "year", amount: -1}}, [
|
||||
{newDate: ISODate("2019-12-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "quarter", amount: 1}},
|
||||
[{newDate: ISODate("2021-03-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "quarter", amount: 1}}, [
|
||||
{newDate: ISODate("2021-03-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2}},
|
||||
[{newDate: ISODate("2021-02-28T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2}}, [
|
||||
{newDate: ISODate("2021-02-28T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "week", amount: 1}},
|
||||
[{newDate: ISODate("2021-01-07T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "week", amount: 1}}, [
|
||||
{newDate: ISODate("2021-01-07T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: 1}},
|
||||
[{newDate: ISODate("2021-01-01T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "day", amount: 1}}, [
|
||||
{newDate: ISODate("2021-01-01T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
|
||||
[{newDate: ISODate("2020-12-31T14:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "hour", amount: 2}}, [
|
||||
{newDate: ISODate("2020-12-31T14:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "minute", amount: -20}},
|
||||
[{newDate: ISODate("2020-12-31T11:50:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "minute", amount: -20}}, [
|
||||
{newDate: ISODate("2020-12-31T11:50:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "millisecond", amount: 1050}},
|
||||
[{newDate: ISODate("2020-12-31T12:10:06.05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "millisecond", amount: 1050}}, [
|
||||
{newDate: ISODate("2020-12-31T12:10:06.05Z")},
|
||||
]);
|
||||
|
||||
// Tests using the document fields for unit and timezone arguments.
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1}},
|
||||
[{newDate: ISODate("2021-01-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "$unit", amount: 1}}, [
|
||||
{newDate: ISODate("2021-01-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2, timezone: "$timezone"}},
|
||||
[{newDate: ISODate("2021-02-28T12:10:05Z")}]);
|
||||
runAndAssert({$dateAdd: {startDate: "$date", unit: "month", amount: 2, timezone: "$timezone"}}, [
|
||||
{newDate: ISODate("2021-02-28T12:10:05Z")},
|
||||
]);
|
||||
})();
|
||||
|
||||
(function testDateSubtractWithValidInputs() {
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "year", amount: 2}},
|
||||
[{newDate: ISODate("2018-12-31T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "year", amount: 2}}, [
|
||||
{newDate: ISODate("2018-12-31T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "quarter", amount: 2}},
|
||||
[{newDate: ISODate("2020-06-30T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "quarter", amount: 2}}, [
|
||||
{newDate: ISODate("2020-06-30T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "day", amount: 15}},
|
||||
[{newDate: ISODate("2020-12-16T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "day", amount: 15}}, [
|
||||
{newDate: ISODate("2020-12-16T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "hour", amount: 48}},
|
||||
[{newDate: ISODate("2020-12-29T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "hour", amount: 48}}, [
|
||||
{newDate: ISODate("2020-12-29T12:10:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "minute", amount: 15}},
|
||||
[{newDate: ISODate("2020-12-31T11:55:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "minute", amount: 15}}, [
|
||||
{newDate: ISODate("2020-12-31T11:55:05Z")},
|
||||
]);
|
||||
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "second", amount: 125}},
|
||||
[{newDate: ISODate("2020-12-31T12:08:00Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "second", amount: 125}}, [
|
||||
{newDate: ISODate("2020-12-31T12:08:00Z")},
|
||||
]);
|
||||
|
||||
// Test last day adjustment in UTC.
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3}},
|
||||
[{newDate: ISODate("2020-09-30T12:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3}}, [
|
||||
{newDate: ISODate("2020-09-30T12:10:05Z")},
|
||||
]);
|
||||
|
||||
// Test last day adjustment and crossing DST time change in a timezone.
|
||||
runAndAssert(
|
||||
{$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3, timezone: "$timezone"}},
|
||||
[{newDate: ISODate("2020-09-30T11:10:05Z")}]);
|
||||
runAndAssert({$dateSubtract: {startDate: "$date", unit: "$unit", amount: 3, timezone: "$timezone"}}, [
|
||||
{newDate: ISODate("2020-09-30T11:10:05Z")},
|
||||
]);
|
||||
|
||||
// Test last day adjustment in the New York timezone.
|
||||
runAndAssert({
|
||||
runAndAssert(
|
||||
{
|
||||
$dateSubtract: {
|
||||
startDate: ISODate("2021-01-31T03:00:00Z"),
|
||||
unit: "month",
|
||||
amount: 2,
|
||||
timezone: "America/New_York"
|
||||
}
|
||||
timezone: "America/New_York",
|
||||
},
|
||||
[{newDate: ISODate("2020-12-01T03:00:00Z")}]);
|
||||
},
|
||||
[{newDate: ISODate("2020-12-01T03:00:00Z")}],
|
||||
);
|
||||
})();
|
||||
|
||||
// Test combinations of $dateAdd and $dateSubtract.
|
||||
(function testDateArithmetics() {
|
||||
runAndAssert({
|
||||
runAndAssert(
|
||||
{
|
||||
$dateSubtract: {
|
||||
startDate: {$dateAdd: {startDate: "$date", unit: "hour", amount: 2}},
|
||||
unit: "hour",
|
||||
amount: 2
|
||||
}
|
||||
amount: 2,
|
||||
},
|
||||
[{newDate: ISODate("2020-12-31T12:10:05Z")}]);
|
||||
},
|
||||
[{newDate: ISODate("2020-12-31T12:10:05Z")}],
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
coll.aggregate([{
|
||||
$project: {newDate: {$dateSubtract: {startDate: "$date", unit: "month", amount: 1}}}
|
||||
}])
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {newDate: {$dateSubtract: {startDate: "$date", unit: "month", amount: 1}}},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
coll.aggregate([{
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
newDate: {
|
||||
$dateAdd:
|
||||
{startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5}
|
||||
}
|
||||
}
|
||||
}])
|
||||
.toArray());
|
||||
$dateAdd: {startDate: ISODate("2020-11-30T12:10:00Z"), unit: "second", amount: 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
);
|
||||
})();
|
||||
|
||||
// Tests for error codes.
|
||||
@ -202,34 +231,27 @@ function runTest({dateArithmeticsSpec, expectedResult, expectedErrorCode}) {
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", units: "day", amount: 1}}, 5166401);
|
||||
|
||||
// Invalid string type of startDate argument.
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "myBirthDate", unit: "year", amount: 10}},
|
||||
5166403);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "myBirthDate", unit: "year", amount: 10}}, 5166403);
|
||||
|
||||
// Invalid numeric type of unit argument.
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: 1, amount: 10}}, 5166404);
|
||||
|
||||
// Invalid value of unit argument.
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "epoch", amount: 10}},
|
||||
ErrorCodes.FailedToParse);
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "epoch", amount: 10}}, ErrorCodes.FailedToParse);
|
||||
|
||||
// Invalid double type of amount argument.
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "year", amount: 1.001}},
|
||||
5166405);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "year", amount: 1.001}}, 5166405);
|
||||
|
||||
// Overflow error of dateAdd operation due to large amount.
|
||||
runAndAssertErrorCode(
|
||||
{$dateSubtract: {startDate: "$date", unit: "month", amount: 12 * 300000000}}, 5166406);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "month", amount: 12 * 300000000}}, 5166406);
|
||||
|
||||
// Invalid 'amount' parameter error of dateAdd operation due to large amount.
|
||||
runAndAssertErrorCode(
|
||||
{$dateSubtract: {startDate: "$date", unit: "month", amount: -30000000000}}, 5976500);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "month", amount: -30000000000}}, 5976500);
|
||||
|
||||
// Invalid 'amount' parameter error of dateSubtract operation: long long min value cannot be
|
||||
// negated.
|
||||
runAndAssertErrorCode(
|
||||
{$dateSubtract: {startDate: "$date", unit: "day", amount: -9223372036854775808}}, 6045000);
|
||||
runAndAssertErrorCode({$dateSubtract: {startDate: "$date", unit: "day", amount: -9223372036854775808}}, 6045000);
|
||||
|
||||
// Invalid value of timezone argument.
|
||||
runAndAssertErrorCode(
|
||||
{$dateAdd: {startDate: "$date", unit: "year", amount: 1, timezone: "Unknown"}}, 40485);
|
||||
runAndAssertErrorCode({$dateAdd: {startDate: "$date", unit: "year", amount: 1, timezone: "Unknown"}}, 40485);
|
||||
})();
|
||||
|
||||
@ -14,16 +14,18 @@ const coll = testDB.collection;
|
||||
assert.commandWorked(testDB.dropDatabase());
|
||||
|
||||
const someDate = new Date("2020-11-01T18:23:36Z");
|
||||
const aggregationPipelineWithDateDiff = [{
|
||||
const aggregationPipelineWithDateDiff = [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
$dateDiff:
|
||||
{startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"}
|
||||
}
|
||||
}
|
||||
}];
|
||||
const aggregationPipelineWithDateDiffAndStartOfWeek = [{
|
||||
$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit", timezone: "$timeZone"},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const aggregationPipelineWithDateDiffAndStartOfWeek = [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
@ -32,60 +34,69 @@ const aggregationPipelineWithDateDiffAndStartOfWeek = [{
|
||||
endDate: "$endDate",
|
||||
unit: "$unit",
|
||||
timezone: "$timeZone",
|
||||
startOfWeek: "$startOfWeek"
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
startOfWeek: "$startOfWeek",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const testCases = [
|
||||
{
|
||||
// Parameters are constants, timezone is not specified.
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: true,
|
||||
date_diff: {
|
||||
$dateDiff: {
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour"
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
unit: "hour",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [{_id: 1}],
|
||||
expectedResults: [{_id: 1, date_diff: NumberLong("6")}]
|
||||
expectedResults: [{_id: 1, date_diff: NumberLong("6")}],
|
||||
},
|
||||
{
|
||||
// Parameters are field paths.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour",
|
||||
timeZone: "America/New_York",
|
||||
startOfWeek: "IGNORED" // Ignored when unit is not week.
|
||||
}],
|
||||
expectedResults: [{date_diff: NumberLong("6")}]
|
||||
startOfWeek: "IGNORED", // Ignored when unit is not week.
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("6")}],
|
||||
},
|
||||
{
|
||||
// Parameters are field paths, 'timezone' is not specified.
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff:
|
||||
{$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}}
|
||||
}
|
||||
}],
|
||||
inputDocuments: [{
|
||||
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "$unit"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2020-11-01T18:23:36Z"),
|
||||
endDate: new Date("2020-11-02T00:00:00Z"),
|
||||
unit: "hour"
|
||||
}],
|
||||
expectedResults: [{date_diff: NumberLong("6")}]
|
||||
unit: "hour",
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("6")}],
|
||||
},
|
||||
{
|
||||
// 'startDate' and 'endDate' are object ids.
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {
|
||||
@ -93,24 +104,27 @@ const testCases = [
|
||||
startDate: "$_id",
|
||||
endDate: "$_id",
|
||||
unit: "millisecond",
|
||||
timezone: "America/New_York"
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
timezone: "America/New_York",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [{}],
|
||||
expectedResults: [{date_diff: NumberLong("0")}]
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
// 'startDate' and 'endDate' are timestamps.
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}}
|
||||
}
|
||||
}],
|
||||
date_diff: {$dateDiff: {startDate: "$ts", endDate: "$ts", unit: "millisecond"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [{ts: new Timestamp()}],
|
||||
expectedResults: [{date_diff: NumberLong("0")}]
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
// Invalid 'startDate' type.
|
||||
@ -189,46 +203,53 @@ const testCases = [
|
||||
{
|
||||
// Invalid 'timezone' value.
|
||||
pipeline: aggregationPipelineWithDateDiff,
|
||||
inputDocuments:
|
||||
[{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
|
||||
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
|
||||
expectedErrorCode: 40485,
|
||||
},
|
||||
{
|
||||
// Specified 'startOfWeek'.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
|
||||
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
|
||||
unit: "week",
|
||||
timeZone: "GMT",
|
||||
startOfWeek: "MONDAY"
|
||||
}],
|
||||
startOfWeek: "MONDAY",
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("1")}],
|
||||
},
|
||||
{
|
||||
// Specified 'startOfWeek' and timezone.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2021-01-17T05:00:00Z"), // Sunday in New York.
|
||||
endDate: new Date("2021-01-17T04:59:00Z"), // Saturday in New York.
|
||||
unit: "week",
|
||||
timeZone: "America/New_York",
|
||||
startOfWeek: "sunday"
|
||||
}],
|
||||
startOfWeek: "sunday",
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("-1")}],
|
||||
},
|
||||
{
|
||||
// Unspecified 'startOfWeek' - defaults to Sunday.
|
||||
pipeline: [{
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: false,
|
||||
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}}
|
||||
}
|
||||
}],
|
||||
inputDocuments: [{
|
||||
date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
|
||||
endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
|
||||
}],
|
||||
},
|
||||
],
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
@ -246,30 +267,28 @@ const testCases = [
|
||||
{
|
||||
// Invalid 'startOfWeek' type.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [
|
||||
{startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}
|
||||
],
|
||||
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}],
|
||||
expectedErrorCode: 5439015,
|
||||
},
|
||||
{
|
||||
// Invalid 'startOfWeek' type, unit is not the week.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [
|
||||
{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}
|
||||
],
|
||||
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}],
|
||||
expectedResults: [{date_diff: NumberLong("0")}],
|
||||
},
|
||||
{
|
||||
// Invalid 'startOfWeek' value.
|
||||
pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
|
||||
inputDocuments: [{
|
||||
inputDocuments: [
|
||||
{
|
||||
startDate: someDate,
|
||||
endDate: someDate,
|
||||
unit: "week",
|
||||
timeZone: "GMT",
|
||||
startOfWeek: "FRIDIE"
|
||||
}],
|
||||
startOfWeek: "FRIDIE",
|
||||
},
|
||||
],
|
||||
expectedErrorCode: 5439016,
|
||||
}
|
||||
},
|
||||
];
|
||||
testCases.forEach(testCase => executeAggregationTestCase(coll, testCase));
|
||||
testCases.forEach((testCase) => executeAggregationTestCase(coll, testCase));
|
||||
|
||||
@ -7,35 +7,33 @@ function test(date, testSynthetics) {
|
||||
c.drop();
|
||||
c.save({date: date});
|
||||
|
||||
var ISOfmt = (date.getUTCMilliseconds() == 0) ? 'ISODate("%Y-%m-%dT%H:%M:%SZ")'
|
||||
: 'ISODate("%Y-%m-%dT%H:%M:%S.%LZ")';
|
||||
var ISOfmt = date.getUTCMilliseconds() == 0 ? 'ISODate("%Y-%m-%dT%H:%M:%SZ")' : 'ISODate("%Y-%m-%dT%H:%M:%S.%LZ")';
|
||||
|
||||
// Can't use aggregate helper or assertErrorCode because we need to handle multiple error types
|
||||
var res = c.runCommand('aggregate', {
|
||||
pipeline: [{
|
||||
var res = c.runCommand("aggregate", {
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
year: {$year: '$date'},
|
||||
month: {$month: '$date'},
|
||||
dayOfMonth: {$dayOfMonth: '$date'},
|
||||
hour: {$hour: '$date'},
|
||||
minute: {$minute: '$date'},
|
||||
second: {$second: '$date'}
|
||||
year: {$year: "$date"},
|
||||
month: {$month: "$date"},
|
||||
dayOfMonth: {$dayOfMonth: "$date"},
|
||||
hour: {$hour: "$date"},
|
||||
minute: {$minute: "$date"},
|
||||
second: {$second: "$date"},
|
||||
|
||||
// server-6666
|
||||
,
|
||||
millisecond: {$millisecond: '$date'}
|
||||
millisecond: {$millisecond: "$date"},
|
||||
|
||||
// server-9289
|
||||
,
|
||||
millisecondPlusTen: {$millisecond: {$add: ['$date', 10]}}
|
||||
millisecondPlusTen: {$millisecond: {$add: ["$date", 10]}},
|
||||
|
||||
// server-11118
|
||||
,
|
||||
format: {$dateToString: {format: ISOfmt, date: '$date'}}
|
||||
}
|
||||
}],
|
||||
cursor: {}
|
||||
format: {$dateToString: {format: ISOfmt, date: "$date"}},
|
||||
},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
});
|
||||
|
||||
if (date.valueOf() < 0 && _isWindows() && res.code == 16422) {
|
||||
@ -58,9 +56,11 @@ function test(date, testSynthetics) {
|
||||
assert.eq(res.cursor.firstBatch[0].minute, date.getUTCMinutes(), "minute");
|
||||
assert.eq(res.cursor.firstBatch[0].second, date.getUTCSeconds(), "second");
|
||||
assert.eq(res.cursor.firstBatch[0].millisecond, date.getUTCMilliseconds(), "millisecond");
|
||||
assert.eq(res.cursor.firstBatch[0].millisecondPlusTen,
|
||||
assert.eq(
|
||||
res.cursor.firstBatch[0].millisecondPlusTen,
|
||||
(date.getUTCMilliseconds() + 10) % 1000,
|
||||
"millisecondPlusTen");
|
||||
"millisecondPlusTen",
|
||||
);
|
||||
assert.eq(res.cursor.firstBatch[0].format, date.tojson(), "format");
|
||||
assert.eq(res.cursor.firstBatch[0], {
|
||||
year: date.getUTCFullYear(),
|
||||
@ -70,8 +70,8 @@ function test(date, testSynthetics) {
|
||||
minute: date.getUTCMinutes(),
|
||||
second: date.getUTCSeconds(),
|
||||
millisecond: date.getUTCMilliseconds(),
|
||||
millisecondPlusTen: ((date.getUTCMilliseconds() + 10) % 1000),
|
||||
format: date.tojson()
|
||||
millisecondPlusTen: (date.getUTCMilliseconds() + 10) % 1000,
|
||||
format: date.tojson(),
|
||||
});
|
||||
|
||||
if (testSynthetics) {
|
||||
@ -79,39 +79,39 @@ function test(date, testSynthetics) {
|
||||
res = c.aggregate({
|
||||
$project: {
|
||||
_id: 0,
|
||||
week: {$week: '$date'},
|
||||
dayOfWeek: {$dayOfWeek: '$date'},
|
||||
dayOfYear: {$dayOfYear: '$date'},
|
||||
format: {$dateToString: {format: '%U-%w-%j', date: '$date'}}
|
||||
}
|
||||
week: {$week: "$date"},
|
||||
dayOfWeek: {$dayOfWeek: "$date"},
|
||||
dayOfYear: {$dayOfYear: "$date"},
|
||||
format: {$dateToString: {format: "%U-%w-%j", date: "$date"}},
|
||||
},
|
||||
});
|
||||
|
||||
assert.eq(res.toArray()[0], {week: 0, dayOfWeek: 7, dayOfYear: 2, format: '00-7-002'});
|
||||
assert.eq(res.toArray()[0], {week: 0, dayOfWeek: 7, dayOfYear: 2, format: "00-7-002"});
|
||||
}
|
||||
}
|
||||
|
||||
// Basic test
|
||||
test(ISODate('1960-01-02 03:04:05.006Z'), true);
|
||||
test(ISODate("1960-01-02 03:04:05.006Z"), true);
|
||||
|
||||
// Testing special rounding rules for seconds
|
||||
test(ISODate('1960-01-02 03:04:04.999Z'), false); // second = 4
|
||||
test(ISODate('1960-01-02 03:04:05.000Z'), true); // second = 5
|
||||
test(ISODate('1960-01-02 03:04:05.001Z'), true); // second = 5
|
||||
test(ISODate('1960-01-02 03:04:05.999Z'), true); // second = 5
|
||||
test(ISODate("1960-01-02 03:04:04.999Z"), false); // second = 4
|
||||
test(ISODate("1960-01-02 03:04:05.000Z"), true); // second = 5
|
||||
test(ISODate("1960-01-02 03:04:05.001Z"), true); // second = 5
|
||||
test(ISODate("1960-01-02 03:04:05.999Z"), true); // second = 5
|
||||
|
||||
// Test date before 1900 (negative tm_year values from gmtime)
|
||||
test(ISODate('1860-01-02 03:04:05.006Z'), false);
|
||||
test(ISODate("1860-01-02 03:04:05.006Z"), false);
|
||||
|
||||
// Test with time_t == -1 and 0
|
||||
test(new Date(-1000), false);
|
||||
test(new Date(0), false);
|
||||
|
||||
// Testing dates between 1970 and 2000
|
||||
test(ISODate('1970-01-01 00:00:00.000Z'), false);
|
||||
test(ISODate('1970-01-01 00:00:00.999Z'), false);
|
||||
test(ISODate('1980-05-20 12:53:64.834Z'), false);
|
||||
test(ISODate('1999-12-31 00:00:00.000Z'), false);
|
||||
test(ISODate('1999-12-31 23:59:59.999Z'), false);
|
||||
test(ISODate("1970-01-01 00:00:00.000Z"), false);
|
||||
test(ISODate("1970-01-01 00:00:00.999Z"), false);
|
||||
test(ISODate("1980-05-20 12:53:64.834Z"), false);
|
||||
test(ISODate("1999-12-31 00:00:00.000Z"), false);
|
||||
test(ISODate("1999-12-31 23:59:59.999Z"), false);
|
||||
|
||||
// Test date > 2000 for completeness (using now)
|
||||
test(new Date(), false);
|
||||
|
||||
@ -13,7 +13,8 @@ import "jstests/libs/query/sbe_assert_error_override.js";
|
||||
const coll = db.date_expressions_with_time_zones;
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
// Three sales on 2017-06-16 in UTC.
|
||||
{_id: 0, date: new ISODate("2017-06-16T00:00:00.000Z"), sales: 1},
|
||||
{_id: 1, date: new ISODate("2017-06-16T12:02:21.013Z"), sales: 2},
|
||||
@ -21,28 +22,31 @@ assert.commandWorked(coll.insert([
|
||||
{_id: 2, date: new ISODate("2017-06-17T00:00:00.000Z"), sales: 2},
|
||||
{_id: 3, date: new ISODate("2017-06-17T12:02:21.013Z"), sales: 2},
|
||||
{_id: 4, date: new ISODate("2017-06-17T15:00:33.101Z"), sales: 2},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
// Compute how many sales happened on each day, in UTC.
|
||||
assert.eq(
|
||||
[
|
||||
{_id: {year: 2017, month: 6, day: 16}, totalSales: 3},
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 6}
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 6},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: {$year: "$date"},
|
||||
month: {$month: "$date"},
|
||||
day: {$dayOfMonth: "$date"}
|
||||
day: {$dayOfMonth: "$date"},
|
||||
},
|
||||
totalSales: {$sum: "$sales"}
|
||||
}
|
||||
totalSales: {$sum: "$sales"},
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Compute how many sales happened on each day, in New York. The sales made at midnight should
|
||||
// move to the previous days.
|
||||
@ -50,44 +54,48 @@ assert.eq(
|
||||
[
|
||||
{_id: {year: 2017, month: 6, day: 15}, totalSales: 1},
|
||||
{_id: {year: 2017, month: 6, day: 16}, totalSales: 4},
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4}
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: {$year: {date: "$date", timezone: "America/New_York"}},
|
||||
month: {$month: {date: "$date", timezone: "America/New_York"}},
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "America/New_York"}}
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "America/New_York"}},
|
||||
},
|
||||
totalSales: {$sum: "$sales"}
|
||||
}
|
||||
totalSales: {$sum: "$sales"},
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Compute how many sales happened on each day, in Sydney (+10 hours).
|
||||
assert.eq(
|
||||
[
|
||||
{_id: {year: 2017, month: 6, day: 16}, totalSales: 3},
|
||||
{_id: {year: 2017, month: 6, day: 17}, totalSales: 4},
|
||||
{_id: {year: 2017, month: 6, day: 18}, totalSales: 2}
|
||||
{_id: {year: 2017, month: 6, day: 18}, totalSales: 2},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: {$year: {date: "$date", timezone: "Australia/Sydney"}},
|
||||
month: {$month: {date: "$date", timezone: "Australia/Sydney"}},
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "Australia/Sydney"}}
|
||||
day: {$dayOfMonth: {date: "$date", timezone: "Australia/Sydney"}},
|
||||
},
|
||||
totalSales: {$sum: "$sales"}
|
||||
}
|
||||
totalSales: {$sum: "$sales"},
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}}
|
||||
},
|
||||
{$sort: {"_id.year": 1, "_id.month": 1, "_id.day": 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({}));
|
||||
@ -101,36 +109,38 @@ function runDateTimeExpressionWithTimezone(exprName, tz) {
|
||||
|
||||
function testDateTimeExpression(exprName, expectedValues) {
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insert({date: ISODate("2017-01-16T01:02:03.456Z"), timezone: "America/Sao_Paulo"}));
|
||||
assert.eq(expectedValues.idBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.commandWorked(coll.insert({date: ISODate("2017-01-16T01:02:03.456Z"), timezone: "America/Sao_Paulo"}));
|
||||
assert.eq(
|
||||
expectedValues.idBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "America/Sao_Paulo").cursor.firstBatch[0].out);
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out,
|
||||
);
|
||||
assert.eq(
|
||||
expectedValues.idBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "America/Sao_Paulo").cursor.firstBatch[0].out,
|
||||
);
|
||||
|
||||
// Test expression with offset based timezone
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insert({date: ISODate("2017-01-01T01:02:03.456Z"), timezone: "-01:30"}));
|
||||
assert.eq(expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "-01:30").cursor.firstBatch[0].out);
|
||||
assert.commandWorked(coll.insert({date: ISODate("2017-01-01T01:02:03.456Z"), timezone: "-01:30"}));
|
||||
assert.eq(
|
||||
expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out,
|
||||
);
|
||||
assert.eq(
|
||||
expectedValues.offsetBasedTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName, "-01:30").cursor.firstBatch[0].out,
|
||||
);
|
||||
|
||||
// Test expression when document has no $timezone field
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({date: ISODate("2017-01-16T01:02:03.456Z")}));
|
||||
assert.eq(null,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(expectedValues.noTzExpected,
|
||||
runDateTimeExpressionWithTimezone(exprName).cursor.firstBatch[0].out);
|
||||
assert.eq(null, runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(expectedValues.noTzExpected, runDateTimeExpressionWithTimezone(exprName).cursor.firstBatch[0].out);
|
||||
|
||||
// Test expression when document has no date field
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(coll.insert({timezone: "America/Sao_Paulo"}));
|
||||
assert.eq(null,
|
||||
runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
assert.eq(null, runDateTimeExpressionWithTimezone(exprName, "$timezone").cursor.firstBatch[0].out);
|
||||
|
||||
// test with invalid timezone identifier
|
||||
assert(coll.drop());
|
||||
@ -146,36 +156,23 @@ function testDateTimeExpression(exprName, expectedValues) {
|
||||
|
||||
// test with invalid date type
|
||||
assert(coll.drop());
|
||||
assert.commandWorked(
|
||||
coll.insert({date: "2017-06-16T00:00:00.000Z", timezone: "America/Sao_Paulo"}));
|
||||
assert.commandWorked(coll.insert({date: "2017-06-16T00:00:00.000Z", timezone: "America/Sao_Paulo"}));
|
||||
assert.commandFailedWithCode(runDateTimeExpressionWithTimezone(exprName, "$timezone"), 16006);
|
||||
}
|
||||
|
||||
testDateTimeExpression("$dayOfWeek",
|
||||
{idBasedTzExpected: 1, offsetBasedTzExpected: 7, noTzExpected: 2});
|
||||
testDateTimeExpression("$dayOfMonth",
|
||||
{idBasedTzExpected: 15, offsetBasedTzExpected: 31, noTzExpected: 16});
|
||||
testDateTimeExpression("$dayOfYear",
|
||||
{idBasedTzExpected: 15, offsetBasedTzExpected: 366, noTzExpected: 16});
|
||||
testDateTimeExpression("$year",
|
||||
{idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$month",
|
||||
{idBasedTzExpected: 1, offsetBasedTzExpected: 12, noTzExpected: 1});
|
||||
testDateTimeExpression("$hour",
|
||||
{idBasedTzExpected: 23, offsetBasedTzExpected: 23, noTzExpected: 1});
|
||||
testDateTimeExpression("$minute",
|
||||
{idBasedTzExpected: 2, offsetBasedTzExpected: 32, noTzExpected: 2});
|
||||
testDateTimeExpression("$second",
|
||||
{idBasedTzExpected: 3, offsetBasedTzExpected: 3, noTzExpected: 3});
|
||||
testDateTimeExpression("$millisecond",
|
||||
{idBasedTzExpected: 456, offsetBasedTzExpected: 456, noTzExpected: 456});
|
||||
testDateTimeExpression("$dayOfWeek", {idBasedTzExpected: 1, offsetBasedTzExpected: 7, noTzExpected: 2});
|
||||
testDateTimeExpression("$dayOfMonth", {idBasedTzExpected: 15, offsetBasedTzExpected: 31, noTzExpected: 16});
|
||||
testDateTimeExpression("$dayOfYear", {idBasedTzExpected: 15, offsetBasedTzExpected: 366, noTzExpected: 16});
|
||||
testDateTimeExpression("$year", {idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$month", {idBasedTzExpected: 1, offsetBasedTzExpected: 12, noTzExpected: 1});
|
||||
testDateTimeExpression("$hour", {idBasedTzExpected: 23, offsetBasedTzExpected: 23, noTzExpected: 1});
|
||||
testDateTimeExpression("$minute", {idBasedTzExpected: 2, offsetBasedTzExpected: 32, noTzExpected: 2});
|
||||
testDateTimeExpression("$second", {idBasedTzExpected: 3, offsetBasedTzExpected: 3, noTzExpected: 3});
|
||||
testDateTimeExpression("$millisecond", {idBasedTzExpected: 456, offsetBasedTzExpected: 456, noTzExpected: 456});
|
||||
testDateTimeExpression("$week", {idBasedTzExpected: 3, offsetBasedTzExpected: 52, noTzExpected: 3});
|
||||
testDateTimeExpression("$isoWeekYear",
|
||||
{idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$isoDayOfWeek",
|
||||
{idBasedTzExpected: 7, offsetBasedTzExpected: 6, noTzExpected: 1});
|
||||
testDateTimeExpression("$isoWeek",
|
||||
{idBasedTzExpected: 2, offsetBasedTzExpected: 52, noTzExpected: 3});
|
||||
testDateTimeExpression("$isoWeekYear", {idBasedTzExpected: 2017, offsetBasedTzExpected: 2016, noTzExpected: 2017});
|
||||
testDateTimeExpression("$isoDayOfWeek", {idBasedTzExpected: 7, offsetBasedTzExpected: 6, noTzExpected: 1});
|
||||
testDateTimeExpression("$isoWeek", {idBasedTzExpected: 2, offsetBasedTzExpected: 52, noTzExpected: 3});
|
||||
|
||||
// Make sure the data type returned by the date/time expressions is correct
|
||||
function testDateTimeExpressionType(exprName, exprType) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,6 @@
|
||||
import "jstests/libs/query/sbe_assert_error_override.js";
|
||||
|
||||
import {
|
||||
anyEq,
|
||||
assertErrCodeAndErrMsgContains,
|
||||
assertErrorCode
|
||||
} from "jstests/aggregation/extras/utils.js";
|
||||
import {anyEq, assertErrCodeAndErrMsgContains, assertErrorCode} from "jstests/aggregation/extras/utils.js";
|
||||
|
||||
const coll = db.date_from_string;
|
||||
|
||||
@ -18,61 +14,63 @@ let testCases = [
|
||||
{
|
||||
expect: "2017-07-04T11:56:02Z",
|
||||
inputString: "2017-07-04T11:56:02Z",
|
||||
format: "%Y-%m-%dT%H:%M:%SZ"
|
||||
format: "%Y-%m-%dT%H:%M:%SZ",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02.813Z",
|
||||
inputString: "2017-07-04T11:56:02.813Z",
|
||||
format: "%Y-%m-%dT%H:%M:%S.%LZ"
|
||||
format: "%Y-%m-%dT%H:%M:%S.%LZ",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02.810Z",
|
||||
inputString: "2017-07-04T11:56:02.81Z",
|
||||
format: "%Y-%m-%dT%H:%M:%S.%LZ"
|
||||
format: "%Y-%m-%dT%H:%M:%S.%LZ",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02.800Z",
|
||||
inputString: "2017-07-04T11:56:02.8Z",
|
||||
format: "%Y-%m-%dT%H:%M:%S.%LZ"
|
||||
format: "%Y-%m-%dT%H:%M:%S.%LZ",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02Z",
|
||||
inputString: "2017-07-04T11:56.02",
|
||||
format: "%Y-%m-%dT%H:%M.%S"
|
||||
format: "%Y-%m-%dT%H:%M.%S",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02.813Z",
|
||||
inputString: "2017-07-04T11:56.02.813",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02.810Z",
|
||||
inputString: "2017-07-04T11:56.02.81",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T11:56:02.800Z",
|
||||
inputString: "2017-07-04T11:56.02.8",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
];
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}}).toArray(),
|
||||
tojson(testCase),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -85,50 +83,55 @@ testCases = [
|
||||
{
|
||||
expect: "2017-07-04T10:56:02Z",
|
||||
inputString: "2017-07-04T11:56.02",
|
||||
format: "%Y-%m-%dT%H:%M.%S"
|
||||
format: "%Y-%m-%dT%H:%M.%S",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T10:56:02.813Z",
|
||||
inputString: "2017-07-04T11:56.02.813",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T10:56:02.810Z",
|
||||
inputString: "2017-07-04T11:56.02.81",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T10:56:02.800Z",
|
||||
inputString: "2017-07-04T11:56.02.8",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
];
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq([{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: testCase.inputString, timezone: "Europe/London"}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: testCase.inputString, timezone: "Europe/London"},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
assert.eq([{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
tojson(testCase),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: testCase.inputString,
|
||||
timezone: "Europe/London",
|
||||
format: testCase.format
|
||||
}
|
||||
}
|
||||
}
|
||||
format: testCase.format,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -141,61 +144,68 @@ testCases = [
|
||||
{
|
||||
expect: "2017-07-04T10:56:02Z",
|
||||
inputString: "2017-07-04T11:56.02",
|
||||
format: "%Y-%m-%dT%H:%M.%S"
|
||||
format: "%Y-%m-%dT%H:%M.%S",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T10:56:02.813Z",
|
||||
inputString: "2017-07-04T11:56.02.813",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T10:56:02.810Z",
|
||||
inputString: "2017-07-04T11:56.02.81",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
{
|
||||
expect: "2017-07-04T10:56:02.800Z",
|
||||
inputString: "2017-07-04T11:56.02.8",
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L"
|
||||
format: "%Y-%m-%dT%H:%M.%S.%L",
|
||||
},
|
||||
];
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {$dateFromString: {dateString: testCase.inputString, timezone: "+01:00"}}
|
||||
}
|
||||
date: {$dateFromString: {dateString: testCase.inputString, timezone: "+01:00"}},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
assert.eq([{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
tojson(testCase),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: testCase.inputString,
|
||||
timezone: "+01:00",
|
||||
format: testCase.format
|
||||
}
|
||||
}
|
||||
}
|
||||
format: testCase.format,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Normal format tests from data. */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, dateString: "2017-07-06T12:35:37Z", format: "%Y-%m-%dT%H:%M:%SZ"},
|
||||
{_id: 1, dateString: "2017-07-06T12:35:37.513Z", format: "%Y-%m-%dT%H:%M:%S.%LZ"},
|
||||
{_id: 2, dateString: "2017-07-06T12:35:37", format: "%Y-%m-%dT%H:%M:%S"},
|
||||
{_id: 3, dateString: "2017-07-06T12:35:37.513", format: "%Y-%m-%dT%H:%M:%S.%L"},
|
||||
{_id: 4, dateString: "1960-07-10T12:10:37.448", format: "%Y-%m-%dT%H:%M:%S.%L"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
let expectedResults = [
|
||||
{"_id": 0, "date": ISODate("2017-07-06T12:35:37Z")},
|
||||
@ -204,25 +214,30 @@ let expectedResults = [
|
||||
{"_id": 3, "date": ISODate("2017-07-06T12:35:37.513Z")},
|
||||
{"_id": 4, "date": ISODate("1960-07-10T12:10:37.448Z")},
|
||||
];
|
||||
assert.eq(expectedResults,
|
||||
coll.aggregate([
|
||||
assert.eq(
|
||||
expectedResults,
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Repeat the test with an explicit format specifier string.
|
||||
assert.eq(
|
||||
expectedResults,
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString", format: "$format"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
expectedResults = [
|
||||
{"_id": 0, "date": new Date(1499344537000)},
|
||||
@ -231,31 +246,37 @@ expectedResults = [
|
||||
{"_id": 3, "date": new Date(1499344537513)},
|
||||
{"_id": 4, "date": new Date(-299072962552)},
|
||||
];
|
||||
assert.eq(expectedResults,
|
||||
coll.aggregate([
|
||||
assert.eq(
|
||||
expectedResults,
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Repeat the test with an explicit format specifier string.
|
||||
assert.eq(
|
||||
expectedResults,
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString", format: "$format"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Normal format tests from data, with time zone. */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, dateString: "2017-07-06T12:35:37.513", timezone: "GMT"},
|
||||
{_id: 1, dateString: "2017-07-06T12:35:37.513", timezone: "UTC"},
|
||||
{_id: 2, dateString: "1960-07-10T12:35:37.513", timezone: "America/New_York"},
|
||||
@ -263,7 +284,8 @@ assert.commandWorked(coll.insert([
|
||||
{_id: 4, dateString: "2017-07-06T12:35:37.513", timezone: "America/Los_Angeles"},
|
||||
{_id: 5, dateString: "2017-07-06T12:35:37.513", timezone: "Europe/Paris"},
|
||||
{_id: 6, dateString: "2017-07-06T12:35:37.513", timezone: "+04:00"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
expectedResults = [
|
||||
{"_id": 0, "date": ISODate("2017-07-06T12:35:37.513Z")},
|
||||
@ -277,63 +299,66 @@ expectedResults = [
|
||||
|
||||
assert.eq(
|
||||
expectedResults,
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project:
|
||||
{date: {$dateFromString: {dateString: "$dateString", timezone: "$timezone"}}},
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString", timezone: "$timezone"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Repeat the test with an explicit format specifier string.
|
||||
assert.eq(expectedResults,
|
||||
coll.aggregate([
|
||||
assert.eq(
|
||||
expectedResults,
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: "$dateString",
|
||||
timezone: "$timezone",
|
||||
format: "%Y-%m-%dT%H:%M:%S.%L"
|
||||
}
|
||||
}
|
||||
format: "%Y-%m-%dT%H:%M:%S.%L",
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* dateString from data with timezone as constant */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, dateString: "2017-07-06T12:35:37"},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0, dateString: "2017-07-06T12:35:37"}]));
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{"_id": 0, "date": ISODate("2017-07-06T03:35:37Z")},
|
||||
],
|
||||
coll.aggregate([
|
||||
[{"_id": 0, "date": ISODate("2017-07-06T03:35:37Z")}],
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project:
|
||||
{date: {$dateFromString: {dateString: "$dateString", timezone: "Asia/Tokyo"}}},
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString", timezone: "Asia/Tokyo"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* dateString from constant with timezone from data */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, timezone: "Europe/London"},
|
||||
{_id: 1, timezone: "America/New_York"},
|
||||
{_id: 2, timezone: "-05:00"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -341,18 +366,19 @@ assert.eq(
|
||||
{"_id": 1, "date": ISODate("2017-07-19T22:52:35.199Z")},
|
||||
{"_id": 2, "date": ISODate("2017-07-19T23:52:35.199Z")},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: "2017-07-19T18:52:35.199", timezone: "$timezone"}
|
||||
}
|
||||
$dateFromString: {dateString: "2017-07-19T18:52:35.199", timezone: "$timezone"},
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* BI format tests. */
|
||||
@ -363,37 +389,39 @@ assert.commandWorked(coll.insert({_id: 0}));
|
||||
let pipelines = [
|
||||
{
|
||||
expect: "2017-01-01T00:00:00Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-01-01 00:00:00"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-01-01 00:00:00"}}}},
|
||||
},
|
||||
{
|
||||
expect: "2017-07-01T00:00:00Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-01 00:00:00"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-01 00:00:00"}}}},
|
||||
},
|
||||
{
|
||||
expect: "2017-07-06T00:00:00Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06"}}}},
|
||||
},
|
||||
{
|
||||
expect: "2017-07-06T00:00:00Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 00:00:00"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 00:00:00"}}}},
|
||||
},
|
||||
{
|
||||
expect: "2017-07-06T11:00:00Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:00:00"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:00:00"}}}},
|
||||
},
|
||||
{
|
||||
expect: "2017-07-06T11:36:00Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:00"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:00"}}}},
|
||||
},
|
||||
{
|
||||
expect: "2017-07-06T11:36:54Z",
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:54"}}}}
|
||||
pipeline: {$project: {date: {$dateFromString: {dateString: "2017-07-06 11:36:54"}}}},
|
||||
},
|
||||
];
|
||||
pipelines.forEach(function (pipeline) {
|
||||
assert.eq([{_id: 0, date: ISODate(pipeline.expect)}],
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(pipeline.expect)}],
|
||||
coll.aggregate(pipeline.pipeline).toArray(),
|
||||
tojson(pipeline));
|
||||
tojson(pipeline),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -427,28 +455,30 @@ testCases = [
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: testCase.inputString}}}}).toArray(),
|
||||
tojson(testCase),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: testCase.inputString, format: "%Y-%m-%dT%H:%M:%S.%L%z"}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: testCase.inputString, format: "%Y-%m-%dT%H:%M:%S.%L%z"},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* BI format tests from data. */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, dateString: "2017-01-01 00:00:00"},
|
||||
{_id: 1, dateString: "2017-07-01 00:00:00"},
|
||||
{_id: 2, dateString: "2017-07-06"},
|
||||
@ -456,7 +486,8 @@ assert.commandWorked(coll.insert([
|
||||
{_id: 4, dateString: "2017-07-06 11:00:00"},
|
||||
{_id: 5, dateString: "2017-07-06 11:36:00"},
|
||||
{_id: 6, dateString: "2017-07-06 11:36:54"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -466,21 +497,24 @@ assert.eq(
|
||||
{"_id": 3, "date": ISODate("2017-07-06T00:00:00Z")},
|
||||
{"_id": 4, "date": ISODate("2017-07-06T11:00:00Z")},
|
||||
{"_id": 5, "date": ISODate("2017-07-06T11:36:00Z")},
|
||||
{"_id": 6, "date": ISODate("2017-07-06T11:36:54Z")}
|
||||
{"_id": 6, "date": ISODate("2017-07-06T11:36:54Z")},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Wacky format tests from data. */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, dateString: "July 4th, 2017"},
|
||||
{_id: 1, dateString: "July 4th, 2017 12:39:30 BST"},
|
||||
{_id: 2, dateString: "July 4th, 2017 11am"},
|
||||
@ -490,7 +524,8 @@ assert.commandWorked(coll.insert([
|
||||
{_id: 6, dateString: "2017-Jul-04 noon"},
|
||||
{_id: 7, dateString: "2017-07-04 12:48:07 GMT+0545"},
|
||||
{_id: 8, dateString: "2017-07-04 12:48:07 GMT-0200"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -504,13 +539,15 @@ assert.eq(
|
||||
{"_id": 7, "date": ISODate("2017-07-04T07:03:07Z")},
|
||||
{"_id": 8, "date": ISODate("2017-07-04T14:48:07Z")},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "$dateString"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Tests formats that aren't supported with the normal $dateFromString parser. */
|
||||
@ -527,7 +564,7 @@ testCases = [
|
||||
{
|
||||
inputString: "Day: 05 Month: 12 Year: 1988",
|
||||
format: "Day: %d Month: %m Year: %Y",
|
||||
expect: "1988-12-05T00:00:00Z"
|
||||
expect: "1988-12-05T00:00:00Z",
|
||||
},
|
||||
{inputString: "Date: 1992/04/26", format: "Date: %Y/%m/%d", expect: "1992-04-26T00:00:00Z"},
|
||||
{inputString: "4/26/1992:+0445", format: "%m/%d/%Y:%z", expect: "1992-04-25T19:15:00Z"},
|
||||
@ -536,15 +573,17 @@ testCases = [
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -561,15 +600,17 @@ testCases = [
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -582,15 +623,17 @@ testCases = [
|
||||
testCases.forEach(function (testCase) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: ISODate(testCase.expect)}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: testCase.inputString, format: testCase.format},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray(),
|
||||
tojson(testCase));
|
||||
tojson(testCase),
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -598,20 +641,20 @@ testCases.forEach(function(testCase) {
|
||||
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0}]));
|
||||
|
||||
pipelines = [
|
||||
[{'$project': {date: {$dateFromString: {dateString: "July 4th"}}}}],
|
||||
[{'$project': {date: {$dateFromString: {dateString: "12:50:53"}}}}],
|
||||
[{"$project": {date: {$dateFromString: {dateString: "July 4th"}}}}],
|
||||
[{"$project": {date: {$dateFromString: {dateString: "12:50:53"}}}}],
|
||||
];
|
||||
|
||||
pipelines.forEach(function (pipeline) {
|
||||
assertErrCodeAndErrMsgContains(coll,
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll,
|
||||
pipeline,
|
||||
ErrorCodes.ConversionFailure,
|
||||
"an incomplete date/time string has been found");
|
||||
"an incomplete date/time string has been found",
|
||||
);
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
@ -619,76 +662,84 @@ pipelines.forEach(function(pipeline) {
|
||||
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0}]));
|
||||
|
||||
pipelines = [
|
||||
[{'$project': {date: {$dateFromString: {dateString: "2017, 12:50:53"}}}}],
|
||||
[{'$project': {date: {$dateFromString: {dateString: "60.Monday1770/06:59"}}}}],
|
||||
[{"$project": {date: {$dateFromString: {dateString: "2017, 12:50:53"}}}}],
|
||||
[{"$project": {date: {$dateFromString: {dateString: "60.Monday1770/06:59"}}}}],
|
||||
];
|
||||
|
||||
pipelines.forEach(function (pipeline) {
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* NULL returns. */
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-06-19T15:13:25.713Z")},
|
||||
{_id: 1, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: null},
|
||||
{_id: 2, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: undefined},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
pipelines = [
|
||||
[{$project: {date: {$dateFromString: {dateString: "$tz"}}}}, {$sort: {_id: 1}}],
|
||||
[
|
||||
{
|
||||
$project:
|
||||
{date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", timezone: "$tz"}}}
|
||||
$project: {date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", timezone: "$tz"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
],
|
||||
];
|
||||
pipelines.forEach(function (pipeline) {
|
||||
assert.eq([{_id: 0, date: null}, {_id: 1, date: null}, {_id: 2, date: null}],
|
||||
assert.eq(
|
||||
[
|
||||
{_id: 0, date: null},
|
||||
{_id: 1, date: null},
|
||||
{_id: 2, date: null},
|
||||
],
|
||||
coll.aggregate(pipeline).toArray(),
|
||||
tojson(pipeline));
|
||||
tojson(pipeline),
|
||||
);
|
||||
});
|
||||
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0},
|
||||
{_id: 1, format: null},
|
||||
{_id: 2, format: undefined},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0}, {_id: 1, format: null}, {_id: 2, format: undefined}]));
|
||||
|
||||
assert(anyEq(
|
||||
[{_id: 0, date: null}, {_id: 1, date: null}, {_id: 2, date: null}],
|
||||
coll.aggregate({
|
||||
$project:
|
||||
{date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", format: "$format"}}}
|
||||
assert(
|
||||
anyEq(
|
||||
[
|
||||
{_id: 0, date: null},
|
||||
{_id: 1, date: null},
|
||||
{_id: 2, date: null},
|
||||
],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: "2017-07-11T17:05:19Z", format: "$format"}}},
|
||||
})
|
||||
.toArray()));
|
||||
.toArray(),
|
||||
),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Parse errors. */
|
||||
|
||||
let pipeline = [{$project: {date: {$dateFromString: "no-object"}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, 40540, "$dateFromString only supports an object as an argument");
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 40540, "$dateFromString only supports an object as an argument");
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {"unknown": "$tz"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 40541, "Unrecognized argument");
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: 5}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll,
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll,
|
||||
pipeline,
|
||||
ErrorCodes.ConversionFailure,
|
||||
"$dateFromString requires that 'dateString' be a string");
|
||||
"$dateFromString requires that 'dateString' be a string",
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Passing in time zone with date/time string. */
|
||||
@ -696,32 +747,30 @@ assertErrCodeAndErrMsgContains(coll,
|
||||
pipeline = {
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: "2017-07-12T22:23:55 GMT+02:00", timezone: "Europe/Amsterdam"}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: "2017-07-12T22:23:55 GMT+02:00", timezone: "Europe/Amsterdam"},
|
||||
},
|
||||
},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
|
||||
|
||||
pipeline = {
|
||||
$project: {
|
||||
date: {$dateFromString: {dateString: "2017-07-12T22:23:55Z", timezone: "Europe/Amsterdam"}}
|
||||
}
|
||||
date: {$dateFromString: {dateString: "2017-07-12T22:23:55Z", timezone: "Europe/Amsterdam"}},
|
||||
},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
|
||||
|
||||
pipeline = {
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: "2017-07-12T22:23:55 America/New_York", timezone: "Europe/Amsterdam"}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: "2017-07-12T22:23:55 America/New_York", timezone: "Europe/Amsterdam"},
|
||||
},
|
||||
},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
|
||||
|
||||
pipeline = {
|
||||
$project: {date: {$dateFromString: {dateString: "2017-07-12T22:23:55 Europe/Amsterdam"}}}
|
||||
$project: {date: {$dateFromString: {dateString: "2017-07-12T22:23:55 Europe/Amsterdam"}}},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure);
|
||||
|
||||
@ -737,67 +786,65 @@ assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Tr
|
||||
|
||||
// Test missing specifier prefix '%'.
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "1992-26-04", format: "Y-d-m"}}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, ErrorCodes.ConversionFailure, "Format literal not found");
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Format literal not found");
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "1992", format: "%n"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 18536, "Invalid format character");
|
||||
|
||||
pipeline = [{
|
||||
pipeline = [
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: "4/26/1992:+0445", format: "%m/%d/%Y:%z", timezone: "+0500"}
|
||||
}
|
||||
}
|
||||
}];
|
||||
$dateFromString: {dateString: "4/26/1992:+0445", format: "%m/%d/%Y:%z", timezone: "+0500"},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll,
|
||||
pipeline,
|
||||
ErrorCodes.ConversionFailure,
|
||||
"you cannot pass in a date/time string with GMT offset together with a timezone argument");
|
||||
"you cannot pass in a date/time string with GMT offset together with a timezone argument",
|
||||
);
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "4/26/1992", format: 5}}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "4/26/1992", format: {}}}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 40684, "$dateFromString requires that 'format' be a string");
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "ISO Day 6", format: "ISO Day %u"}}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
|
||||
|
||||
pipeline =
|
||||
[{$project: {date: {$dateFromString: {dateString: "ISO Week 52", format: "ISO Week %V"}}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "ISO Week 52", format: "ISO Week %V"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "The parsed date was invalid");
|
||||
|
||||
pipeline = [{
|
||||
$project: {date: {$dateFromString: {dateString: "ISO Week 1, 2018", format: "ISO Week %V, %Y"}}}
|
||||
}];
|
||||
assertErrCodeAndErrMsgContains(coll,
|
||||
pipeline = [
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "ISO Week 1, 2018", format: "ISO Week %V, %Y"}}},
|
||||
},
|
||||
];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll,
|
||||
pipeline,
|
||||
ErrorCodes.ConversionFailure,
|
||||
"Mixing of ISO dates with natural dates is not allowed");
|
||||
"Mixing of ISO dates with natural dates is not allowed",
|
||||
);
|
||||
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d/%G"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll,
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll,
|
||||
pipeline,
|
||||
ErrorCodes.ConversionFailure,
|
||||
"Mixing of ISO dates with natural dates is not allowed");
|
||||
"Mixing of ISO dates with natural dates is not allowed",
|
||||
);
|
||||
|
||||
pipeline =
|
||||
[{$project: {date: {$dateFromString: {dateString: "Dece 31 2018", format: "%b %d %Y"}}}}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "Dece 31 2018", format: "%b %d %Y"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Error parsing date string");
|
||||
|
||||
// Test embedded null bytes in the 'dateString' and 'format' fields.
|
||||
pipeline =
|
||||
[{$project: {date: {$dateFromString: {dateString: "12/31\0/2018", format: "%m/%d/%Y"}}}}];
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31\0/2018", format: "%m/%d/%Y"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Not enough data");
|
||||
|
||||
pipeline =
|
||||
[{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d\0/%Y"}}}}];
|
||||
pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d\0/%Y"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Trailing data");
|
||||
|
||||
@ -12,165 +12,181 @@ coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0}));
|
||||
|
||||
// Test that the 'onError' value is returned when 'dateString' is not a valid date/time.
|
||||
for (let inputDate of ["July 4th",
|
||||
"12:50:53",
|
||||
"2017",
|
||||
"60.Monday1770/06:59",
|
||||
"Not even close",
|
||||
"July 4th, 10000"]) {
|
||||
for (let inputDate of ["July 4th", "12:50:53", "2017", "60.Monday1770/06:59", "Not even close", "July 4th, 10000"]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onErrorValue}],
|
||||
coll.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}}
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that the 'onError' value is returned when 'dateString' is not a string.
|
||||
for (let inputDate of [5, {year: 2018, month: 2, day: 5}, ["2018-02-05"]]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onErrorValue}],
|
||||
coll.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}}
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that the 'onError' value is ignored when 'dateString' is nullish.
|
||||
for (let inputDate of [null, undefined, "$missing"]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}}
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: inputDate, onError: onErrorValue}}},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that the 'onError' value is returned for unmatched format strings.
|
||||
for (let inputFormat of ["%Y", "%Y-%m-%dT%H", "Y-m-d"]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onErrorValue}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: "2018-02-06", format: inputFormat, onError: onErrorValue}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: "2018-02-06", format: inputFormat, onError: onErrorValue},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that null is returned when the 'timezone' or 'format' is nullish, regardless of the
|
||||
// 'onError' value.
|
||||
for (let nullishValue of [null, undefined, "$missing"]) {
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: "2018-02-06T11:56:02Z",
|
||||
format: nullishValue,
|
||||
onError: onErrorValue
|
||||
}
|
||||
}
|
||||
}
|
||||
onError: onErrorValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
.toArray(),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: "2018-02-06T11:56:02Z",
|
||||
timezone: nullishValue,
|
||||
onError: onErrorValue
|
||||
}
|
||||
}
|
||||
}
|
||||
onError: onErrorValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that onError is returned when the input is not a string and other parameters are
|
||||
// nullish.
|
||||
assert.eq(
|
||||
[{_id: 0, date: onErrorValue}],
|
||||
coll.aggregate({
|
||||
$project:
|
||||
{date: {$dateFromString: {dateString: 5, format: null, onError: onErrorValue}}}
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {date: {$dateFromString: {dateString: 5, format: null, onError: onErrorValue}}},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: onErrorValue}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date:
|
||||
{$dateFromString: {dateString: 5, timezone: "$missing", onError: onErrorValue}}
|
||||
}
|
||||
date: {$dateFromString: {dateString: 5, timezone: "$missing", onError: onErrorValue}},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Test that onError is ignored when the input is an invalid string and other parameters are
|
||||
// nullish.
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString:
|
||||
{dateString: "Invalid date string", format: null, onError: onErrorValue}
|
||||
}
|
||||
}
|
||||
$dateFromString: {dateString: "Invalid date string", format: null, onError: onErrorValue},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
.toArray(),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: "Invalid date string",
|
||||
timezone: "$missing",
|
||||
onError: onErrorValue
|
||||
}
|
||||
}
|
||||
}
|
||||
onError: onErrorValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Test that 'onError' can be any type, not just an ISODate.
|
||||
for (let onError of [{}, 5, "Not a date", null, undefined]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onError}],
|
||||
coll.aggregate(
|
||||
{$project: {date: {$dateFromString: {dateString: "invalid", onError: onError}}}})
|
||||
.toArray());
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: "invalid", onError: onError}}}}).toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that a missing 'onError' value results in no output field when used within a $project
|
||||
// stage.
|
||||
assert.eq(
|
||||
[{_id: 0}],
|
||||
coll.aggregate(
|
||||
{$project: {date: {$dateFromString: {dateString: "invalid", onError: "$missing"}}}})
|
||||
.toArray());
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: "invalid", onError: "$missing"}}}}).toArray(),
|
||||
);
|
||||
|
||||
// Test that 'onError' is ignored when the 'format' is invalid.
|
||||
let res = coll.runCommand("aggregate", {
|
||||
pipeline: [{
|
||||
$project:
|
||||
{date: {$dateFromString: {dateString: "4/26/1992", format: 5, onError: onErrorValue}}}
|
||||
}],
|
||||
cursor: {}
|
||||
pipeline: [
|
||||
{
|
||||
$project: {date: {$dateFromString: {dateString: "4/26/1992", format: 5, onError: onErrorValue}}},
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
});
|
||||
assert.commandFailedWithCode(res, 40684);
|
||||
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll,
|
||||
[{
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
date: {$dateFromString: {dateString: "4/26/1992", format: "%n", onError: onErrorValue}}
|
||||
}
|
||||
}],
|
||||
date: {$dateFromString: {dateString: "4/26/1992", format: "%n", onError: onErrorValue}},
|
||||
},
|
||||
},
|
||||
],
|
||||
18536,
|
||||
"Invalid format character '%n' in format string");
|
||||
"Invalid format character '%n' in format string",
|
||||
);
|
||||
|
||||
@ -11,52 +11,55 @@ assert.commandWorked(coll.insert({_id: 0}));
|
||||
for (let inputDate of [null, undefined, "$missing"]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onNullValue}],
|
||||
coll.aggregate(
|
||||
{$project: {date: {$dateFromString: {dateString: inputDate, onNull: onNullValue}}}})
|
||||
.toArray());
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: inputDate, onNull: onNullValue}}}}).toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that null is returned when the 'timezone' or 'format' is nullish, regardless of the
|
||||
// 'onNull' value.
|
||||
for (let nullishValue of [null, undefined, "$missing"]) {
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: "2018-02-06T11:56:02Z",
|
||||
format: nullishValue,
|
||||
onNull: onNullValue
|
||||
}
|
||||
}
|
||||
}
|
||||
onNull: onNullValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
.toArray(),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateFromString: {
|
||||
dateString: "2018-02-06T11:56:02Z",
|
||||
timezone: nullishValue,
|
||||
onNull: onNullValue
|
||||
}
|
||||
}
|
||||
}
|
||||
onNull: onNullValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that 'onNull' can be any type, not just an ISODate.
|
||||
for (let onNull of [{}, 5, "Not a date", null, undefined]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onNull}],
|
||||
coll.aggregate(
|
||||
{$project: {date: {$dateFromString: {dateString: "$missing", onNull: onNull}}}})
|
||||
.toArray());
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: "$missing", onNull: onNull}}}}).toArray(),
|
||||
);
|
||||
}
|
||||
assert.eq(
|
||||
[{_id: 0}],
|
||||
coll.aggregate(
|
||||
{$project: {date: {$dateFromString: {dateString: "$missing", onNull: "$missing"}}}})
|
||||
.toArray());
|
||||
coll.aggregate({$project: {date: {$dateFromString: {dateString: "$missing", onNull: "$missing"}}}}).toArray(),
|
||||
);
|
||||
|
||||
@ -6,99 +6,89 @@ const coll = db.dateToParts;
|
||||
coll.drop();
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: "UTC"},
|
||||
{_id: 1, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: "Europe/London"},
|
||||
{_id: 2, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: "America/New_York", iso: true},
|
||||
{_id: 3, date: new ISODate("2017-06-19T15:13:25.713Z"), tz: "America/New_York", iso: false},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: 0,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 1,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 2,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 3,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {'$dateToParts': {date: "$date"}}}}, {$sort: {_id: 1}}])
|
||||
.toArray());
|
||||
coll.aggregate([{$project: {date: {"$dateToParts": {date: "$date"}}}}, {$sort: {_id: 1}}]).toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: 0,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 1,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 2,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 3,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll.aggregate([
|
||||
{$project: {date: {'$dateToParts': {date: "$date", "timezone": "$tz"}}}},
|
||||
{$sort: {_id: 1}}
|
||||
coll
|
||||
.aggregate([{$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz"}}}}, {$sort: {_id: 1}}])
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: 0,
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 1,
|
||||
date: {year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 2,
|
||||
date: {year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
{
|
||||
_id: 3,
|
||||
date: {year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz", "iso8601": false}}},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: 0,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
},
|
||||
{
|
||||
_id: 1,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713}
|
||||
},
|
||||
{
|
||||
_id: 2,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713}
|
||||
},
|
||||
{
|
||||
_id: 3,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713}
|
||||
},
|
||||
],
|
||||
coll.aggregate([
|
||||
{
|
||||
$project:
|
||||
{date: {'$dateToParts': {date: "$date", "timezone": "$tz", "iso8601": false}}}
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -111,8 +101,8 @@ assert.eq(
|
||||
hour: 15,
|
||||
minute: 13,
|
||||
second: 25,
|
||||
millisecond: 713
|
||||
}
|
||||
millisecond: 713,
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 1,
|
||||
@ -123,8 +113,8 @@ assert.eq(
|
||||
hour: 16,
|
||||
minute: 13,
|
||||
second: 25,
|
||||
millisecond: 713
|
||||
}
|
||||
millisecond: 713,
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 2,
|
||||
@ -135,8 +125,8 @@ assert.eq(
|
||||
hour: 11,
|
||||
minute: 13,
|
||||
second: 25,
|
||||
millisecond: 713
|
||||
}
|
||||
millisecond: 713,
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 3,
|
||||
@ -147,18 +137,19 @@ assert.eq(
|
||||
hour: 11,
|
||||
minute: 13,
|
||||
second: 25,
|
||||
millisecond: 713
|
||||
}
|
||||
millisecond: 713,
|
||||
},
|
||||
},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project:
|
||||
{date: {'$dateToParts': {date: "$date", "timezone": "$tz", "iso8601": true}}}
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz", "iso8601": true}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -171,70 +162,74 @@ assert.eq(
|
||||
hour: 11,
|
||||
minute: 13,
|
||||
second: 25,
|
||||
millisecond: 713
|
||||
}
|
||||
millisecond: 713,
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 3,
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 11, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{$match: {iso: {$exists: true}}},
|
||||
{
|
||||
$project:
|
||||
{date: {'$dateToParts': {date: "$date", "timezone": "$tz", "iso8601": "$iso"}}}
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz", "iso8601": "$iso"}}},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Tests with timestamp */
|
||||
assert(coll.drop());
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{
|
||||
_id: ObjectId("58c7cba47bbadf523cf2c313"),
|
||||
date: new ISODate("2017-06-19T15:13:25.713Z"),
|
||||
tz: "Europe/London"
|
||||
tz: "Europe/London",
|
||||
},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: ObjectId("58c7cba47bbadf523cf2c313"),
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 15, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {'$dateToParts': {date: "$date"}}}}]).toArray());
|
||||
coll.aggregate([{$project: {date: {"$dateToParts": {date: "$date"}}}}]).toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: ObjectId("58c7cba47bbadf523cf2c313"),
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {'$dateToParts': {date: "$date", "timezone": "$tz"}}}}])
|
||||
.toArray());
|
||||
coll.aggregate([{$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz"}}}}]).toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: ObjectId("58c7cba47bbadf523cf2c313"),
|
||||
date:
|
||||
{year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713}
|
||||
date: {year: 2017, month: 6, day: 19, hour: 16, minute: 13, second: 25, millisecond: 713},
|
||||
},
|
||||
],
|
||||
coll.aggregate([{
|
||||
$project: {date: {'$dateToParts': {date: "$date", "timezone": "$tz", "iso8601": false}}}
|
||||
}])
|
||||
.toArray());
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz", "iso8601": false}}},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -247,99 +242,92 @@ assert.eq(
|
||||
hour: 16,
|
||||
minute: 13,
|
||||
second: 25,
|
||||
millisecond: 713
|
||||
}
|
||||
millisecond: 713,
|
||||
},
|
||||
},
|
||||
],
|
||||
coll.aggregate([{
|
||||
$project: {date: {'$dateToParts': {date: "$date", "timezone": "$tz", "iso8601": true}}}
|
||||
}])
|
||||
.toArray());
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz", "iso8601": true}}},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{
|
||||
_id: ObjectId("58c7cba47bbadf523cf2c313"),
|
||||
date: {year: 2017, month: 3, day: 14, hour: 10, minute: 53, second: 24, millisecond: 0}
|
||||
date: {year: 2017, month: 3, day: 14, hour: 10, minute: 53, second: 24, millisecond: 0},
|
||||
},
|
||||
],
|
||||
coll.aggregate([{
|
||||
$project: {date: {'$dateToParts': {date: "$_id", "timezone": "$tz", "iso8601": false}}}
|
||||
}])
|
||||
.toArray());
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {date: {"$dateToParts": {date: "$_id", "timezone": "$tz", "iso8601": false}}},
|
||||
},
|
||||
])
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
assert(coll.drop());
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, date: ISODate("2017-06-27T12:00:20Z")},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0, date: ISODate("2017-06-27T12:00:20Z")}]));
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{_id: 0, date: null},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {'$dateToParts': {date: "$date", timezone: "$tz"}}}}])
|
||||
.toArray());
|
||||
[{_id: 0, date: null}],
|
||||
coll.aggregate([{$project: {date: {"$dateToParts": {date: "$date", timezone: "$tz"}}}}]).toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
assert(coll.drop());
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, date: ISODate("2017-06-27T12:00:20Z")},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0, date: ISODate("2017-06-27T12:00:20Z")}]));
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{_id: 0, date: null},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {'$dateToParts': {date: "$date", iso8601: "$iso8601"}}}}])
|
||||
.toArray());
|
||||
[{_id: 0, date: null}],
|
||||
coll.aggregate([{$project: {date: {"$dateToParts": {date: "$date", iso8601: "$iso8601"}}}}]).toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
assert(coll.drop());
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
{_id: 0, tz: "Europe/London"},
|
||||
]));
|
||||
assert.commandWorked(coll.insert([{_id: 0, tz: "Europe/London"}]));
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
{_id: 0, date: null},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {'$dateToParts': {date: "$date"}}}}]).toArray());
|
||||
assert.eq([{_id: 0, date: null}], coll.aggregate([{$project: {date: {"$dateToParts": {date: "$date"}}}}]).toArray());
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
|
||||
let pipeline = {$project: {date: {'$dateToParts': {"timezone": "$tz"}}}};
|
||||
let pipeline = {$project: {date: {"$dateToParts": {"timezone": "$tz"}}}};
|
||||
assertErrorCode(coll, pipeline, 40522);
|
||||
|
||||
pipeline = {
|
||||
$project: {date: {'$dateToParts': {date: "$date", "timezone": "$tz", "iso8601": 5}}}
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "$tz", "iso8601": 5}}},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, 40521);
|
||||
|
||||
pipeline = {
|
||||
$project: {date: {'$dateToParts': {date: 42}}}
|
||||
$project: {date: {"$dateToParts": {date: 42}}},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, 16006);
|
||||
|
||||
pipeline = {
|
||||
$project: {date: {'$dateToParts': {date: "$date", "timezone": 5}}}
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": 5}}},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, 40517);
|
||||
|
||||
pipeline = {
|
||||
$project: {date: {'$dateToParts': {date: "$date", "timezone": "DoesNot/Exist"}}}
|
||||
$project: {date: {"$dateToParts": {date: "$date", "timezone": "DoesNot/Exist"}}},
|
||||
};
|
||||
assertErrorCode(coll, pipeline, 40485);
|
||||
|
||||
// Given a date, executes an aggregation command which requires the server to convert this date to
|
||||
// its parts in a timezone-aware fashion. Returns the resulting document containing the date parts.
|
||||
function runDateToPartsExpression(inputDate, timezone) {
|
||||
const results =
|
||||
coll.aggregate(
|
||||
[{$project: {_id: 0, out: {$dateToParts: {date: inputDate, timezone: timezone}}}}])
|
||||
const results = coll
|
||||
.aggregate([{$project: {_id: 0, out: {$dateToParts: {date: inputDate, timezone: timezone}}}}])
|
||||
.toArray();
|
||||
assert.eq(results.length, 1, results);
|
||||
return results[0].out;
|
||||
@ -350,19 +338,35 @@ function runDateToPartsExpression(inputDate, timezone) {
|
||||
// UTC-3 to UTC-2, typically starting in October or November and ending in February. Here we test
|
||||
// that dates in December and January use UTC-2 in 2017 and 2018, but use UTC-3 in 2019 and 2020 for
|
||||
// the Sao Paulo timezone.
|
||||
assert.eq({year: 2017, month: 12, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2017-12-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2018, month: 1, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2018-01-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2018, month: 12, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2018-12-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2019, month: 1, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2019-01-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2019, month: 12, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2019-12-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2020, month: 1, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2020-01-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2020, month: 12, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2020-12-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq({year: 2021, month: 1, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2021-01-15T10:00:00.000Z"), "America/Sao_Paulo"));
|
||||
assert.eq(
|
||||
{year: 2017, month: 12, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2017-12-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2018, month: 1, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2018-01-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2018, month: 12, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2018-12-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2019, month: 1, day: 15, hour: 8, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2019-01-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2019, month: 12, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2019-12-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2020, month: 1, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2020-01-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2020, month: 12, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2020-12-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
assert.eq(
|
||||
{year: 2021, month: 1, day: 15, hour: 7, minute: 0, second: 0, millisecond: 0},
|
||||
runDateToPartsExpression(ISODate("2021-01-15T10:00:00.000Z"), "America/Sao_Paulo"),
|
||||
);
|
||||
|
||||
@ -7,7 +7,8 @@ coll.drop();
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-07-04T14:56:42.911Z"), tz: "UTC"},
|
||||
{_id: 1, date: new ISODate("2017-07-04T14:56:42.911Z"), tz: "Europe/London"},
|
||||
{_id: 2, date: new ISODate("2017-07-04T14:56:42.911Z"), tz: "America/New_York"},
|
||||
@ -15,7 +16,8 @@ assert.commandWorked(coll.insert([
|
||||
{_id: 4, date: new ISODate("2017-07-04T14:56:42.911Z"), tz: "Asia/Kathmandu"},
|
||||
{_id: 5, date: new ISODate("1935-07-10T11:36:37.133Z"), tz: "Europe/Amsterdam"},
|
||||
{_id: 6, date: new ISODate("1900-07-10T11:41:22.418Z"), tz: "America/Caracas"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -27,30 +29,34 @@ assert.eq(
|
||||
{_id: 5, date: "1935-07-10 12:56:09 +0119 (79 minutes)"},
|
||||
{_id: 6, date: "1900-07-10 07:13:42 -0427 (-267 minutes)"},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
format: "%Y-%m-%d %H:%M:%S %z (%Z minutes)",
|
||||
date: "$date",
|
||||
timezone: "$tz"
|
||||
}
|
||||
}
|
||||
}
|
||||
timezone: "$tz",
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-01-04T15:08:51.911Z")},
|
||||
{_id: 1, date: new ISODate("2017-07-04T15:09:12.911Z")},
|
||||
{_id: 2, date: new ISODate("2017-12-04T15:09:14.911Z")},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -58,30 +64,34 @@ assert.eq(
|
||||
{_id: 1, date: "2017-07-04 11:09:12 -0400 (-240 minutes)"},
|
||||
{_id: 2, date: "2017-12-04 10:09:14 -0500 (-300 minutes)"},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
format: "%Y-%m-%d %H:%M:%S %z (%Z minutes)",
|
||||
date: "$date",
|
||||
timezone: "America/New_York"
|
||||
}
|
||||
}
|
||||
}
|
||||
timezone: "America/New_York",
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-01-04T15:08:51.911Z")},
|
||||
{_id: 1, date: new ISODate("2017-07-04T15:09:12.911Z")},
|
||||
{_id: 2, date: new ISODate("2017-12-04T15:09:14.911Z")},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -89,26 +99,30 @@ assert.eq(
|
||||
{_id: 1, date: "2017-07-04 15:09:12 +0000 (0 minutes)"},
|
||||
{_id: 2, date: "2017-12-04 15:09:14 +0000 (0 minutes)"},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {format: "%Y-%m-%d %H:%M:%S %z (%Z minutes)", date: "$date"}
|
||||
}
|
||||
}
|
||||
$dateToString: {format: "%Y-%m-%d %H:%M:%S %z (%Z minutes)", date: "$date"},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-01-01T15:08:51.911Z")},
|
||||
{_id: 1, date: new ISODate("2017-07-04T15:09:12.911Z")},
|
||||
{_id: 2, date: new ISODate("2017-12-04T15:09:14.911Z")},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -116,26 +130,30 @@ assert.eq(
|
||||
{_id: 1, date: "Natural: 2017-W3-27, ISO: 2017-W2-27"},
|
||||
{_id: 2, date: "Natural: 2017-W2-49, ISO: 2017-W1-49"},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {format: "Natural: %Y-W%w-%U, ISO: %G-W%u-%V", date: "$date"}
|
||||
}
|
||||
}
|
||||
$dateToString: {format: "Natural: %Y-W%w-%U, ISO: %G-W%u-%V", date: "$date"},
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-01-01T15:08:51.911Z")},
|
||||
{_id: 1, date: new ISODate("2017-07-04T15:09:12.911Z")},
|
||||
{_id: 2, date: new ISODate("2017-12-04T15:09:14.911Z")},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -143,23 +161,24 @@ assert.eq(
|
||||
{_id: 1, date: "Jul (July) 04, 2017"},
|
||||
{_id: 2, date: "Dec (December) 04, 2017"},
|
||||
],
|
||||
coll.aggregate([
|
||||
{$project: {date: {$dateToString: {format: "%b (%B) %d, %Y", date: "$date"}}}},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray());
|
||||
coll
|
||||
.aggregate([{$project: {date: {$dateToString: {format: "%b (%B) %d, %Y", date: "$date"}}}}, {$sort: {_id: 1}}])
|
||||
.toArray(),
|
||||
);
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Test that missing expressions, turn into BSON null values */
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-01-04T15:08:51.911Z")},
|
||||
{_id: 1, date: new ISODate("2017-01-04T15:08:51.911Z"), timezone: null},
|
||||
{_id: 2, date: new ISODate("2017-01-04T15:08:51.911Z"), timezone: undefined},
|
||||
{_id: 3, timezone: "Europe/Oslo"},
|
||||
{_id: 4, date: null, timezone: "Europe/Oslo"},
|
||||
{_id: 5, date: undefined, timezone: "Europe/Oslo"},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
assert.eq(
|
||||
[
|
||||
@ -170,21 +189,23 @@ assert.eq(
|
||||
{_id: 4, date: null},
|
||||
{_id: 5, date: null},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
format: "%Y-%m-%d %H:%M:%S %z (%Z minutes)",
|
||||
date: "$date",
|
||||
timezone: "$timezone"
|
||||
}
|
||||
}
|
||||
}
|
||||
timezone: "$timezone",
|
||||
},
|
||||
{$sort: {_id: 1}}
|
||||
},
|
||||
},
|
||||
},
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Test that the default format is
|
||||
@ -194,11 +215,13 @@ assert.eq(
|
||||
*/
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insert([
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{_id: 0, date: new ISODate("2017-01-04T15:08:51.911Z")},
|
||||
{_id: 1, date: new ISODate("2017-07-04T15:09:12.911Z")},
|
||||
{_id: 2, date: new ISODate("2017-12-04T15:09:14.911Z")},
|
||||
]));
|
||||
]),
|
||||
);
|
||||
|
||||
// No timezone specified. Defaults to UTC time, and the format includes the 'Z' (UTC) suffix.
|
||||
assert.eq(
|
||||
@ -207,8 +230,8 @@ assert.eq(
|
||||
{_id: 1, date: "2017-07-04T15:09:12.911Z"},
|
||||
{_id: 2, date: "2017-12-04T15:09:14.911Z"},
|
||||
],
|
||||
coll.aggregate([{$project: {date: {$dateToString: {date: "$date"}}}}, {$sort: {_id: 1}}])
|
||||
.toArray());
|
||||
coll.aggregate([{$project: {date: {$dateToString: {date: "$date"}}}}, {$sort: {_id: 1}}]).toArray(),
|
||||
);
|
||||
|
||||
// UTC timezone explicitly specified. Gives UTC time, and the format includes the 'Z' (UTC) suffix.
|
||||
assert.eq(
|
||||
@ -217,11 +240,10 @@ assert.eq(
|
||||
{_id: 1, date: "2017-07-04T15:09:12.911Z"},
|
||||
{_id: 2, date: "2017-12-04T15:09:14.911Z"},
|
||||
],
|
||||
coll.aggregate([
|
||||
{$project: {date: {$dateToString: {date: "$date", timezone: "UTC"}}}},
|
||||
{$sort: {_id: 1}}
|
||||
])
|
||||
.toArray());
|
||||
coll
|
||||
.aggregate([{$project: {date: {$dateToString: {date: "$date", timezone: "UTC"}}}}, {$sort: {_id: 1}}])
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Non-UTC timezone explicitly specified. Gives the requested time, and the format omits 'Z'.
|
||||
assert.eq(
|
||||
@ -230,75 +252,86 @@ assert.eq(
|
||||
{_id: 1, date: "2017-07-04T11:09:12.911"},
|
||||
{_id: 2, date: "2017-12-04T10:09:14.911"},
|
||||
],
|
||||
coll.aggregate([
|
||||
coll
|
||||
.aggregate([
|
||||
{$project: {date: {$dateToString: {date: "$date", timezone: "America/New_York"}}}},
|
||||
{$sort: {_id: 1}}
|
||||
{$sort: {_id: 1}},
|
||||
])
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
/* Test that null is returned when 'format' evaluates to nullish. */
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({_id: 0}));
|
||||
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
date: new ISODate("2017-01-04T15:08:51.911Z"),
|
||||
format: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
.toArray(),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
date: new ISODate("2017-01-04T15:08:51.911Z"),
|
||||
format: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
.toArray(),
|
||||
);
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
date: new ISODate("2017-01-04T15:08:51.911Z"),
|
||||
format: "$missing",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
/* --------------------------------------------------------------------------------------- */
|
||||
|
||||
let pipeline = [
|
||||
{$project: {date: {$dateToString: {date: new ISODate("2017-01-04T15:08:51.911Z"), format: 5}}}}
|
||||
];
|
||||
let pipeline = [{$project: {date: {$dateToString: {date: new ISODate("2017-01-04T15:08:51.911Z"), format: 5}}}}];
|
||||
let res = coll.runCommand("aggregate", {pipeline: pipeline, cursor: {}});
|
||||
assert.commandFailedWithCode(res, 18533);
|
||||
|
||||
pipeline = [{$project: {date: {$dateToString: {format: "%Y-%m-%d %H:%M:%S", timezone: "$tz"}}}}];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 18628, "Missing 'date' parameter to $dateToString");
|
||||
|
||||
pipeline = [{
|
||||
pipeline = [
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
date: new ISODate("2017-01-04T15:08:51.911Z"),
|
||||
format: "%Y-%m-%d %H:%M:%S",
|
||||
timezone: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
timezone: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
res = coll.runCommand("aggregate", {pipeline: pipeline, cursor: {}});
|
||||
assert.commandFailedWithCode(res, 40517);
|
||||
|
||||
@ -306,29 +339,34 @@ pipeline = [{$project: {date: {$dateToString: {format: "%Y-%m-%d %H:%M:%S", date
|
||||
res = coll.runCommand("aggregate", {pipeline: pipeline, cursor: {}});
|
||||
assert.commandFailedWithCode(res, 16006);
|
||||
|
||||
pipeline = [{
|
||||
pipeline = [
|
||||
{
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
date: new ISODate("2017-01-04T15:08:51.911Z"),
|
||||
format: "%Y-%m-%d %H:%M:%S",
|
||||
timezone: "DoesNotExist"
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
timezone: "DoesNotExist",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
res = coll.runCommand("aggregate", {pipeline: pipeline, cursor: {}});
|
||||
assert.commandFailedWithCode(res, 40485);
|
||||
|
||||
pipeline = [{
|
||||
$project: {date: {$dateToString: {date: new ISODate("2017-01-04T15:08:51.911Z"), format: "%"}}}
|
||||
}];
|
||||
pipeline = [
|
||||
{
|
||||
$project: {date: {$dateToString: {date: new ISODate("2017-01-04T15:08:51.911Z"), format: "%"}}},
|
||||
},
|
||||
];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 18535, "Unmatched '%' at end of format string");
|
||||
|
||||
// Fails for unknown format specifier.
|
||||
pipeline = [{
|
||||
$project: {date: {$dateToString: {date: new ISODate("2017-01-04T15:08:51.911Z"), format: "%n"}}}
|
||||
}];
|
||||
assertErrCodeAndErrMsgContains(
|
||||
coll, pipeline, 18536, "Invalid format character '%n' in format string");
|
||||
pipeline = [
|
||||
{
|
||||
$project: {date: {$dateToString: {date: new ISODate("2017-01-04T15:08:51.911Z"), format: "%n"}}},
|
||||
},
|
||||
];
|
||||
assertErrCodeAndErrMsgContains(coll, pipeline, 18536, "Invalid format character '%n' in format string");
|
||||
|
||||
@ -10,10 +10,8 @@ function testFormat(date, formatStr, expectedStr) {
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({date: date}));
|
||||
|
||||
const res =
|
||||
coll.aggregate([
|
||||
{$project: {_id: 0, formatted: {$dateToString: {format: formatStr, date: "$date"}}}}
|
||||
])
|
||||
const res = coll
|
||||
.aggregate([{$project: {_id: 0, formatted: {$dateToString: {format: formatStr, date: "$date"}}}}])
|
||||
.toArray();
|
||||
|
||||
assert.eq(res[0].formatted, expectedStr);
|
||||
@ -32,14 +30,16 @@ function testDateValueError(dateVal, errCode) {
|
||||
coll.drop();
|
||||
assert.commandWorked(coll.insert({date: dateVal}));
|
||||
|
||||
assertErrorCode(
|
||||
coll, {$project: {formatted: {$dateToString: {format: "%Y", date: "$date"}}}}, errCode);
|
||||
assertErrorCode(coll, {$project: {formatted: {$dateToString: {format: "%Y", date: "$date"}}}}, errCode);
|
||||
}
|
||||
|
||||
const now = ISODate();
|
||||
|
||||
// Use all modifiers we can test with js provided function
|
||||
testFormat(now, "%%-%Y-%m-%d-%H-%M-%S-%L", [
|
||||
testFormat(
|
||||
now,
|
||||
"%%-%Y-%m-%d-%H-%M-%S-%L",
|
||||
[
|
||||
"%",
|
||||
now.getUTCFullYear().zeroPad(4),
|
||||
(now.getUTCMonth() + 1).zeroPad(2),
|
||||
@ -47,8 +47,9 @@ testFormat(now, "%%-%Y-%m-%d-%H-%M-%S-%L", [
|
||||
now.getUTCHours().zeroPad(2),
|
||||
now.getUTCMinutes().zeroPad(2),
|
||||
now.getUTCSeconds().zeroPad(2),
|
||||
now.getUTCMilliseconds().zeroPad(3)
|
||||
].join("-"));
|
||||
now.getUTCMilliseconds().zeroPad(3),
|
||||
].join("-"),
|
||||
);
|
||||
|
||||
// Padding tests
|
||||
const padme = ISODate("2001-02-03T04:05:06.007Z");
|
||||
@ -63,7 +64,10 @@ testFormat(padme, "%S", padme.getUTCSeconds().zeroPad(2));
|
||||
testFormat(padme, "%L", padme.getUTCMilliseconds().zeroPad(3));
|
||||
|
||||
// no space and multiple characters between modifiers
|
||||
testFormat(now, "%d%d***%d***%d**%d*%d", [
|
||||
testFormat(
|
||||
now,
|
||||
"%d%d***%d***%d**%d*%d",
|
||||
[
|
||||
now.getUTCDate().zeroPad(2),
|
||||
now.getUTCDate().zeroPad(2),
|
||||
"***",
|
||||
@ -73,11 +77,12 @@ testFormat(now, "%d%d***%d***%d**%d*%d", [
|
||||
"**",
|
||||
now.getUTCDate().zeroPad(2),
|
||||
"*",
|
||||
now.getUTCDate().zeroPad(2)
|
||||
].join(""));
|
||||
now.getUTCDate().zeroPad(2),
|
||||
].join(""),
|
||||
);
|
||||
|
||||
// JS doesn't have equivalents of these format specifiers
|
||||
testFormat(ISODate('1999-01-02 03:04:05.006Z'), "%U-%w-%j", "00-7-002");
|
||||
testFormat(ISODate("1999-01-02 03:04:05.006Z"), "%U-%w-%j", "00-7-002");
|
||||
|
||||
// Missing date
|
||||
testFormatError({format: "%Y"}, 18628);
|
||||
|
||||
@ -11,58 +11,65 @@ for (let nullishValue of [null, undefined, "$missing"]) {
|
||||
// Test that the 'onNull' value is returned when the 'date' is nullish.
|
||||
assert.eq(
|
||||
[{_id: 0, date: onNullValue}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString:
|
||||
{date: nullishValue, format: "%Y-%m-%d %H:%M:%S", onNull: onNullValue}
|
||||
}
|
||||
}
|
||||
$dateToString: {date: nullishValue, format: "%Y-%m-%d %H:%M:%S", onNull: onNullValue},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
// Test that null is returned when the 'timezone' is nullish, regardless of the 'onNull'
|
||||
// value.
|
||||
assert.eq([{_id: 0, date: null}],
|
||||
coll.aggregate({
|
||||
assert.eq(
|
||||
[{_id: 0, date: null}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString: {
|
||||
date: "2018-02-06T11:56:02Z",
|
||||
format: "%Y-%m-%d %H:%M:%S",
|
||||
timezone: nullishValue,
|
||||
onNull: onNullValue
|
||||
}
|
||||
}
|
||||
}
|
||||
onNull: onNullValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that 'onNull' can be any type, not just an ISODate.
|
||||
for (let onNullValue of [{}, 5, "Not a date", null, undefined]) {
|
||||
assert.eq(
|
||||
[{_id: 0, date: onNullValue}],
|
||||
coll.aggregate({
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString:
|
||||
{date: "$missing", format: "%Y-%m-%d %H:%M:%S", onNull: onNullValue}
|
||||
}
|
||||
}
|
||||
$dateToString: {date: "$missing", format: "%Y-%m-%d %H:%M:%S", onNull: onNullValue},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that 'onNull' can be missing, resulting in no output field when used within a $project
|
||||
// stage.
|
||||
assert.eq([{_id: 0}],
|
||||
coll.aggregate({
|
||||
assert.eq(
|
||||
[{_id: 0}],
|
||||
coll
|
||||
.aggregate({
|
||||
$project: {
|
||||
date: {
|
||||
$dateToString:
|
||||
{date: "$missing", format: "%Y-%m-%d %H:%M:%S", onNull: "$missing"}
|
||||
}
|
||||
}
|
||||
$dateToString: {date: "$missing", format: "%Y-%m-%d %H:%M:%S", onNull: "$missing"},
|
||||
},
|
||||
},
|
||||
})
|
||||
.toArray());
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user