diff options
| author | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
|---|---|---|
| committer | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
| commit | 4cb8841196d0625dfa3825aa326f071cd27c7b8b (patch) | |
| tree | 1682a647d4463397c119183369ae6f750d5fdcff /jstests/aggregation | |
| parent | aa03c6362cbaa767638e6eed9b031d86dd2643d1 (diff) | |
| parent | 8f0827553e09872941945a093b647a4211a9db7f (diff) | |
Update upstream source from tag 'upstream/6.0.0'master
Update to upstream version '6.0.0'
with Debian dir 5604a80ec1c96ca76f25f40d78e6ef855abec322
Diffstat (limited to 'jstests/aggregation')
54 files changed, 480 insertions, 2440 deletions
diff --git a/jstests/aggregation/accumulators/accumulator_js.js b/jstests/aggregation/accumulators/accumulator_js.js index 16e9a62116a..3a389851e24 100644 --- a/jstests/aggregation/accumulators/accumulator_js.js +++ b/jstests/aggregation/accumulators/accumulator_js.js @@ -192,9 +192,8 @@ command.pipeline = [{ } } }]; -// ErrorCodes.TypeMismatch means "$add only supports numeric or date types". Code 16554 represented -// a type mismatch before 6.0 for this specific check. -assert.commandFailedWithCode(db.runCommand(command), [16554, ErrorCodes.TypeMismatch]); +// 16554 means "$add only supports numeric or date types" +assert.commandFailedWithCode(db.runCommand(command), 16554); // Test that initArgs can have a different length per group. assert(db.accumulator_js.drop()); diff --git a/jstests/aggregation/accumulators/top_bottom_top_n_bottom_n.js b/jstests/aggregation/accumulators/top_bottom_top_n_bottom_n.js index 2556850752a..d0844a4acac 100644 --- a/jstests/aggregation/accumulators/top_bottom_top_n_bottom_n.js +++ b/jstests/aggregation/accumulators/top_bottom_top_n_bottom_n.js @@ -392,17 +392,4 @@ const testOperatorText = (op) => { // most relevant results first. testOperatorText("$bottomN"); testOperatorText("$topN"); - -// Test constant output and sortBy. -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: {}}}}}]) - .toArray(); - assert.eq(results.length, 1, results); - assert.docEq(results[0], {_id: null, result: ["abc", "abc", "abc"]}, results); -}; -testConstantOutputAndSort("$topN"); -testConstantOutputAndSort("$bottomN"); })(); diff --git a/jstests/aggregation/add_with_date.js b/jstests/aggregation/add_with_date.js index 84335e0ecc1..8e3cb8597b5 100644 --- a/jstests/aggregation/add_with_date.js +++ b/jstests/aggregation/add_with_date.js @@ -1,19 +1,12 @@ -// @tags: [do_not_wrap_aggregations_in_facets,] (function() { "use strict"; -load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs. -// TODO BACKPORT-19405, BACKPORT-19406: SBE and classic have different behaviors documented in this -// test. These are unified by the SERVER tickets of the mentioned backports. -load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. const coll = db.getSiblingDB(jsTestName()).coll; coll.drop(); -const isSbeEnabled = checkSBEEnabled(db, ["featureFlagSbeFull"]); - function getResultOfExpression(expr) { const resultArray = coll.aggregate({$project: {computed: expr}}).toArray(); - assert.eq(1, resultArray.length, "ERROR from " + tojson(expr)); + assert.eq(1, resultArray.length); return resultArray[0].computed; } @@ -68,24 +61,13 @@ assert.eq(ISODate("2019-01-30T07:30:10.957Z"), getResultOfExpression({$add: ["$int32Val", "$dateVal"]})); // Addition with a date and multiple values of differing data types. -// SBE uses doubleDoubleSum for $add whereas classic does not. -if (isSbeEnabled) { - assert.eq( - ISODate("2019-01-30T07:30:12.596Z"), - getResultOfExpression({$add: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]})); - assert.eq( - ISODate("2019-01-30T07:30:12.596Z"), - getResultOfExpression({$add: ["$decimalVal", "$dateVal", "$doubleVal", "$int64Val"]})); -} else { - 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: ["$dateVal", "$decimalVal", "$doubleVal", "$int64Val"]})); +assert.eq(ISODate("2019-01-30T07:30:12.596Z"), + 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. @@ -103,27 +85,13 @@ assert.throwsWithCode( () => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDouble"]}), ErrorCodes.Overflow); -// An overflow into the domain of Decimal128 results in an overflow exception. -// SBE overflows to return nan, whereas classic throws. -if (isSbeEnabled) { - // One quirk of date addition semantics is that an overflow into the domain of Decimal128 is not - // fatal and instead results in an invalid "NaN" Date value. - const nanDate = new Date(""); - assert.eq(nanDate, getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]})); - assert.eq(nanDate, - getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDecimal"]})); - assert.eq(nanDate, - getResultOfExpression({$add: ["$dateVal", "$overflowDouble", "$overflowDecimal"]})); -} else { - assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]}), - ErrorCodes.Overflow); - assert.throwsWithCode( - () => getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDecimal"]}), - ErrorCodes.Overflow); - assert.throwsWithCode( - () => getResultOfExpression({$add: ["$dateVal", "$overflowDouble", "$overflowDecimal"]}), - ErrorCodes.Overflow); -} +// One quirk of date addition semantics is that an overflow into the domain of Decimal128 is not +// fatal and instead results in an invalid "NaN" Date value. +const nanDate = new Date(""); +assert.eq(nanDate, getResultOfExpression({$add: ["$dateVal", "$overflowDecimal"]})); +assert.eq(nanDate, + getResultOfExpression({$add: ["$dateVal", "$overflowDouble", "$overflowDecimal"]})); +assert.eq(nanDate, getResultOfExpression({$add: ["$int64Val", "$dateVal", "$overflowDecimal"]})); // Adding a double-typed NaN to a date value. assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDouble"]}), @@ -132,43 +100,14 @@ assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDoubl assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDouble", "$dateVal"]}), ErrorCodes.Overflow); -// An NaN Decimal128 added to date results in an overflow exception. -// SBE overflows to return NaN, whereas classic throws. -if (isSbeEnabled) { - const nanDate = new Date(""); - assert.eq(nanDate, getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]})); - assert.eq(nanDate, getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]})); -} else { - assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]}), - ErrorCodes.Overflow); - assert.throwsWithCode(() => getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]}), - ErrorCodes.Overflow); -} +// Adding a Decimal128-typed NaN to a date value. +assert.eq(nanDate, getResultOfExpression({$add: ["$dateVal", "$nanDecimal"]})); +assert.eq(nanDate, getResultOfExpression({$add: ["$nanDecimal", "$dateVal"]})); + // Addition with a date, a double-typed NaN, and a third value. assert.throwsWithCode(() => getResultOfExpression({$add: ["$dateVal", "$doubleVal", "$nanDouble"]}), ErrorCodes.Overflow); // Addition with a date, and both types of NaN. -// SBE returns NaN if it's a value in $add, whereas classic throws. -if (isSbeEnabled) { - const nanDate = new Date(""); - assert.eq(nanDate, getResultOfExpression({$add: ["$dateVal", "$nanDouble", "$nanDecimal"]})); -} else { - assert.throwsWithCode( - () => getResultOfExpression({$add: ["$dateVal", "$nanDouble", "$nanDecimal"]}), - 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(nanDate, getResultOfExpression({$add: ["$dateVal", "$nanDouble", "$nanDecimal"]})); }()); diff --git a/jstests/aggregation/api_version_stage_allowance_checks.js b/jstests/aggregation/api_version_stage_allowance_checks.js index 0c3e404842e..f47b27fd630 100644 --- a/jstests/aggregation/api_version_stage_allowance_checks.js +++ b/jstests/aggregation/api_version_stage_allowance_checks.js @@ -43,6 +43,7 @@ let result = curDB.runCommand({ remotes: [], nss: "test.mergeCursors", allowPartialResults: false, + recordRemoteOpWaitTime: false } }], cursor: {}, @@ -63,6 +64,7 @@ result = testDB.runCommand({ remotes: [], nss: "test.mergeCursors", allowPartialResults: false, + recordRemoteOpWaitTime: false } }], cursor: {}, @@ -83,6 +85,7 @@ result = testDB.runCommand({ remotes: [], nss: "test.mergeCursors", allowPartialResults: false, + recordRemoteOpWaitTime: false } }], cursor: {}, diff --git a/jstests/aggregation/bugs/empty_field_names.js b/jstests/aggregation/bugs/empty_field_names.js deleted file mode 100644 index 04429027e51..00000000000 --- a/jstests/aggregation/bugs/empty_field_names.js +++ /dev/null @@ -1,19 +0,0 @@ -// Testing documents that contain empty field names. This was written as part of -// SERVER-86619. -db.emptyFields.drop(); -db.createCollection('emptyFields'); - -const kNumDocs = 50; - -for (let i = 0; i < 50; ++i) { - db.emptyFields.insert({"": 123, "b": 456, sortField: i}); -} - -assert.eq(db.emptyFields - .aggregate([ - {$sort: {sortField: 1}}, - {$addFields: {"m": {$meta: "sortKey"}}}, - {$match: {"b": 456}} - ]) - .itcount(), - kNumDocs); diff --git a/jstests/aggregation/bugs/groupMissing.js b/jstests/aggregation/bugs/groupMissing.js index ba6b0f58fff..f13477a90b6 100644 --- a/jstests/aggregation/bugs/groupMissing.js +++ b/jstests/aggregation/bugs/groupMissing.js @@ -7,23 +7,23 @@ // @tags: [ // do_not_wrap_aggregations_in_facets, // ] -load('jstests/aggregation/extras/utils.js'); // For assertArrayEq. +load('jstests/aggregation/extras/utils.js'); // For resultsEq. (function() { "use strict"; -const coll = db.getCollection(jsTestName()); +var coll = db.groupMissing; coll.drop(); -assert.commandWorked(coll.insert({a: null})); -assert.commandWorked(coll.insert({})); +coll.insert({a: null}); +coll.insert({}); -let res = coll.aggregate({$group: {_id: "$a"}}); -let arr = res.toArray(); +var res = coll.aggregate({$group: {_id: "$a"}}); +var arr = res.toArray(); assert.eq(arr.length, 1); assert.eq(arr[0]._id, null); -assert.commandWorked(coll.createIndex({a: 1})); +coll.createIndex({a: 1}); res = coll.aggregate({$sort: {a: 1}}, {$group: {_id: "$a"}}); arr = res.toArray(); assert.eq(arr.length, 1); @@ -31,49 +31,41 @@ assert.eq(arr[0]._id, null); coll.drop(); -assert.commandWorked(coll.insert({a: null})); -assert.commandWorked(coll.insert({})); +coll.insert({a: null}); +coll.insert({}); // Bug, see SERVER-21992. res = coll.aggregate({$group: {_id: {a: "$a"}}}); -assertArrayEq({actual: res.toArray(), expected: [{_id: {a: null}}]}); +assert(resultsEq(res.toArray(), [{_id: {a: null}}])); // Bug, see SERVER-21992. -assert.commandWorked(coll.createIndex({a: 1})); +coll.createIndex({a: 1}); res = coll.aggregate({$group: {_id: {a: "$a"}}}); -assertArrayEq({actual: res.toArray(), expected: [{_id: {a: null}}]}); +assert(resultsEq(res.toArray(), [{_id: {a: null}}])); // Correct behavior after SERVER-21992 is fixed. if (0) { res = coll.aggregate({$group: {_id: {a: "$a"}}}); - assertArrayEq({actual: res.toArray(), expected: [{_id: {a: null}}, {_id: {}}]}); + assert(resultsEq(res.toArray(), [{_id: {a: null}}, {_id: {}}])); } coll.drop(); -assert.commandWorked(coll.insert({a: null, b: 1})); -assert.commandWorked(coll.insert({b: 1})); -assert.commandWorked(coll.insert({a: null, b: 1})); +coll.insert({a: null, b: 1}); +coll.insert({b: 1}); +coll.insert({a: null, b: 1}); res = coll.aggregate({$group: {_id: {a: "$a", b: "$b"}}}); -assertArrayEq({actual: res.toArray(), expected: [{_id: {b: 1}}, {_id: {a: null, b: 1}}]}); +assert(resultsEq(res.toArray(), [{_id: {b: 1}}, {_id: {a: null, b: 1}}])); -assert.commandWorked(coll.createIndex({a: 1, b: 1})); -res = coll.aggregate([{$group: {_id: {a: "$a", b: "$b"}}}, {$sort: {"_id.a": 1, "_id.b": 1}}]); -// Before fixing SERVER-23229 we were getting [{_id: {a: null, b: 1}}] -assertArrayEq({actual: res.toArray(), expected: [{_id: {b: 1}}, {_id: {a: null, b: 1}}]}); +// Bug, see SERVER-23229. +coll.createIndex({a: 1, b: 1}); +res = coll.aggregate({$sort: {a: 1, b: 1}}, {$group: {_id: {a: "$a", b: "$b"}}}); +assert(resultsEq(res.toArray(), [{_id: {a: null, b: 1}}])); -// Try another variation of the query that is taken more directly from the bug report SERVER-23229. -coll.drop(); -assert.commandWorked(coll.insert({a: 1, b: null})); -assert.commandWorked(coll.insert({a: null, b: 1})); -assert.commandWorked(coll.insert({b: 1})); -assert.commandWorked(coll.insert({a: 1})); - -let preSortResult = - coll.aggregate({$sort: {a: 1, b: 1}}, {$group: {_id: {a: "$a", b: "$b"}}}).toArray(); -assert.commandWorked(coll.createIndex({a: 1, b: 1})); -assertArrayEq({ - actual: preSortResult, - expected: coll.aggregate({$group: {_id: {a: "$a", b: "$b"}}}).toArray() -}); +// Correct behavior after SERVER-23229 is fixed. +if (0) { + coll.createIndex({a: 1, b: 1}); + res = coll.aggregate({$sort: {a: 1, b: 1}}, {$group: {_id: {a: "$a", b: "$b"}}}); + assert(resultsEq(res.toArray(), [{_id: {b: 1}}, {_id: {a: null, b: 1}}])); +} }()); diff --git a/jstests/aggregation/bugs/hash_lookup_spill_large_and_small_documents_correctly.js b/jstests/aggregation/bugs/hash_lookup_spill_large_and_small_documents_correctly.js deleted file mode 100644 index de8f79bbea7..00000000000 --- a/jstests/aggregation/bugs/hash_lookup_spill_large_and_small_documents_correctly.js +++ /dev/null @@ -1,74 +0,0 @@ -// Regression test to check that different document sizes work correctly with $lookup. -// @tags: [ -// requires_fcv_71, -// ] -(function() { -'use strict'; - -load('jstests/libs/fixture_helpers.js'); // For 'FixtureHelpers' - -const localColl = db.lookup_spill_local; -const foreignColl = db.lookup_spill_foreign; -localColl.drop(); -foreignColl.drop(); - -const memoryLimit = 128; // Spill at 128 bytes - -function setHashLookupMemoryLimit(memoryLimit) { - const commandResArr = FixtureHelpers.runCommandOnEachPrimary({ - db: db.getSiblingDB("admin"), - cmdObj: { - setParameter: 1, - internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: memoryLimit, - } - }); - assert.gt(commandResArr.length, 0, "Setting memory limit on primaries failed"); - assert.commandWorked(commandResArr[0]); -} - -function runHashLookupSpill() { - const smallStr = "small"; - const bigStr = Array(memoryLimit).toString(); - const localDoc = {_id: 1, a: 2}; - const foreignDocs = [ - {_id: 0, b: 1, padding: smallStr}, - {_id: 1, b: 2, padding: bigStr}, - {_id: 2, b: 1, padding: smallStr}, - {_id: 3, b: 2, padding: bigStr}, - {_id: 4, b: 1, padding: smallStr}, - {_id: 5, b: 2, padding: bigStr}, - {_id: 6, b: 1, padding: smallStr}, - {_id: 7, b: 2, padding: bigStr}, - {_id: 8, b: 1, padding: smallStr}, - ]; - - assert.commandWorked(localColl.insert(localDoc)); - assert.commandWorked(foreignColl.insertMany(foreignDocs)); - const pipeline = [ - {$lookup: {from: foreignColl.getName(), localField: "a", foreignField: "b", as: "matched"}}, - {$sort: {_id: 1}} - ]; - - const result = localColl.aggregate(pipeline).toArray(); - assert.eq(result.length, 1, result); - assert.eq(result[0].matched.length, 4, result); - for (let matched of result[0].matched) { - assert.eq(matched.padding, bigStr); - } -} - -const oldMemoryLimit = - assert - .commandWorked(db.adminCommand({ - getParameter: 1, - internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: 1 - })) - .internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill; - -try { - setHashLookupMemoryLimit(memoryLimit); - runHashLookupSpill(); -} finally { - setHashLookupMemoryLimit(oldMemoryLimit); -} -})(); diff --git a/jstests/aggregation/expressions/log_pow_exp.js b/jstests/aggregation/bugs/server18427.js index b74cf4fed93..a633632ab3d 100644 --- a/jstests/aggregation/expressions/log_pow_exp.js +++ b/jstests/aggregation/bugs/server18427.js @@ -10,29 +10,13 @@ var coll = db.log_exponential_expressions; coll.drop(); assert.commandWorked(coll.insert({_id: 0, a: 8, b: 2})); -const doubleE = 2.7182818284590452; -const decimalE = NumberDecimal("2.718281828459045235360287471352662"); -const decimal1overE = NumberDecimal("0.3678794411714423215955237701614609"); - -// Given a double, is it an integer? -function isInteger(n) { - return !n.toString().includes('.'); -} - -function isNumberDecimal(n) { - return n.toString().includes('NumberDecimal'); -} +var decimalE = NumberDecimal("2.718281828459045235360287471352662"); +var decimal1overE = NumberDecimal("0.3678794411714423215955237701614609"); // Helper for testing that op returns expResult. -function testOp(op, expResult, failMsg) { - const pipeline = [{$project: {_id: 0, result: op}}]; - const result = coll.aggregate(pipeline).toArray(); - assert.eq(result.length, 1); - if (expResult === null || isNaN(expResult) || isNumberDecimal(expResult)) { - assert.eq(result[0].result, expResult, failMsg); - } else { - assert.close(result[0].result, expResult, failMsg, 12 /*places*/); - } +function testOp(op, expResult) { + var pipeline = [{$project: {_id: 0, result: op}}]; + assert.eq(coll.aggregate(pipeline).toArray(), [{result: expResult}]); } // $log, $log10, $ln. @@ -42,180 +26,11 @@ function testOp(op, expResult, failMsg) { testOp({$log: [10, 10]}, 1); testOp({$log10: [10]}, 1); testOp({$ln: [Math.E]}, 1); - -// Different double and NumberDecimal inputs, verified manually. -const logTestCases = [ - // Base 8 - {input: 1, base: 8, doubleResult: 0, decResult: NumberDecimal("0E+33")}, - { - input: 2.5, - base: 8, - doubleResult: 0.4406426982957875, - decResult: NumberDecimal("0.4406426982957874492901064764964633") - }, - { - input: 7, - base: 8, - doubleResult: 0.9357849740192015, - decResult: NumberDecimal("0.9357849740192013691473231057439436") - }, - {input: 8, base: 8, doubleResult: 1, decResult: NumberDecimal("1")}, - { - input: 64, - base: 8, - doubleResult: 2, - decResult: NumberDecimal("2.000000000000000000000000000000000") - }, - { - input: 65, - base: 8, - doubleResult: 2.0074559376761516, - decResult: NumberDecimal("2.007455937676151502755710694582028") - }, - - // Base 9, a more unusual base. - {input: 1, base: 9, doubleResult: 0, decResult: NumberDecimal("0E+33")}, - { - input: 2.5, - base: 9, - doubleResult: 0.41702188357323483, - decResult: NumberDecimal("0.4170218835732348650487566466679398") - }, - { - input: 4, - base: 9, - doubleResult: 0.6309297535714574, - decResult: NumberDecimal("0.6309297535714574370995271143427609") - }, - {input: 9, base: 9, doubleResult: 1, decResult: NumberDecimal("1")}, - { - input: 10, - base: 9, - doubleResult: 1.0479516371446924, - decResult: NumberDecimal("1.047951637144692302148283761010701") - }, - {input: 81, base: 9, doubleResult: 2, decResult: NumberDecimal("2")}, - { - input: 82, - base: 9, - doubleResult: 2.0055843597957064, - decResult: NumberDecimal("2.005584359795706389324272570756155") - }, - - // Base 12.77, an even MORE unusual base. - {input: 1, base: 12.77, doubleResult: 0, decResult: NumberDecimal("0E+33")}, - { - input: 2.5, - base: 12.77, - doubleResult: 0.3597390013391846, - decResult: NumberDecimal("0.3597390013391846393309152124110717") - }, - {input: 12.77, base: 12.77, doubleResult: 1, decResult: NumberDecimal("1")}, - { - input: 13, - base: 12.77, - doubleResult: 1.0070082433896357, - decResult: NumberDecimal("1.007008243389635671677137269228113") - }, - { - input: 163.0729, - base: 12.77, - doubleResult: 2, - decResult: NumberDecimal("2.000000000000000000000000000000000") - }, - { - input: 170, - base: 12.77, - doubleResult: 2.016332738676606, - decResult: NumberDecimal("2.016332738676605709114981994718173") - }, -]; -for (const test of logTestCases) { - // If we can cast our input, base (or both) to integer types, test them as well. - const inputs = [test.input, NumberDecimal(test.input.toString())]; - if (isInteger(test.input)) { - inputs.push(NumberInt(test.input), NumberLong(test.input)); - } - const bases = [test.base, NumberDecimal(test.base.toString())]; - if (isInteger(test.base)) { - bases.push(NumberInt(test.base), NumberLong(test.base)); - } - for (const input of inputs) { - for (const base of bases) { - const hasDecimalInput = isNumberDecimal(input) || isNumberDecimal(base); - testOp( - {$log: [input, base]}, hasDecimalInput ? test.decResult : test.doubleResult, test); - } - } -} - -// Base 10, using $log10 -const log10TestCases = [ - {input: 1, doubleResult: 0, decResult: NumberDecimal("0")}, - { - input: 2.5, - doubleResult: 0.3979400086720376, - decResult: NumberDecimal("0.3979400086720376095725222105510140") - }, - {input: 10, doubleResult: 1, decResult: NumberDecimal("1")}, - { - input: 11, - doubleResult: 1.041392685158225, - decResult: NumberDecimal("1.041392685158225040750199971243024") - }, - {input: 100, doubleResult: 2, decResult: NumberDecimal("2")}, - { - input: 101, - doubleResult: 2.0043213737826426, - decResult: NumberDecimal("2.004321373782642574275188178222938") - }, -]; -for (const test of log10TestCases) { - // If the input is an integer anyway, test with our integer types as well. - if (isInteger(test.input)) { - testOp({$log10: NumberInt(test.input)}, test.doubleResult, test); - testOp({$log10: NumberLong(test.input)}, test.doubleResult, test); - } - testOp({$log10: test.input}, test.doubleResult, test); - testOp({$log10: NumberDecimal(test.input.toString())}, test.decResult, test); -} - -// Base `e`, using $ln. -const lnTestCases = [ - {input: 1, doubleResult: 0, decResult: NumberDecimal("0")}, - // `e` is about 2.7, so this should be close to 1. - { - input: 2.5, - doubleResult: 0.9162907318741551, - decResult: NumberDecimal("0.9162907318741550651835272117680110") - }, - { - input: 7, - doubleResult: 1.9459101490553132, - decResult: NumberDecimal("1.945910149055313305105352743443180") - }, - { - input: 10, - doubleResult: 2.302585092994046, - decResult: NumberDecimal("2.302585092994045684017991454684364") - }, -]; -for (const test of lnTestCases) { - if (isInteger(test.input)) { - testOp({$ln: NumberInt(test.input)}, test.doubleResult, test); - testOp({$ln: NumberLong(test.input)}, test.doubleResult, test); - } - testOp({$ln: test.input}, test.doubleResult, test); - testOp({$ln: NumberDecimal(test.input.toString())}, test.decResult, test); -} - -// We represent `e` differently with double and NumberDecimal, so test that here. -testOp({$ln: doubleE}, 1); -testOp({$ln: 1 / doubleE}, -1); -// The below answer is actually correct: the input is an approximation of E. -testOp({$ln: decimalE}, NumberDecimal("0.9999999999999999999999999999999998")); -testOp({$ln: decimal1overE}, NumberDecimal("-0.9999999999999999999999999999999998")); - +// - NumberDecimal +testOp({$log: [NumberDecimal("10"), NumberDecimal("10")]}, NumberDecimal("1")); +testOp({$log10: [NumberDecimal("10")]}, NumberDecimal("1")); +// The below answer is actually correct: the input is an approximation of E +testOp({$ln: [decimalE]}, NumberDecimal("0.9999999999999999999999999999999998")); // All types converted to doubles. testOp({$log: [NumberLong("10"), NumberLong("10")]}, 1); testOp({$log10: [NumberLong("10")]}, 1); diff --git a/jstests/aggregation/bugs/server6570.js b/jstests/aggregation/bugs/server6570.js index 1d0b6338b43..112feb49406 100644 --- a/jstests/aggregation/bugs/server6570.js +++ b/jstests/aggregation/bugs/server6570.js @@ -6,19 +6,10 @@ c = db.s6570; c.drop(); c.save({x: 17, y: "foo"}); -// 16554 was the code used instead of TypeMismatch before 6.0. -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]);
\ No newline at end of file +assertErrorCode(c, {$project: {string_fields: {$add: [3, "$y", 4, "$y"]}}}, 16554); +assertErrorCode(c, {$project: {number_fields: {$add: ["a", "$x", "b", "$x"]}}}, 16554); +assertErrorCode(c, {$project: {all_strings: {$add: ["c", "$y", "d", "$y"]}}}, 16554); +assertErrorCode(c, {$project: {potpourri_1: {$add: [5, "$y", "e", "$x"]}}}, 16554); +assertErrorCode(c, {$project: {potpourri_2: {$add: [6, "$x", "f", "$y"]}}}, 16554); +assertErrorCode(c, {$project: {potpourri_3: {$add: ["g", "$y", 7, "$x"]}}}, 16554); +assertErrorCode(c, {$project: {potpourri_4: {$add: ["h", "$x", 8, "$y"]}}}, 16554); diff --git a/jstests/aggregation/bugs/server72651.js b/jstests/aggregation/bugs/server72651.js deleted file mode 100644 index b4100bdc32c..00000000000 --- a/jstests/aggregation/bugs/server72651.js +++ /dev/null @@ -1,12 +0,0 @@ -// SERVER-72651 $match filter is erroneously pushed past $project into COLLSCAN -(function() { - -const c = db.server72651; - -c.drop(); -assert.commandWorked(c.insert({_id: 0, a: 1})); -// The bug caused the query below to return {"_id" : 0} instead of no documents. -assert.eq( - [], - c.aggregate([{$project: {"b": 1}}, {$match: {$expr: {$getField: {$literal: "a"}}}}]).toArray()); -})();
\ No newline at end of file diff --git a/jstests/aggregation/bugs/timeseries_should_not_push_match_before_project.js b/jstests/aggregation/bugs/timeseries_should_not_push_match_before_project.js deleted file mode 100644 index f2465988f0e..00000000000 --- a/jstests/aggregation/bugs/timeseries_should_not_push_match_before_project.js +++ /dev/null @@ -1,30 +0,0 @@ -// Regression test for SERVER-71270. -(function() { -"use strict"; -load('jstests/aggregation/extras/utils.js'); // For assertArrayEq. -const doc = { - _id: 0, - time: new Date('2019-01-18T13:24:15.443Z'), - tag: {}, -}; - -db.ts.drop(); -db.coll.drop(); - -db.createCollection('ts', {timeseries: {timeField: 'time', metaField: 'tag'}}); -db.createCollection('coll'); - -db.ts.insertOne(doc); -db.coll.insertOne(doc); -const pipeline = [ - {$project: {'time': 0}}, - {$match: {'time': {$lte: new Date('2019-02-13T11:36:03.481Z')}}}, -]; - -const ts = db.ts.aggregate(pipeline).toArray(); -const vanilla = db.coll.aggregate(pipeline).toArray(); -assertArrayEq({ - actual: ts, - expected: vanilla, -}); -}()); diff --git a/jstests/aggregation/bugs/window_inside_facet.js b/jstests/aggregation/bugs/window_inside_facet.js deleted file mode 100644 index baf9fcd5150..00000000000 --- a/jstests/aggregation/bugs/window_inside_facet.js +++ /dev/null @@ -1,77 +0,0 @@ -// Test that $setWindowFields inside $facet correctly propagates its state when it encounters paused -// execution. -(function() { -"use strict"; - -const coll = db.window_inside_facet; -coll.drop(); - -assert.commandWorked(coll.insert([ - {_id: 'a', n: 0}, - {_id: 'b', n: 1}, - {_id: 'c', n: 2}, - {_id: 'd', n: 3}, - {_id: 'e', n: 4}, - {_id: 'f', n: 5} -])); - -// Test window with a sort within $facet alongside another pipeline that will cause it to pause -// execution. The sort will cause the window to hit paused execution immediately, before an advance. -let result = - coll.aggregate({ - $facet: { - facet1: [{ - $setWindowFields: { - output: {prevId: {$shift: {by: -1, default: null, output: "$_id"}}}, - sortBy: {_id: 1} - } - }], - facet2: [{$count: "count"}] - } - }) - .toArray()[0]; -let expected = { - facet1: [ - {_id: 'a', n: 0, prevId: null}, - {_id: 'b', n: 1, prevId: 'a'}, - {_id: 'c', n: 2, prevId: 'b'}, - {_id: 'd', n: 3, prevId: 'c'}, - {_id: 'e', n: 4, prevId: 'd'}, - {_id: 'f', n: 5, prevId: 'e'} - ], - facet2: [{count: 6}] -}; -assert.docEq(expected, result, "$setWindowFields with sort failed."); - -// Test window with no sort within $facet alongside another pipeline that will cause it to pause -// execution. Having no sort will cause the window to hit paused execution after advancing. -result = coll.aggregate({ - $facet: { - facet1: [ - { - $setWindowFields: { - output: { - min: {$min: "$n"}, - max: {$max: "$n"}, - } - } - }, - {$sort: {_id: 1}} - ], - facet2: [{$count: "count"}] - } - }) - .toArray()[0]; -expected = { - facet1: [ - {_id: 'a', n: 0, min: 0, max: 5}, - {_id: 'b', n: 1, min: 0, max: 5}, - {_id: 'c', n: 2, min: 0, max: 5}, - {_id: 'd', n: 3, min: 0, max: 5}, - {_id: 'e', n: 4, min: 0, max: 5}, - {_id: 'f', n: 5, min: 0, max: 5} - ], - facet2: [{count: 6}] -}; -assert.docEq(expected, result, "$setWindowFields without sort failed."); -}()); diff --git a/jstests/aggregation/collection_uuid_coll_stats_index_stats.js b/jstests/aggregation/collection_uuid_coll_stats_index_stats.js index 67c8ca65113..5aa92524652 100644 --- a/jstests/aggregation/collection_uuid_coll_stats_index_stats.js +++ b/jstests/aggregation/collection_uuid_coll_stats_index_stats.js @@ -49,19 +49,10 @@ const testCommand = function(cmd, cmdObj) { jsTestLog("The command '" + cmd + "' fails when the provided UUID corresponds to a different collection, even if the " + "provided namespace does not exist."); - assert.commandWorked(testDB.runCommand({drop: coll2.getName()})); + coll2.drop(); res = assert.commandFailedWithCode(testDB.runCommand(cmdObj), ErrorCodes.CollectionUUIDMismatch); validateErrorResponse(res, testDB.getName(), uuid, coll2.getName(), coll.getName()); - assert(!testDB.getCollectionNames().includes(coll2.getName())); - - jsTestLog("The command '" + cmd + - "' fails with CollectionUUIDMismatch even if the database does not exist."); - const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); - cmdObj[cmd] = 'nonexistent'; - res = assert.commandFailedWithCode(nonexistentDB.runCommand(cmdObj), - ErrorCodes.CollectionUUIDMismatch); - validateErrorResponse(res, nonexistentDB.getName(), uuid, 'nonexistent', null); jsTestLog("The command '" + cmd + "' succeeds on view when no UUID is provided."); const viewName = "view"; diff --git a/jstests/aggregation/documents_merge.js b/jstests/aggregation/documents_merge.js deleted file mode 100644 index 99e9a8f1a41..00000000000 --- a/jstests/aggregation/documents_merge.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * This is the test for $documents stage along with $merge stage in an aggregation pipeline, - * including verifying the bug in SERVER-85892 is addressed when the spec 'whenMatched' is not - * empty. - * - * @tags: [ - * requires_fcv_80, - * assumes_against_mongod_not_mongos, # not yet supported until 7.2 with SERVER-65534 - * ] - */ - -load("jstests/aggregation/extras/merge_helpers.js"); // For withEachMergeMode and - // dropWithoutImplicitRecreate. - -const outColl = db[`${jsTest.name()}_out`]; -const outCollName = outColl.getName(); -const expectedTotalDocs = 100; - -function assertDocsInsertedCorrectly(docs, pipeline) { - const msg = `Failed with pipeline: ${JSON.stringify(pipeline, null, 2)}`; - - assert.eq(expectedTotalDocs, docs.length, msg); - for (let i = 0; i < expectedTotalDocs; i++) { - assert.eq(docs[i].x, i, msg); - } -} - -const documentsStage = { - $documents: {$map: {input: {$range: [0, expectedTotalDocs]}, in : {x: "$$this"}}} -}; - -function testFn(pipeline, assertFn) { - // Creates an index as $merge requires a unique index with the 'on' identifier field. Then - // inserts a document allowed to be matched. - dropWithoutImplicitRecreate(outCollName); - assert.commandWorked(outColl.createIndex({x: 1}, {unique: true})); - assert.commandWorked(outColl.insert({x: 10})); - - assert.doesNotThrow(() => db.aggregate(pipeline)); - let res = outColl.find({}, {_id: 0}).sort({x: 1}).toArray(); - assertDocsInsertedCorrectly(res, pipeline); - assertFn(res); -} - -{ // Tests $merge with non-empty pipeline along with let in whenMatched spec. - const pipeline = [ - documentsStage, - { - $merge: { - into: outCollName, - let : {num: 123}, - whenMatched: [{$set: {number: "$$num"}}], - on: "x" - } - } - ]; - - testFn(pipeline, res => { - assert.eq(res.filter(elem => elem.number === 123).length, 1); - }); -} - -{ // Tests $merge with non-empty pipeline in whenMatched spec. - const pipeline = [ - documentsStage, - {$merge: {into: outCollName, whenMatched: [{$set: {new: true}}], on: "x"}} - ]; - - testFn(pipeline, res => { - assert.eq(res.filter(elem => elem.new === true).length, 1); - }); -} - -// Tests each combination of merge modes. -withEachMergeMode(({whenMatchedMode, whenNotMatchedMode}) => { - const expectErrorCode = whenMatchedMode === "fail" - ? ErrorCodes.DuplicateKey - : whenNotMatchedMode === "fail" ? ErrorCodes.MergeStageNoMatchingDocument : null; - - // Creates an index as $merge requires a unique index with the 'on' identifier field. Then - // inserts a document allowed to be matched. - dropWithoutImplicitRecreate(outCollName); - assert.commandWorked(outColl.createIndex({x: 1}, {unique: true})); - assert.commandWorked(outColl.insert({x: 10, old: true})); - - const pipeline = [ - documentsStage, - { - $merge: { - into: outCollName, - whenMatched: whenMatchedMode, - whenNotMatched: whenNotMatchedMode, - on: "x", - } - } - ]; - - if (expectErrorCode) { - assert.throwsWithCode(() => db.aggregate(pipeline), expectErrorCode); - return; - } - - assert.doesNotThrow(() => db.aggregate(pipeline)); - let res = outColl.find({}, {_id: 0}).sort({x: 1}).toArray(); - if (whenNotMatchedMode == "discard") { - assert.eq(outColl.count(), 1); - } else { - assertDocsInsertedCorrectly(res, pipeline); - } - - // Asserts if the old document is replaced when 'whenMatchedMode' is "replace". - assert.eq(res.filter(elem => elem.old === true).length, whenMatchedMode == "replace" ? 0 : 1); -}); diff --git a/jstests/aggregation/expressions/add.js b/jstests/aggregation/expressions/add.js deleted file mode 100644 index 28fff6e33c7..00000000000 --- a/jstests/aggregation/expressions/add.js +++ /dev/null @@ -1,121 +0,0 @@ -(function() { -"use strict"; -load("jstests/aggregation/extras/utils.js"); // For assertErrorCode and assertErrMsgContains. -load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs. -// TODO BACKPORT-19405, BACKPORT-19406: SBE and classic have different behaviors documented in this -// test. These are unified by the SERVER tickets of the mentioned backports. -load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. - -// In SERVER-63012, translation of $add expression into sbe now defaults the translation of $add -// with no operands to a zero integer constant. -const coll = db.add_coll; -coll.drop(); - -const isSbeEnabled = checkSBEEnabled(db, ["featureFlagSbeFull"]); - -assert.commandWorked(coll.insert({x: 1})); -let result = coll.aggregate([{$project: {y: {$add: []}}}]).toArray(); -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 -]; -let doc = {_id: 0}; -let i = 0; -let queryArr = []; -arr.forEach(num => { - i++; - doc[`f_${i}`] = num; - queryArr.push(`$f_${i}`); -}); - -assert.eq(true, coll.drop()); -assert.commandWorked(coll.insert(doc)); - -let addResult = coll.aggregate([{$project: {add: {$add: queryArr}}}]).toArray(); -let sumResult = coll.aggregate([{$project: {sum: {$sum: queryArr}}}]).toArray(); - -// SBE uses doubleDoubleSum for $add and $sum, whereas classic uses it for only $sum. -if (isSbeEnabled) { - assert.eq(addResult[0]["add"], sumResult[0]["sum"]); -} else { - assert.neq(addResult[0]["add"], sumResult[0]["sum"]); - 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 -assert.commandWorked(coll.insert({_id: 0, lhs: new Date(1683794065002), rhs: 0.5})); -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")})); - -let result1 = - coll.aggregate([{$project: {sum: {$add: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}]).toArray(); - -// SBE uses doubleDoubleSum for $add whereas classic does not. -if (isSbeEnabled) { - assert.eq(result1[0].sum, new Date(1683794065002)); -} else { - assert.eq(result1[0].sum, new Date(1683794065003)); -} -assert.eq(result1[1].sum, new Date(1683794065003)); - -// SBE uses doubleDoubleSum for $add whereas classic does not. -if (isSbeEnabled) { - assert.eq(result1[2].sum, new Date(1683794065003)); -} else { - assert.eq(result1[2].sum, new Date(1683794065004)); -} -assert.eq(result1[3].sum, new Date(1683794065004)); -assert.eq(result1[4].sum, new Date(1683794065003)); -assert.eq(result1[5].sum, new Date(1683794065004)); -assert.eq(result1[6].sum, new Date(1683794065004)); -assert.eq(result1[7].sum, new Date(1683794065004)); - -coll.drop(); - -assert.commandWorked(coll.insert([{ - _id: 0, - veryBigPositiveLong: NumberLong("9223372036854775806"), - veryBigPositiveDouble: 9223372036854775806, - veryBigPositiveDecimal: NumberDecimal("9223372036854775806") -}])); - -let pipeline = [{$project: {res: {$add: [new Date(10), "$veryBigPositiveLong"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); - -pipeline = [{$project: {res: {$add: [new Date(10), "$veryBigPositiveDouble"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); - -pipeline = [{$project: {res: {$add: [new Date(10), "$veryBigPositiveDecimal"]}}}]; -if (isSbeEnabled) { - assert.commandWorked( - coll.getDB().runCommand({aggregate: coll.getName(), pipeline: pipeline, cursor: {}})); -} else { - assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); -} -}()); diff --git a/jstests/aggregation/expressions/collation_expressions.js b/jstests/aggregation/expressions/collation_expressions.js index c9643d49506..5ecad4f02ec 100644 --- a/jstests/aggregation/expressions/collation_expressions.js +++ b/jstests/aggregation/expressions/collation_expressions.js @@ -86,19 +86,6 @@ results = coll.aggregate([{$project: {out: {$setUnion: [["a", "B", "c"], ["d", " assert.eq(1, results.length); assert.eq(4, results[0].out.length); -// Test that $setUnion is not commutative when a collation is set. A non-const value is required for -// reordering to occur. -assert(coll.drop); -coll.drop(); -assert.commandWorked(coll.insert({_id: 1, upper: "A", lower: "a"})); -var results1 = coll.aggregate([{$project: {out: {$setUnion: [["$upper"], ["a"]]}}}], - {collation: caseInsensitive}) - .toArray(); -var results2 = coll.aggregate([{$project: {out: {$setUnion: [["A"], ["$lower"]]}}}], - {collation: caseInsensitive}) - .toArray(); -assert.eq(results1, results2); - // Test that $setDifference respects the collation. testExpressionWithCollation(coll, {$setDifference: [["a", "B"], ["b", "A"]]}, [], caseInsensitive); diff --git a/jstests/aggregation/expressions/date_from_string.js b/jstests/aggregation/expressions/date_from_string.js index 16b4d0ce311..9b4d0702e5e 100644 --- a/jstests/aggregation/expressions/date_from_string.js +++ b/jstests/aggregation/expressions/date_from_string.js @@ -705,7 +705,7 @@ assertErrorCode(coll, pipeline, ErrorCodes.ConversionFailure); // Test umatched format specifier string. pipeline = [{$project: {date: {$dateFromString: {dateString: "2018-01", format: "%Y-%m-%d"}}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Not enough data"); +assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Data missing"); pipeline = [{$project: {date: {$dateFromString: {dateString: "2018-01", format: "%Y"}}}}]; assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Trailing data"); @@ -766,7 +766,7 @@ assertErrCodeAndErrMsgContains(coll, // Test embedded null bytes in the 'dateString' and 'format' fields. pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31\0/2018", format: "%m/%d/%Y"}}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Not enough data"); +assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.ConversionFailure, "Data missing"); pipeline = [{$project: {date: {$dateFromString: {dateString: "12/31/2018", format: "%m/%d\0/%Y"}}}}]; diff --git a/jstests/aggregation/expressions/expression_trigonometric.js b/jstests/aggregation/expressions/expression_trigonometric.js index 29da3b96315..8992ab4b5cc 100644 --- a/jstests/aggregation/expressions/expression_trigonometric.js +++ b/jstests/aggregation/expressions/expression_trigonometric.js @@ -8,286 +8,285 @@ load('jstests/libs/sbe_assert_error_override.js'); // Override error-code-check const coll = db.expression_trigonometric; coll.drop(); - -// Constructs and inserts a document containing 'val', which are the arguments to trigonometric -// aggregation expression 'op'. If an aggregation expression has constant arguments, it will be -// constant folded and will not be evaluated during query execution. Embedding in a document ensures -// we evaluate expressions during query execution. eg. {$acos: NumberInt(1)} -> {$acos: "$y"} on -// document {"y" : NumberInt(1)} -function convertToOpOnDocument(op, val) { - let nonOptimizedOp = {}; - if (Array.isArray(val) && val.length === 2) { - nonOptimizedOp = {[op]: ["$y", "$x"]}; - assert.commandWorked(coll.insert({"y": val[0], "x": val[1]})); - } else { - nonOptimizedOp = {[op]: "$y"}; - assert.commandWorked(coll.insert({"y": val})); +// We need at least one document in the collection in order to test expressions, add it here. +assert.commandWorked(coll.insert({})); + +// Run a pipeline with `op` and check that the result of operation against the `expResult` value. If +// the actual or expected result is non-numeric (NaN or infinite), this function asserts that they +// match and returns 0 if they do. If they are both numeric, this function returns the difference +// between them as a double. +function testOpReturningDifference(op, expResult) { + const pipeline = [ + {$project: {_id: 0, result: op, expected: {$literal: expResult}}}, + { + $addFields: { + resultType: {$type: "$result"}, + expType: {$type: "$expected"}, + resultAsString: {$convert: {input: "$result", to: "string"}}, + expAsString: {$convert: {input: "$expected", to: "string"}}, + difference: + {$abs: {$convert: {input: {$subtract: ["$result", "$expected"]}, to: "double"}}} + } + } + ]; + const {result, resultType, expType, resultAsString, expAsString, difference} = function() { + const resultArray = coll.aggregate(pipeline).toArray(); + assert.eq(resultArray.length, 1, resultArray); + return resultArray[0]; + }(); + + assert.eq(resultType, expType); + + // We don't want to do direct comparisons with non-real values, because they can have unexpected + // semantics. String comparisons gives us the desired equality semantics for the purposes of + // this test. + const nonRealValues = ["NaN", "Infinity", "-Infinity"]; + if (nonRealValues.includes(expAsString) || nonRealValues.includes(resultAsString)) { + assert.eq(expAsString, resultAsString, tojson(result)); + return 0; } - return nonOptimizedOp; + + return difference; } -function testOp(op, val, expResult) { - const nonOptimizedOp = convertToOpOnDocument(op, val); - const pipeline = [{$project: {_id: 0, result: nonOptimizedOp}}]; - assert.eq(coll.aggregate(pipeline).toArray(), [{result: expResult}]); - assert(coll.drop()); +// Helper for testing that op returns expResult. +function testOp(op, expResult) { + const diff = testOpReturningDifference(op, expResult); + assert.eq(0, diff); } // Helper for testing that the aggregation expression 'op' returns expResult, approximately, // since NumberDecimal has so many representations for a given number (0 versus 0e-40 for // instance). -function testOpApprox(op, val, expResult) { - const nonOptimizedOp = convertToOpOnDocument(op, val); - const pipeline = [{$project: {_id: 0, result: nonOptimizedOp}}]; - const res = coll.aggregate(pipeline).toArray(); - const {result} = res[0]; - const pipeline2 = { - $project: { - difference: {$abs: {$convert: {input: {$subtract: [result, expResult]}, to: "double"}}} - } - }; - const res2 = coll.aggregate(pipeline2).toArray(); - const {difference} = res2[0]; - assert.lt(difference, 0.00000005); - assert(coll.drop()); -} - -function testErrorCode(op, val, expErrorCode) { - const nonOptimizedOp = convertToOpOnDocument(op, val); - const pipeline = [{$project: {_id: 0, result: nonOptimizedOp}}]; - assertErrorCode(coll, pipeline, expErrorCode); - assert(coll.drop()); +function testOpApprox(op, expResult) { + const diff = testOpReturningDifference(op, expResult); + assert.lt(diff, 0.00000005); } // Simple successful int input. -testOp("$acos", NumberInt(1), 0); -testOp("$acosh", NumberInt(1), 0); -testOp("$asin", NumberInt(0), 0); -testOp("$asinh", NumberInt(0), 0); -testOp("$atan", NumberInt(0), 0); -testOp("$atan2", [NumberInt(0), NumberInt(1)], 0); -testOp("$atan2", [NumberInt(0), NumberInt(0)], 0); -testOp("$atanh", NumberInt(0), 0); -testOp("$cos", NumberInt(0), 1); -testOp("$cosh", NumberInt(0), 1); -testOp("$sin", NumberInt(0), 0); -testOp("$sinh", NumberInt(0), 0); -testOp("$tan", NumberInt(0), 0); -testOp("$tanh", NumberInt(0), 0); -testOp("$degreesToRadians", NumberInt(0), 0); -testOp("$radiansToDegrees", NumberInt(0), 0); +testOp({$acos: NumberInt(1)}, 0); +testOp({$acosh: NumberInt(1)}, 0); +testOp({$asin: NumberInt(0)}, 0); +testOp({$asinh: NumberInt(0)}, 0); +testOp({$atan: NumberInt(0)}, 0); +testOp({$atan2: [NumberInt(0), NumberInt(1)]}, 0); +testOp({$atan2: [NumberInt(0), NumberInt(0)]}, 0); +testOp({$atanh: NumberInt(0)}, 0); +testOp({$cos: NumberInt(0)}, 1); +testOp({$cosh: NumberInt(0)}, 1); +testOp({$sin: NumberInt(0)}, 0); +testOp({$sinh: NumberInt(0)}, 0); +testOp({$tan: NumberInt(0)}, 0); +testOp({$tanh: NumberInt(0)}, 0); +testOp({$degreesToRadians: NumberInt(0)}, 0); +testOp({$radiansToDegrees: NumberInt(0)}, 0); // Simple successful long input. -testOp("$acos", NumberLong(1), 0); -testOp("$acosh", NumberLong(1), 0); -testOp("$asin", NumberLong(0), 0); -testOp("$asinh", NumberLong(0), 0); -testOp("$atan", NumberLong(0), 0); -testOp("$atan2", [NumberLong(0), NumberLong(1)], 0); -testOp("$atan2", [NumberLong(0), NumberLong(0)], 0); -testOp("$atanh", NumberLong(0), 0); -testOp("$cos", NumberLong(0), 1); -testOp("$cosh", NumberLong(0), 1); -testOp("$sin", NumberLong(0), 0); -testOp("$sinh", NumberLong(0), 0); -testOp("$tan", NumberLong(0), 0); -testOp("$tanh", NumberLong(0), 0); -testOp("$degreesToRadians", NumberLong(0), 0); -testOp("$radiansToDegrees", NumberLong(0), 0); +testOp({$acos: NumberLong(1)}, 0); +testOp({$acosh: NumberLong(1)}, 0); +testOp({$asin: NumberLong(0)}, 0); +testOp({$asinh: NumberLong(0)}, 0); +testOp({$atan: NumberLong(0)}, 0); +testOp({$atan2: [NumberLong(0), NumberLong(1)]}, 0); +testOp({$atan2: [NumberLong(0), NumberLong(0)]}, 0); +testOp({$atanh: NumberLong(0)}, 0); +testOp({$cos: NumberLong(0)}, 1); +testOp({$cosh: NumberLong(0)}, 1); +testOp({$sin: NumberLong(0)}, 0); +testOp({$sinh: NumberLong(0)}, 0); +testOp({$tan: NumberLong(0)}, 0); +testOp({$tanh: NumberLong(0)}, 0); +testOp({$degreesToRadians: NumberLong(0)}, 0); +testOp({$radiansToDegrees: NumberLong(0)}, 0); // Simple successful double input. -testOp("$acos", 1, 0); -testOp("$acosh", 1, 0); -testOp("$asin", 0, 0); -testOp("$asinh", 0, 0); -testOp("$atan", 0, 0); -testOp("$atan2", [0, 1], 0); -testOp("$atan2", [0, 0], 0); -testOp("$atanh", 0, 0); -testOp("$cos", 0, 1); -testOp("$cosh", 0, 1); -testOp("$sin", 0, 0); -testOp("$sinh", 0, 0); -testOp("$tan", 0, 0); -testOp("$tanh", 0, 0); -testOp("$degreesToRadians", 0, 0); -testOp("$radiansToDegrees", 0, 0); +testOp({$acos: 1}, 0); +testOp({$acosh: 1}, 0); +testOp({$asin: 0}, 0); +testOp({$asinh: 0}, 0); +testOp({$atan: 0}, 0); +testOp({$atan2: [0, 1]}, 0); +testOp({$atan2: [0, 0]}, 0); +testOp({$atanh: 0}, 0); +testOp({$cos: 0}, 1); +testOp({$cosh: 0}, 1); +testOp({$sin: 0}, 0); +testOp({$sinh: 0}, 0); +testOp({$tan: 0}, 0); +testOp({$tanh: 0}, 0); +testOp({$degreesToRadians: 0}, 0); +testOp({$radiansToDegrees: 0}, 0); // Simple successful decimal input. -testOpApprox("$acos", NumberDecimal(1), NumberDecimal(0)); -testOpApprox("$acosh", NumberDecimal(1), NumberDecimal(0)); -testOpApprox("$asin", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$asinh", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$atan", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$atan2", [NumberDecimal(0), 1], NumberDecimal(0)); -testOpApprox("$atan2", [NumberDecimal(0), 0], NumberDecimal(0)); -testOpApprox("$atanh", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$cos", NumberDecimal(0), NumberDecimal(1)); -testOpApprox("$cosh", NumberDecimal(0), NumberDecimal(1)); -testOpApprox("$sin", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$sinh", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$tan", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$tanh", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$degreesToRadians", NumberDecimal(0), NumberDecimal(0)); -testOpApprox("$radiansToDegrees", NumberDecimal(0), NumberDecimal(0)); +testOpApprox({$acos: NumberDecimal(1)}, NumberDecimal(0)); +testOpApprox({$acosh: NumberDecimal(1)}, NumberDecimal(0)); +testOpApprox({$asin: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$asinh: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$atan: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$atan2: [NumberDecimal(0), 1]}, NumberDecimal(0)); +testOpApprox({$atan2: [NumberDecimal(0), 0]}, NumberDecimal(0)); +testOpApprox({$atanh: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$cos: NumberDecimal(0)}, NumberDecimal(1)); +testOpApprox({$cosh: NumberDecimal(0)}, NumberDecimal(1)); +testOpApprox({$sin: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$sinh: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$tan: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$tanh: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$degreesToRadians: NumberDecimal(0)}, NumberDecimal(0)); +testOpApprox({$radiansToDegrees: NumberDecimal(0)}, NumberDecimal(0)); // Infinity input produces out of bounds error. -testErrorCode("$acos", -Infinity, 50989); -testErrorCode("$acos", NumberDecimal('-Infinity'), 50989); -testErrorCode("$acos", Infinity, 50989); -testErrorCode("$acos", NumberDecimal('Infinity'), 50989); - -testErrorCode("$acosh", -Infinity, 50989); -testErrorCode("$acosh", NumberDecimal('-Infinity'), 50989); - -testErrorCode("$asin", -Infinity, 50989); -testErrorCode("$asin", NumberDecimal('-Infinity'), 50989); -testErrorCode("$asin", Infinity, 50989); -testErrorCode("$asin", NumberDecimal('Infinity'), 50989); - -testErrorCode("$atanh", -Infinity, 50989); -testErrorCode("$atanh", NumberDecimal('-Infinity'), 50989); -testErrorCode("$atanh", Infinity, 50989); -testErrorCode("$atanh", NumberDecimal('Infinity'), 50989); - -testErrorCode("$cos", -Infinity, 50989); -testErrorCode("$cos", NumberDecimal('-Infinity'), 50989); -testErrorCode("$cos", Infinity, 50989); -testErrorCode("$cos", NumberDecimal('Infinity'), 50989); - -testErrorCode("$sin", -Infinity, 50989); -testErrorCode("$sin", NumberDecimal('-Infinity'), 50989); -testErrorCode("$sin", Infinity, 50989); -testErrorCode("$sin", NumberDecimal('Infinity'), 50989); - -testErrorCode("$tan", -Infinity, 50989); -testErrorCode("$tan", NumberDecimal('-Infinity'), 50989); -testErrorCode("$tan", Infinity, 50989); -testErrorCode("$tan", NumberDecimal('Infinity'), 50989); +assertErrorCode(coll, [{$project: {a: {$acos: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberDecimal('-Infinity')}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberDecimal('Infinity')}}}], 50989); + +assertErrorCode(coll, [{$project: {a: {$acosh: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acosh: NumberDecimal('-Infinity')}}}], 50989); + +assertErrorCode(coll, [{$project: {a: {$asin: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberDecimal('-Infinity')}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberDecimal('Infinity')}}}], 50989); + +assertErrorCode(coll, [{$project: {a: {$atanh: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberDecimal('-Infinity')}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberDecimal('Infinity')}}}], 50989); + +assertErrorCode(coll, [{$project: {a: {$cos: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$cos: NumberDecimal('-Infinity')}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$cos: Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$cos: NumberDecimal('Infinity')}}}], 50989); + +assertErrorCode(coll, [{$project: {a: {$sin: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$sin: NumberDecimal('-Infinity')}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$sin: Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$sin: NumberDecimal('Infinity')}}}], 50989); + +assertErrorCode(coll, [{$project: {a: {$tan: -Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$tan: NumberDecimal('-Infinity')}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$tan: Infinity}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$tan: NumberDecimal('Infinity')}}}], 50989); // Infinity input produces Infinity as output. -testOp("$acosh", NumberDecimal('Infinity'), NumberDecimal('Infinity')); -testOp("$acosh", Infinity, Infinity); - -testOp("$asinh", NumberDecimal('Infinity'), NumberDecimal('Infinity')); -testOp("$asinh", NumberDecimal('-Infinity'), NumberDecimal('-Infinity')); -testOp("$asinh", Infinity, Infinity); -testOp("$asinh", -Infinity, -Infinity); -testOp("$cosh", NumberDecimal('Infinity'), NumberDecimal('Infinity')); -testOp("$cosh", NumberDecimal('-Infinity'), NumberDecimal('Infinity')); -testOp("$cosh", Infinity, Infinity); -testOp("$cosh", -Infinity, Infinity); -testOp("$sinh", NumberDecimal('Infinity'), NumberDecimal('Infinity')); -testOp("$sinh", NumberDecimal('-Infinity'), NumberDecimal('-Infinity')); -testOp("$sinh", Infinity, Infinity); -testOp("$sinh", -Infinity, -Infinity); +testOp({$acosh: NumberDecimal('Infinity')}, NumberDecimal('Infinity')); +testOp({$acosh: Infinity}, Infinity); + +testOp({$asinh: NumberDecimal('Infinity')}, NumberDecimal('Infinity')); +testOp({$asinh: NumberDecimal('-Infinity')}, NumberDecimal('-Infinity')); +testOp({$asinh: Infinity}, Infinity); +testOp({$asinh: -Infinity}, -Infinity); +testOp({$cosh: NumberDecimal('Infinity')}, NumberDecimal('Infinity')); +testOp({$cosh: NumberDecimal('-Infinity')}, NumberDecimal('Infinity')); +testOp({$cosh: Infinity}, Infinity); +testOp({$cosh: -Infinity}, Infinity); +testOp({$sinh: NumberDecimal('Infinity')}, NumberDecimal('Infinity')); +testOp({$sinh: NumberDecimal('-Infinity')}, NumberDecimal('-Infinity')); +testOp({$sinh: Infinity}, Infinity); +testOp({$sinh: -Infinity}, -Infinity); // Infinity produces finite output (due to asymptotic bounds). -testOpApprox("$atan", NumberDecimal('Infinity'), NumberDecimal(Math.PI / 2)); -testOpApprox("$atan", NumberDecimal('-Infinity'), NumberDecimal(-Math.PI / 2)); -testOpApprox("$atan", Infinity, Math.PI / 2); -testOpApprox("$atan", -Infinity, -Math.PI / 2); - -testOpApprox("$atan2", [NumberDecimal('Infinity'), 0], NumberDecimal(Math.PI / 2)); -testOpApprox("$atan2", [NumberDecimal('-Infinity'), 0], NumberDecimal(-Math.PI / 2)); -testOpApprox( - "$atan2", [NumberDecimal('-Infinity'), NumberDecimal("Infinity")], NumberDecimal(-Math.PI / 4)); -testOpApprox("$atan2", - [NumberDecimal('-Infinity'), NumberDecimal("-Infinity")], +testOpApprox({$atan: NumberDecimal('Infinity')}, NumberDecimal(Math.PI / 2)); +testOpApprox({$atan: NumberDecimal('-Infinity')}, NumberDecimal(-Math.PI / 2)); +testOpApprox({$atan: Infinity}, Math.PI / 2); +testOpApprox({$atan: -Infinity}, -Math.PI / 2); + +testOpApprox({$atan2: [NumberDecimal('Infinity'), 0]}, NumberDecimal(Math.PI / 2)); +testOpApprox({$atan2: [NumberDecimal('-Infinity'), 0]}, NumberDecimal(-Math.PI / 2)); +testOpApprox({$atan2: [NumberDecimal('-Infinity'), NumberDecimal("Infinity")]}, + NumberDecimal(-Math.PI / 4)); +testOpApprox({$atan2: [NumberDecimal('-Infinity'), NumberDecimal("-Infinity")]}, NumberDecimal(-3 * Math.PI / 4)); -testOpApprox("$atan2", [NumberDecimal('0'), NumberDecimal("-Infinity")], NumberDecimal(Math.PI)); -testOpApprox("$atan2", [NumberDecimal('0'), NumberDecimal("Infinity")], NumberDecimal(0)); +testOpApprox({$atan2: [NumberDecimal('0'), NumberDecimal("-Infinity")]}, NumberDecimal(Math.PI)); +testOpApprox({$atan2: [NumberDecimal('0'), NumberDecimal("Infinity")]}, NumberDecimal(0)); -testOp("$tanh", NumberDecimal('Infinity'), NumberDecimal('1')); -testOp("$tanh", NumberDecimal('-Infinity'), NumberDecimal('-1')); +testOp({$tanh: NumberDecimal('Infinity')}, NumberDecimal('1')); +testOp({$tanh: NumberDecimal('-Infinity')}, NumberDecimal('-1')); // Finite input produces infinite outputs. -testOp("$atanh", NumberDecimal(1), NumberDecimal('Infinity')); -testOp("$atanh", NumberDecimal(-1), NumberDecimal('-Infinity')); -testOp("$atanh", 1, Infinity); -testOp("$atanh", -1, -Infinity); +testOp({$atanh: NumberDecimal(1)}, NumberDecimal('Infinity')); +testOp({$atanh: NumberDecimal(-1)}, NumberDecimal('-Infinity')); +testOp({$atanh: 1}, Infinity); +testOp({$atanh: -1}, -Infinity); -testOp("$tanh", Infinity, 1); -testOp("$tanh", -Infinity, -1); +testOp({$tanh: Infinity}, 1); +testOp({$tanh: -Infinity}, -1); // Int argument out of bounds. -testErrorCode("$acos", NumberInt(-2), 50989); -testErrorCode("$acos", NumberInt(2), 50989); -testErrorCode("$asin", NumberInt(-2), 50989); -testErrorCode("$asin", NumberInt(2), 50989); -testErrorCode("$acosh", NumberInt(0), 50989); -testErrorCode("$atanh", NumberInt(2), 50989); -testErrorCode("$atanh", NumberInt(-2), 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberInt(-2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberInt(2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberInt(-2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberInt(2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acosh: NumberInt(0)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberInt(2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberInt(-2)}}}], 50989); // Long argument out of bounds. -testErrorCode("$acos", NumberLong(-2), 50989); -testErrorCode("$acos", NumberLong(2), 50989); -testErrorCode("$asin", NumberLong(-2), 50989); -testErrorCode("$asin", NumberLong(2), 50989); -testErrorCode("$acosh", NumberLong(0), 50989); -testErrorCode("$atanh", NumberLong(2), 50989); -testErrorCode("$atanh", NumberLong(-2), 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberLong(-2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberLong(2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberLong(-2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberLong(2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acosh: NumberLong(0)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberLong(2)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberLong(-2)}}}], 50989); // Double argument out of bounds. -testErrorCode("$acos", -1.1, 50989); -testErrorCode("$acos", 1.1, 50989); -testErrorCode("$asin", -1.1, 50989); -testErrorCode("$asin", 1.1, 50989); -testErrorCode("$acosh", 0.9, 50989); -testErrorCode("$atanh", -1.00001, 50989); -testErrorCode("$atanh", 1.00001, 50989); +assertErrorCode(coll, [{$project: {a: {$acos: -1.1}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: 1.1}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: -1.1}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: 1.1}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acosh: 0.9}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: -1.00001}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: 1.00001}}}], 50989); // Decimal argument out of bounds. -testErrorCode("$acos", NumberDecimal(-1.1), 50989); -testErrorCode("$acos", NumberDecimal(1.1), 50989); -testErrorCode("$asin", NumberDecimal(-1.1), 50989); -testErrorCode("$asin", NumberDecimal(1.1), 50989); -testErrorCode("$acosh", NumberDecimal(0.9), 50989); -testErrorCode("$atanh", NumberDecimal(-1.00001), 50989); -testErrorCode("$atanh", NumberDecimal(1.000001), 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberDecimal(-1.1)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acos: NumberDecimal(1.1)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberDecimal(-1.1)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$asin: NumberDecimal(1.1)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$acosh: NumberDecimal(0.9)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberDecimal(-1.00001)}}}], 50989); +assertErrorCode(coll, [{$project: {a: {$atanh: NumberDecimal(1.000001)}}}], 50989); // Check NaN is preserved. ["$acos", "$asin", "$atan", "$cos", "$sin", "$tan"].forEach(op => { - testOp([op], NaN, NaN); - testOp([op], NumberDecimal(NaN), NumberDecimal(NaN)); + testOp({[op]: NaN}, NaN); + testOp({[op]: NumberDecimal(NaN)}, NumberDecimal(NaN)); // Check the hyperbolic version of each function. - testOp([op + 'h'], NaN, NaN); - testOp([op + 'h'], NumberDecimal(NaN), NumberDecimal(NaN)); + testOp({[op + 'h']: NaN}, NaN); + testOp({[op + 'h']: NumberDecimal(NaN)}, NumberDecimal(NaN)); }); ["$radiansToDegrees", "$degreesToRadians"].forEach(op => { - testOp([op], NaN, NaN); - testOp([op], NumberDecimal(NaN), NumberDecimal(NaN)); - testOp([op], -Infinity, -Infinity); - testOp([op], NumberDecimal(-Infinity), NumberDecimal(-Infinity)); - testOp([op], Infinity, Infinity); - testOp([op], NumberDecimal(Infinity), NumberDecimal(Infinity)); + testOp({[op]: NaN}, NaN); + testOp({[op]: NumberDecimal(NaN)}, NumberDecimal(NaN)); + testOp({[op]: -Infinity}, -Infinity); + testOp({[op]: NumberDecimal(-Infinity)}, NumberDecimal(-Infinity)); + testOp({[op]: Infinity}, Infinity); + testOp({[op]: NumberDecimal(Infinity)}, NumberDecimal(Infinity)); }); -testOp("$atan2", [NumberDecimal('NaN'), NumberDecimal('NaN')], NumberDecimal('NaN')); -testOp("$atan2", [NumberDecimal('NaN'), NumberDecimal('0')], NumberDecimal('NaN')); -testOp("$atan2", [NumberDecimal('0'), NumberDecimal('NaN')], NumberDecimal('NaN')); - -// atan2 additional testing with unknown constants -testOpApprox("$atan2", [NumberInt(3), NumberInt(2)], NumberDecimal(0.9827937232)); -testOpApprox("$atan2", [NumberInt(621), NumberInt(84)], NumberDecimal(1.4363466632)); +testOp({$atan2: [NumberDecimal('NaN'), NumberDecimal('NaN')]}, NumberDecimal('NaN')); +testOp({$atan2: [NumberDecimal('NaN'), NumberDecimal('0')]}, NumberDecimal('NaN')); +testOp({$atan2: [NumberDecimal('0'), NumberDecimal('NaN')]}, NumberDecimal('NaN')); // Non-numeric input. -testErrorCode("$acos", "string", 28765); -testErrorCode("$acosh", "string", 28765); -testErrorCode("$asin", "string", 28765); -testErrorCode("$asinh", "string", 28765); -testErrorCode("$atan", "string", 28765); -testErrorCode("$atan2", ["string", "string"], 51044); -testErrorCode("$atan2", ["string", 0.0], 51044); -testErrorCode("$atan2", [0.0, "string"], 51045); -testErrorCode("$atanh", "string", 28765); -testErrorCode("$cos", "string", 28765); -testErrorCode("$cosh", "string", 28765); -testErrorCode("$sin", "string", 28765); -testErrorCode("$sinh", "string", 28765); -testErrorCode("$tan", "string", 28765); -testErrorCode("$tanh", "string", 28765); -testErrorCode("$degreesToRadians", "string", 28765); -testErrorCode("$radiansToDegrees", "string", 28765); +assertErrorCode(coll, [{$project: {a: {$acos: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$acosh: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$asin: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$asinh: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$atan: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$atan2: ["string", "string"]}}}], 51044); +assertErrorCode(coll, [{$project: {a: {$atan2: ["string", 0.0]}}}], 51044); +assertErrorCode(coll, [{$project: {a: {$atan2: [0.0, "string"]}}}], 51045); +assertErrorCode(coll, [{$project: {a: {$atanh: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$cos: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$cosh: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$sin: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$sinh: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$tan: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$tanh: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$degreesToRadians: "string"}}}], 28765); +assertErrorCode(coll, [{$project: {a: {$radiansToDegrees: "string"}}}], 28765); }()); diff --git a/jstests/aggregation/expressions/internal_key_string_value.js b/jstests/aggregation/expressions/internal_key_string_value.js deleted file mode 100644 index 71cbb7635c5..00000000000 --- a/jstests/aggregation/expressions/internal_key_string_value.js +++ /dev/null @@ -1,199 +0,0 @@ -// Tests the behavior of $_internalKeyStringValue when used in agg expressions. - -(function() { -"use strict"; - -const coll = db[jsTestName()]; - -// Testing behavior for basic data types. -coll.drop(); -let docs = [ - {_id: 0, a: NumberInt(1)}, - {_id: 1, a: NumberLong(2)}, - {_id: 2, a: 3.0}, - {_id: 3, a: NumberDecimal("4.000000")}, - {_id: 4, a: "abc"}, - {_id: 5, a: ISODate("2024-01-01")}, - {_id: 6, a: [1, 2, 3]}, - {_id: 7, a: {b: 1, c: 2, d: 3}}, -]; -assert.commandWorked(coll.insert(docs)); - -let results = - coll.aggregate( - [{$sort: {_id: 1}}, {$addFields: {b: {$_internalKeyStringValue: {input: "$a"}}}}]) - .toArray(); -assert.eq(results, - [ - {_id: 0, a: NumberInt(1), b: BinData(0, "KwIE")}, - {_id: 1, a: NumberLong(2), b: BinData(0, "KwQE")}, - {_id: 2, a: 3.0, b: BinData(0, "KwYE")}, - {_id: 3, a: NumberDecimal("4.000000"), b: BinData(0, "KwgE")}, - {_id: 4, a: "abc", b: BinData(0, "PGFiYwAE")}, - {_id: 5, a: ISODate("2024-01-01"), b: BinData(0, "eIAAAYzCUfQABA==")}, - {_id: 6, a: [1, 2, 3], b: BinData(0, "UCsCKwQrBgAE")}, - {_id: 7, a: {b: 1, c: 2, d: 3}, b: BinData(0, "Rh5iACsCHmMAKwQeZAArBgAE")}, - ], - results); - -// Testing behavior for same numeric values of different types. -assert(coll.drop()); -docs = [ - {_id: 0, a: NumberInt(1)}, - {_id: 1, a: NumberLong(1)}, - {_id: 2, a: 1.0}, - {_id: 3, a: NumberDecimal("1.000")}, - {_id: 4, a: NumberDecimal("1.000000")}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate( - [{$sort: {_id: 1}}, {$addFields: {b: {$_internalKeyStringValue: {input: "$a"}}}}]) - .toArray(); -assert.eq(docs.length, results.length, results); -assert(results.every(result => bsonWoCompare(result.b, results[0].b) === 0), results); - -// Testing behavior for same numeric values of different types inside object. -assert(coll.drop()); -docs = [ - {_id: 0, a: {b: NumberInt(1)}}, - {_id: 1, a: {b: 1.0}}, - {_id: 2, a: {b: NumberDecimal("1.000")}}, - {_id: 3, a: {b: NumberDecimal("1.000000")}}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate( - [{$sort: {_id: 1}}, {$addFields: {c: {$_internalKeyStringValue: {input: "$a"}}}}]) - .toArray(); -assert.eq(docs.length, results.length, results); -assert(results.every(result => bsonWoCompare(result.c, results[0].c) === 0), results); - -// Testing behavior for close numeric values. -assert(coll.drop()); -docs = [ - {_id: 0, a: 1}, - {_id: 1, a: 1.00001}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate( - [{$sort: {_id: 1}}, {$addFields: {b: {$_internalKeyStringValue: {input: "$a"}}}}]) - .toArray(); -assert.eq(2, results.length, results); -assert(bsonWoCompare(results[0].b, results[1].b) !== 0, results); - -// Testing behavior for large numeric values. -assert(coll.drop()); -docs = [ - {_id: 0, a: 1e20}, - {_id: 1, a: NumberDecimal("1e21")}, -]; -assert.commandWorked(coll.insert(docs)); - -results = - coll.aggregate([ - {$sort: {_id: 1}}, - { - $addFields: - {b: {$_internalKeyStringValue: {input: "$a"}}, c: {$toHashedIndexKey: "$a"}} - } - ]) - .toArray(); -assert.eq(2, results.length, results); -assert(bsonWoCompare(results[0].b, results[1].b) !== 0, results); -// $toHashedIndexKey hashes large numbers greater than 2^63 to the same result. -assert(bsonWoCompare(results[0].c, results[1].c) === 0, results); - -// Testing behavior for strings under case-sensitive collation that doesn't match. -assert(coll.drop()); -docs = [ - {_id: 0, a: "aAa"}, - {_id: 1, a: "AaA"}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate([ - {$sort: {_id: 1}}, - { - $addFields: { - b: { - $_internalKeyStringValue: - {input: "$a", collation: {locale: "en", strength: 3}} - } - } - } - ]) - .toArray(); -assert.eq(2, results.length, results); -assert(bsonWoCompare(results[0].b, results[1].b) !== 0, results); - -// Testing behavior for strings under case-sensitive collation that matches. -assert(coll.drop()); -docs = [ - {_id: 0, a: "aAa"}, - {_id: 1, a: "aAa"}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate([ - {$sort: {_id: 1}}, - { - $addFields: { - b: { - $_internalKeyStringValue: - {input: "$a", collation: {locale: "en", strength: 3}} - } - } - } - ]) - .toArray(); -assert.eq(docs.length, results.length, results); -assert(results.every(result => bsonWoCompare(result.c, results[0].c) === 0), results); - -// Testing behavior for strings under case-insensitive collation that doesn't match. -assert(coll.drop()); -docs = [ - {_id: 0, a: "aAa"}, - {_id: 1, a: "aBa"}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate([ - {$sort: {_id: 1}}, - { - $addFields: { - b: { - $_internalKeyStringValue: - {input: "$a", collation: {locale: "en", strength: 1}} - } - } - } - ]) - .toArray(); -assert.eq(2, results.length, results); -assert(bsonWoCompare(results[0].b, results[1].b) !== 0, results); - -// Testing behavior for strings under case-insensitive collation that matches. -assert(coll.drop()); -docs = [ - {_id: 0, a: "aAa"}, - {_id: 1, a: "AaA"}, -]; -assert.commandWorked(coll.insert(docs)); - -results = coll.aggregate([ - {$sort: {_id: 1}}, - { - $addFields: { - b: { - $_internalKeyStringValue: - {input: "$a", collation: {locale: "en", strength: 1}} - } - } - } - ]) - .toArray(); -assert.eq(docs.length, results.length, results); -assert(results.every(result => bsonWoCompare(result.b, results[0].b) === 0), results); -})(); diff --git a/jstests/aggregation/expressions/subtract.js b/jstests/aggregation/expressions/subtract.js deleted file mode 100644 index 1be9f731cbb..00000000000 --- a/jstests/aggregation/expressions/subtract.js +++ /dev/null @@ -1,81 +0,0 @@ -load("jstests/aggregation/extras/utils.js"); // For assertErrorCode and assertErrMsgContains. -load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs. - -// Tests for $subtract aggregation expression -(function() { -"use strict"; - -const coll = db.subtract_coll; -coll.drop(); - -assert.commandWorked(coll.insert({_id: 0, lhs: 1, rhs: 1})); -assert.commandWorked(coll.insert({_id: 1, lhs: -2000000000, rhs: 2000000000})); -assert.commandWorked( - coll.insert({_id: 2, lhs: NumberLong(-20000000000), rhs: NumberLong(20000000000)})); -assert.commandWorked(coll.insert({_id: 3, lhs: 10.5, rhs: 0.5})); -assert.commandWorked( - coll.insert({_id: 4, lhs: NumberDecimal("10000.12345"), rhs: NumberDecimal("10.1234")})); -assert.commandWorked(coll.insert({_id: 5, lhs: new Date(1912392670000), rhs: 70000})); -assert.commandWorked( - coll.insert({_id: 6, lhs: new Date(1912392670000), rhs: new Date(1912392600000)})); -// Doubles are rounded to int64 when subtracted from Date -assert.commandWorked(coll.insert({_id: 7, lhs: new Date(1683794065002), rhs: 0.5})); -assert.commandWorked(coll.insert({_id: 8, lhs: new Date(1683794065002), rhs: 1.4})); -assert.commandWorked(coll.insert({_id: 9, lhs: new Date(1683794065002), rhs: 1.5})); -assert.commandWorked(coll.insert({_id: 10, lhs: new Date(1683794065002), rhs: 1.7})); -// Decimals are rounded to int64, when tie rounded to even, when subtracted from Date -assert.commandWorked( - coll.insert({_id: 11, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.4")})); -assert.commandWorked( - coll.insert({_id: 12, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.5")})); -assert.commandWorked( - coll.insert({_id: 13, lhs: new Date(1683794065002), rhs: new NumberDecimal("1.7")})); -assert.commandWorked( - coll.insert({_id: 14, lhs: new Date(1683794065002), rhs: new NumberDecimal("2.5")})); - -const result = - coll.aggregate([{$project: {diff: {$subtract: ["$lhs", "$rhs"]}}}, {$sort: {_id: 1}}]) - .toArray(); -assert.eq(result[0].diff, 0); -assert.eq(result[1].diff, NumberLong("-4000000000")); -assert.eq(result[2].diff, NumberLong("-40000000000")); -assert.eq(result[3].diff, 10.0); -assert.eq(result[4].diff, NumberDecimal("9990.00005")); -assert.eq(result[5].diff, new Date(1912392600000)); -assert.eq(result[6].diff, 70000); -assert.eq(result[7].diff, new Date(1683794065001)); -assert.eq(result[8].diff, new Date(1683794065001)); -assert.eq(result[9].diff, new Date(1683794065000)); -assert.eq(result[10].diff, new Date(1683794065000)); -assert.eq(result[11].diff, new Date(1683794065001)); -assert.eq(result[12].diff, new Date(1683794065000)); -assert.eq(result[13].diff, new Date(1683794065000)); -assert.eq(result[14].diff, new Date(1683794065000)); - -// Following cases will report overflow error -coll.drop(); - -assert.commandWorked(coll.insert([{ - _id: 0, - veryBigNegativeLong: NumberLong("-9223372036854775808"), - veryBigNegativeDouble: -9223372036854775808, - veryBigNegativeDecimal: NumberDecimal("-9223372036854775808"), - doubleNaN: NaN, - decimalNaN: NumberDecimal("NaN"), -}])); - -let pipeline = [{$project: {res: {$subtract: [new Date(10), "$veryBigNegativeLong"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); - -pipeline = [{$project: {res: {$subtract: [new Date(10), "$veryBigNegativeDouble"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); - -pipeline = [{$project: {res: {$subtract: [new Date(10), "$veryBigNegativeDecimal"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); - -pipeline = [{$project: {res: {$subtract: [new Date(-1), "$doubleNaN"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); - -pipeline = [{$project: {res: {$subtract: [new Date(-1), "$decimalNaN"]}}}]; -assertErrCodeAndErrMsgContains(coll, pipeline, ErrorCodes.Overflow, "date overflow"); -}()); diff --git a/jstests/aggregation/expressions/switch_errors.js b/jstests/aggregation/expressions/switch_errors.js index 17ccd53f9d5..9701bc4b019 100644 --- a/jstests/aggregation/expressions/switch_errors.js +++ b/jstests/aggregation/expressions/switch_errors.js @@ -60,26 +60,9 @@ pipeline = { }; assertErrorCode(coll, pipeline, 40068, "$switch requires at least one branch"); -assert.commandWorked(coll.insert({x: 1})); +coll.insert({x: 1}); pipeline = { "$project": {"output": {"$switch": {"branches": [{"case": {"$eq": ["$x", 0]}, "then": 1}]}}} }; assertErrorCode(coll, pipeline, 40066, "$switch has no default and an input matched no case"); - -// This query was designed to reproduce SERVER-70190. The first branch of the $switch can be -// optimized away and the $ifNull can be optimized to 2. If the field "x" exists in the input -// document and is truthy, then the expression should return 2. Otherwise it should throw because no -// case statement matched and there is no "default" expression. -pipeline = [{ - $sortByCount: { - $switch: { - branches: - [{case: {$literal: false}, then: 1}, {case: "$x", then: {$ifNull: [2, "$y"]}}] - } - } -}]; -assert.eq([{"_id": 2, "count": 1}], coll.aggregate(pipeline).toArray()); -assert.commandWorked(coll.remove({x: 1})); -assert.commandWorked(coll.insert({z: 1})); -assertErrorCode(coll, pipeline, 40066, "$switch has no default and an input matched no case"); }()); diff --git a/jstests/aggregation/extras/utils.js b/jstests/aggregation/extras/utils.js index febd852c253..6face031533 100644 --- a/jstests/aggregation/extras/utils.js +++ b/jstests/aggregation/extras/utils.js @@ -342,19 +342,6 @@ function assertErrCodeAndErrMsgContains(coll, pipe, code, expectedMessage) { } /** - * Assert that an aggregation ran on admin DB fails with a specific code and the error message - * contains the given string. Note that 'code' can be an array of possible codes. - */ -function assertAdminDBErrCodeAndErrMsgContains(coll, pipe, code, expectedMessage) { - const response = assert.commandFailedWithCode( - coll.getDB().adminCommand({aggregate: 1, pipeline: pipe, cursor: {}}), code); - assert.neq( - -1, - response.errmsg.indexOf(expectedMessage), - "Error message did not contain '" + expectedMessage + "', found:\n" + tojson(response)); -} - -/** * Assert that an aggregation fails with any code and the error message contains the given * string. */ diff --git a/jstests/aggregation/group_by_objectid.js b/jstests/aggregation/group_by_objectid.js deleted file mode 100644 index 4d7b638b6e5..00000000000 --- a/jstests/aggregation/group_by_objectid.js +++ /dev/null @@ -1,36 +0,0 @@ -// Tests that $group aggregation works with group key of type ObjectId. -// @tags: [ -// # Some in memory variants will error because this test uses too much memory. As such, we do not -// # run this test on in-memory variants. -// requires_persistence, -// ] -const collName = jsTestName(); -const coll = db[collName]; -coll.drop(); - -const bigStr = Array(100 * 1000).toString(); // ~ 100KB of ',' -const bigStr2 = bigStr + "2"; -const nDocs = 1000; -const nGroups = 10; - -let objectIds = []; -for (let i = 0; i < nGroups; i++) { - objectIds.push(new ObjectId()); -} - -const bulk = coll.initializeUnorderedBulkOp(); -for (let i = 1; i <= nDocs; i++) { - bulk.insert({b: objectIds[i % nGroups], bigStr: bigStr, b2: bigStr2, c: i}); -} -assert.commandWorked(bulk.execute()); - -const pipeline = [ - { - $sort: { - "c": NumberInt(-1), - } - }, - {$group: {"_id": "$b", "doc": {"$first": "$$ROOT"}}}, -]; - -assert.commandWorked(db.runCommand({aggregate: collName, pipeline: pipeline, cursor: {}})); diff --git a/jstests/aggregation/group_conversion_to_distinct_scan.js b/jstests/aggregation/group_conversion_to_distinct_scan.js index ba2f55ec64b..95a55fec612 100644 --- a/jstests/aggregation/group_conversion_to_distinct_scan.js +++ b/jstests/aggregation/group_conversion_to_distinct_scan.js @@ -42,7 +42,7 @@ function createIndexes() { } createIndexes(); -const documents = [ +assert.commandWorked(coll.insert([ {_id: 0, a: 1, b: 1, c: 1}, {_id: 1, a: 1, b: 2, c: 2}, {_id: 2, a: 1, b: 2, c: 3}, @@ -71,8 +71,7 @@ const documents = [ {_id: 21, str: "FoO", d: 2}, {_id: 22, str: "bar", d: 4}, {_id: 23, str: "bAr", d: 3} -]; -assert.commandWorked(coll.insert(documents)); +])); // Helper for dropping an index and removing it from the list of indexes. function removeIndex(pattern) { @@ -742,24 +741,4 @@ assertResultsMatchWithAndWithoutHintandIndexes( explain = coll.explain().aggregate(pipeline, collationOption); assert.neq(null, getAggPlanStage(explain, "DISTINCT_SCAN"), explain); assert.eq({str: 1, d: 1}, getAggPlanStage(explain, "DISTINCT_SCAN").keyPattern); - -// -// Verify that a $sort-$_internalStreamingGroup pipeline can use DISTINCT_SCAN -// -pipeline = [ - {$sort: {_id: 1}}, - {$_internalStreamingGroup: {_id: "$_id", value: {$first: "$a"}, $monotonicIdFields: ["_id"]}} -]; -const expectedResult = []; -for (let i = 0; i <= 23; i++) { - let resultDocument = {_id: i, value: null}; - if (documents[i].hasOwnProperty("a")) { - resultDocument["value"] = documents[i].a; - } - expectedResult.push(resultDocument); -} -assertResultsMatchWithAndWithoutHintandIndexes(pipeline, expectedResult); -explain = coll.explain().aggregate(pipeline); -assert.neq(null, getAggPlanStage(explain, "DISTINCT_SCAN"), explain); -assert.eq({_id: 1}, getAggPlanStage(explain, "DISTINCT_SCAN").keyPattern); }()); diff --git a/jstests/aggregation/ifnull.js b/jstests/aggregation/ifnull.js index 86d4ad2f85d..f147111a259 100644 --- a/jstests/aggregation/ifnull.js +++ b/jstests/aggregation/ifnull.js @@ -15,8 +15,7 @@ assert.commandWorked(t.insertOne({ my_null: null, my_undefined: undefined, my_obj: {}, - my_list: [], - my_nested: {zero: 0, null: null, undefined: undefined} + my_list: [] })); function assertError(expectedErrorCode, ifNullSpec) { @@ -24,12 +23,8 @@ function assertError(expectedErrorCode, ifNullSpec) { } function assertResult(expectedResult, ifNullSpec) { - let res = t.aggregate({$project: {_id: 0, a: {$ifNull: ifNullSpec}}}).toArray()[0]; + const res = t.aggregate({$project: {_id: 0, a: {$ifNull: ifNullSpec}}}).toArray()[0]; assert.docEq({a: expectedResult}, res); - res = t.aggregate({ - $group: {_id: 0, a: {$push: {$let: {vars: {x: {$ifNull: ifNullSpec}}, in : "$$x"}}}} - }).toArray()[0]; - assert.docEq({_id: 0, a: [expectedResult]}, res); } // Wrong number of args. @@ -38,7 +33,6 @@ assertError(1257300, ['$one']); assertError(1257300, ['$my_null']); // First arg non null. -assertResult(0, ['$my_nested.zero', '$one']); assertResult(1, ['$one', '$two']); assertResult(2, ['$two', '$one']); assertResult(false, ['$my_false', '$one']); @@ -54,7 +48,6 @@ assertResult(1, ['$one', '$two', null]); assertResult(2, ['$two', '$my_undefined', null]); // First arg null. -assertResult(0, ['$my_nested.null', '$my_nested.zero']); assertResult(2, ['$my_null', '$two']); assertResult(1, ['$my_null', '$one']); assertResult(null, ['$my_null', '$my_null']); @@ -66,7 +59,6 @@ assertResult(null, ['$my_null', '$my_null', null]); assertResult(undefined, ['$my_null', '$my_null', undefined]); // First arg undefined. -assertResult(0, ['$my_nested.undefined', '$my_nested.zero']); assertResult(2, ['$my_undefined', '$two']); assertResult(1, ['$my_undefined', '$one']); assertResult(null, ['$my_undefined', '$my_null']); diff --git a/jstests/aggregation/large_bson_mid_pipeline.js b/jstests/aggregation/large_bson_mid_pipeline.js deleted file mode 100644 index 0604bff86c2..00000000000 --- a/jstests/aggregation/large_bson_mid_pipeline.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Tests that extra-large BSON objects (>16MB) can be materialized for the '$match' stage in the - * middle of the query plan without throwing 'BSONObjectTooLarge' exception. - */ -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); // For 'getAggPlanStage()'. - -const testDB = db.getSiblingDB("jsTestName"); -assert.commandWorked(testDB.dropDatabase()); - -const coll = testDB.coll; -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 -// '$match'. Use '$_internalInhibitOptimization' to ensure '$match' is not removed by the optimizer. -const pipeline = [ - {$_internalInhibitOptimization: {}}, - {$addFields: {c: {$concat: ["$b", "-"]}}}, - {$match: {c: {$exists: true}}}, - {$project: {a: 1}} -]; - -assert.doesNotThrow(() => coll.aggregate(pipeline).toArray()); -})(); diff --git a/jstests/aggregation/match_no_swap_rand.js b/jstests/aggregation/match_no_swap_rand.js deleted file mode 100644 index 508056e97f4..00000000000 --- a/jstests/aggregation/match_no_swap_rand.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Test that $rand (and by extension, $sampleRate) doesn't get pushed down. - * @tags: [ - * # Tests the 'stages' field of the explain output which is hidden beneath each shard's name when - * # run against sharded collections. - * assumes_unsharded_collection, - * # Tests the explain output, so does not work when wrapped in a facet. - * do_not_wrap_aggregations_in_facets, - * # Explicitly testing optimization. - * requires_pipeline_optimization, - * ] - */ -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); - -function getWinningPlanForPipeline({coll, pipeline}) { - const explain = assert.commandWorked(coll.explain().aggregate(pipeline)); - if ("queryPlanner" in explain) { - return getWinningPlan(explain.queryPlanner); - } - return getWinningPlan(explain.stages[0].$cursor.queryPlanner); -} - -function assertScanFilterEq({coll, pipeline, filter}) { - const winningPlan = getWinningPlanForPipeline({coll, pipeline}); - const collScan = getPlanStage(winningPlan, "COLLSCAN"); - assert(collScan); - // Sometimes explain will have 'filter' set to an empty object, other times there will be no - // 'filter'. If we are expecting there to be no filter on the COLLSCAN, either is acceptable. - if (filter) { - assert.docEq(collScan.filter, filter); - } else { - assert(!collScan.filter || Object.keys(collScan.filter).length == 0); - } -} - -// Test that a $match with a random expression should not be pushed past a $group. -{ - const coll = db[jsTestName()]; - coll.drop(); - - let bulk = coll.initializeUnorderedBulkOp(); - for (var i = 0; i < 100; i++) { - bulk.insert({a: {b: i % 5, c: i}}); - } - assert.commandWorked(bulk.execute()); - - assertScanFilterEq({ - coll, - pipeline: - [{$group: {_id: "$a.b", first: {$first: "$$CURRENT"}}}, {$match: {$sampleRate: 0.25}}] - }); - - assertScanFilterEq({ - coll, - pipeline: [ - {$group: {_id: "$a.b", first: {$first: "$$CURRENT"}}}, - {$replaceRoot: {newRoot: '$first'}}, - {$match: {$sampleRate: 0.25}}, - ] - }); - - assertScanFilterEq({ - coll, - pipeline: [ - {$group: {_id: "$a.b", first: {$first: "$$CURRENT"}}}, - {$match: {$sampleRate: 0.25}}, - {$replaceRoot: {newRoot: '$first'}}, - ] - }); - - assertScanFilterEq({ - coll, - pipeline: [ - {$match: {c: {$gt: 500}}}, - {$group: {_id: "$a.b", first: {$first: "$$CURRENT"}}}, - {$match: {$sampleRate: 0.25}}, - ], - filter: {c: {$gt: 500}} - }); - - assertScanFilterEq({ - coll, - pipeline: [ - {$match: {c: {$gt: 500}}}, - // A $lookup that split $match exprs can push down past. - { - $lookup: { - as: "joinedC", - from: coll.getName(), - localField: "c", - foreignField: "c", - } - }, - { - $match: { - $and: [ - {$expr: {$lt: ["$c", 800]}}, // Should split me out. - {$expr: {$lt: [{$rand: {}}, {$const: 0.25}]}} // Can't split me out. - ] - } - }, - ], - filter: { - $and: [ - {c: {$gt: 500}}, - {$expr: {$lt: ["$c", {$const: 800}]}}, - {c: {$_internalExprLt: 800}}, - ] - } - }); -} - -// Test that a $match with a random expression should not be pushed past $_internalUnpackBucket. -{ - const collName = jsTestName() + "_ts"; - db[collName].drop(); - assert.commandWorked(db.createCollection(collName, { - timeseries: { - timeField: "t", - metaField: "m", - } - })); - const coll = db[collName]; - - let bulk = coll.initializeUnorderedBulkOp(); - for (var i = 0; i < 100; i++) { - bulk.insert({t: new Date(), m: i}); - } - assert.commandWorked(bulk.execute()); - - assertScanFilterEq({ - coll, - pipeline: [ - {$match: {$sampleRate: 0.25}}, - ] - }); - - if (!FeatureFlagUtil.isEnabled(db, "TimeseriesScalabilityImprovements")) { - assertScanFilterEq({ - coll, - pipeline: [ - { - $match: { - $and: [ - {$expr: {$lt: ["$m", 50]}}, // Should split me out. - {$expr: {$lt: [{$rand: {}}, {$const: 0.25}]}} // Can't split me out. - ] - } - }, - ], - filter: { - $and: [ - {$expr: {$lt: ["$meta", {$const: 50}]}}, - {meta: {$_internalExprLt: 50}}, - ] - } - }); - } -} -}()); diff --git a/jstests/aggregation/optimize_away_pipeline.js b/jstests/aggregation/optimize_away_pipeline.js index d7aa2ce0b15..cd5ba0a5258 100644 --- a/jstests/aggregation/optimize_away_pipeline.js +++ b/jstests/aggregation/optimize_away_pipeline.js @@ -656,9 +656,6 @@ assertProjectionIsNotRemoved([{$project: {a: 1}}, {$group: {_id: "$a", s: {$sum: assertProjectionIsNotRemoved( [{$project: {a: 1, b: 1}}, {$group: {_id: "$a.b", s: {$sum: "$b.c"}}}]); -// Test that an inclusion projection is NOT optimized away if group depends on the entire document. -assertProjectionIsNotRemoved([{$project: {a: 1}}, {$group: {_id: "$$ROOT"}}]); - // Spinoff on the one above: Without supporting this kind of prefixing analysis, we can confuse // ourselves with our dependency analysis. If the $group depends on both "path" and "path.subpath" // then it will generate a $project on only "path" to express its dependency set. We then fail to diff --git a/jstests/aggregation/sources/densify/explicit_range.js b/jstests/aggregation/sources/densify/explicit_range.js index a2c2edd92f7..842ae79d5bf 100644 --- a/jstests/aggregation/sources/densify/explicit_range.js +++ b/jstests/aggregation/sources/densify/explicit_range.js @@ -16,77 +16,66 @@ coll.drop(); // Run all tests for each date unit and on numeric values. for (let i = 0; i < densifyUnits.length; i++) { - coll.drop(); - const unit = densifyUnits[i]; + coll.drop(); const base = unit ? new ISODate("2021-01-01") : 0; const {add} = getArithmeticFunctionsForUnit(unit); - const getBounds = (lower, upper) => { - return [add(base, lower), add(base, upper)]; - }; + const runDensifyRangeTest = ({step, bounds}, msg) => testDensifyStage({ + field: "val", + range: {step, bounds: [add(base, bounds[0]), add(base, bounds[1])], unit: unit} + }, + coll, + msg); // Run all tests for different step values. for (let i = 0; i < interestingSteps.length; i++) { const step = interestingSteps[i]; - // Generate documents in an empty collection. - let stage = {field: "val", range: {step: step, bounds: getBounds(0, 10), unit: unit}}; - testDensifyStage(stage, coll); + runDensifyRangeTest({step, bounds: [0, 10]}); // Fill in some documents between existing docs. coll.drop(); coll.insert({val: base}); - coll.insert({val: add(base, 30)}); - // Checking that the upper bound is exclusive. - stage = {field: "val", range: {step: step, bounds: getBounds(10, 25), unit: unit}}; - testDensifyStage(stage, coll); + coll.insert({val: add(base, 99)}); + runDensifyRangeTest( + {step, bounds: [10, 25]}); // Checking that the upper bound is exclusive. // Fill in odd documents. coll.drop(); - insertDocumentsOnStep({base, min: 2, max: 11, step: 2, addFunc: add, coll: coll}); - - stage = {field: "val", range: {step: step, bounds: getBounds(1, 12), unit: unit}}; - testDensifyStage(stage, coll); - stage = {field: "val", range: {step: step, bounds: getBounds(1, 11), unit: unit}}; - testDensifyStage(stage, coll); - stage = {field: "val", range: {step: step, bounds: getBounds(1, 10), unit: unit}}; - testDensifyStage(stage, coll); + insertDocumentsOnStep({base, min: 2, max: 21, step: 2, addFunc: add, coll: coll}); + runDensifyRangeTest({step, bounds: [1, 22]}); + runDensifyRangeTest({step, bounds: [1, 21]}); + runDensifyRangeTest({step, bounds: [1, 20]}); // Negative numbers. coll.drop(); - insertDocumentsOnStep({base, min: -20, max: -1, step: 2, addFunc: add, coll: coll}); - stage = {field: "val", range: {step: step, bounds: getBounds(-10, -1), unit: unit}}; - testDensifyStage(stage, coll); - stage = {field: "val", range: {step: step, bounds: getBounds(-10, 0), unit: unit}}; - testDensifyStage(stage, coll); - stage = {field: "val", range: {step: step, bounds: getBounds(-10, -2), unit: unit}}; - testDensifyStage(stage, coll); + insertDocumentsOnStep({base, min: -100, max: -1, step: 2, addFunc: add, coll: coll}); + runDensifyRangeTest({step, bounds: [-40, -5]}); + runDensifyRangeTest({step, bounds: [-60, 0]}); + runDensifyRangeTest({step, bounds: [-40, -6]}); // Extend range past collection. coll.drop(); - insertDocumentsOnStep({base, min: 0, max: 10, step: 3, addFunc: add, coll: coll}); - stage = {field: "val", range: {step: step, bounds: getBounds(5, 15), unit: unit}}; - testDensifyStage(stage, coll); + insertDocumentsOnStep({base, min: 0, max: 50, step: 3, addFunc: add, coll: coll}); + runDensifyRangeTest({step, bounds: [30, 75]}); // Start range before collection. coll.drop(); - insertDocumentsOnStep({base, min: 20, max: 30, step: 2, addFunc: add, coll: coll}); - stage = {field: "val", range: {step: step, bounds: getBounds(10, 25), unit: unit}}; - testDensifyStage(stage, coll); + insertDocumentsOnStep({base, min: 20, max: 40, step: 2, addFunc: add, coll: coll}); + runDensifyRangeTest({step, bounds: [10, 25]}); // Extend range in both directions past collection bounds. coll.drop(); - insertDocumentsOnStep({base, min: 20, max: 30, step: 2, addFunc: add, coll: coll}); - stage = {field: "val", range: {step: step, bounds: getBounds(10, 35), unit: unit}}; - testDensifyStage(stage, coll); + insertDocumentsOnStep({base, min: 20, max: 40, step: 2, addFunc: add, coll: coll}); + runDensifyRangeTest({step, bounds: [10, 45]}); // Different off-step documents. coll.drop(); insertDocumentsOnPredicate( - {base, min: 0, max: 25, pred: i => i % 3 == 0 || i % 7 == 0, addFunc: add, coll: coll}); - stage = {field: "val", range: {step: step, bounds: getBounds(10, 20), unit: unit}}; - testDensifyStage(stage, coll); + {base, min: 0, max: 50, pred: i => i % 3 == 0 || i % 7 == 0, addFunc: add, coll: coll}); + + runDensifyRangeTest({step, bounds: [10, 45]}); // Lots of off-step documents with nulls sprinkled in to confirm that a null value is // treated the same as a missing value. @@ -107,33 +96,12 @@ for (let i = 0; i < densifyUnits.length; i++) { insertDocumentsOnPredicate({ base, min: 20, - max: 30, + max: 50, pred: i => i % 3 == 0 || i % 7 == 0, addFunc: add, coll: coll }); - stage = {field: "val", range: {step: step, bounds: getBounds(10, 25), unit: unit}}; - testDensifyStage(stage, coll); + runDensifyRangeTest({step, bounds: [10, 45]}); } } - -// Run a test where there are no documents in the range to ensure we don't generate anything before -// the range. -coll.drop(); -let documents = [ - {"date": ISODate("2022-10-29T23:00:00Z")}, -]; -coll.insert(documents); -let stage = { - field: "date", - range: { - step: 1, - unit: "month", - bounds: [ - ISODate("2022-10-31T23:00:00.000Z"), - ISODate("2022-11-30T23:00:00.000Z"), - ], - }, -}; -testDensifyStage(stage, coll, "Ensure no docs before range"); })(); diff --git a/jstests/aggregation/sources/densify/full_range.js b/jstests/aggregation/sources/densify/full_range.js index f84a8e7fe5a..66f13540019 100644 --- a/jstests/aggregation/sources/densify/full_range.js +++ b/jstests/aggregation/sources/densify/full_range.js @@ -16,33 +16,33 @@ coll.drop(); // Run all tests for each date unit and on numeric values. for (let i = 0; i < densifyUnits.length; i++) { - coll.drop(); - const unit = densifyUnits[i]; + coll.drop(); const base = unit ? new ISODate("2021-01-01") : 0; const {add} = getArithmeticFunctionsForUnit(unit); // Run all tests for different step values. for (let i = 0; i < interestingSteps.length; i++) { const step = interestingSteps[i]; - const stage = {field: "val", range: {step: step, bounds: "full", unit: unit}}; + const runDensifyFullTest = (msg) => + testDensifyStage({field: "val", range: {step, bounds: "full", unit: unit}}, coll, msg); - // Fill in docs between 1 and 10. + // Fill in docs between 1 and 99. coll.drop(); coll.insert({val: base}); - coll.insert({val: add(base, 10)}); - testDensifyStage(stage, coll); + coll.insert({val: add(base, 99)}); + runDensifyFullTest(); // Negative numbers and dates before the epoch. coll.drop(); - insertDocumentsOnStep({base, min: -10, max: -1, step: 2, addFunc: add, coll: coll}); - testDensifyStage(stage, coll); + insertDocumentsOnStep({base, min: -100, max: -1, step: 2, addFunc: add, coll: coll}); + runDensifyFullTest(); // Lots of off-step documents. coll.drop(); insertDocumentsOnPredicate( - {base, min: 0, max: 10, pred: i => i % 3 == 0 || i % 7 == 0, addFunc: add, coll: coll}); - testDensifyStage(stage, coll); + {base, min: 0, max: 50, pred: i => i % 3 == 0 || i % 7 == 0, addFunc: add, coll: coll}); + runDensifyFullTest(); // Lots of off-step documents with nulls sprinkled in to confirm that a null value is // treated the same as a missing value. @@ -69,17 +69,7 @@ for (let i = 0; i < densifyUnits.length; i++) { coll: coll }); - testDensifyStage(stage, coll); + runDensifyFullTest(); } } - -// Test that full range does not fail if there's only one document in the collection. -coll.drop(); -coll.insert({_id: 1, val: 1, orig: true}); -let result = coll.aggregate([ - {"$densify": {"field": "val", "range": {"step": 2, "bounds": "full"}}}, -]); -const expected = [{_id: 1, val: 1, orig: true}]; -const resultArray = result.toArray(); -assert.sameMembers(resultArray, expected); })(); diff --git a/jstests/aggregation/sources/densify/libs/densify_in_js.js b/jstests/aggregation/sources/densify/libs/densify_in_js.js index 7a919e9afeb..4c92bd572bc 100644 --- a/jstests/aggregation/sources/densify/libs/densify_in_js.js +++ b/jstests/aggregation/sources/densify/libs/densify_in_js.js @@ -2,9 +2,7 @@ * This file implements densification in JavaScript to compare with the output from the $densify * stage. */ - -const densifyUnits = [null, "millisecond", "second", "day", "month", "quarter", "year"]; -const interestingSteps = [1, 2, 3, 4, 5, 7]; +load("jstests/aggregation/extras/utils.js"); // arrayEq /** * The code is made a lot shorter by relying on accessing properties on Date objects with @@ -14,7 +12,7 @@ const interestingSteps = [1, 2, 3, 4, 5, 7]; * @returns functions to immutably add/subtract a specific duration with a date. */ const makeArithmeticHelpers = (unitName, factor) => { - const getTimeInUnits = date => { + const getter = date => { const newDate = new ISODate(date.toISOString()); // Calling the proper function on the passed in date object. If the unitName was "Seconds" // would be equivalent to `newDate.getSeconds()`. @@ -24,16 +22,27 @@ const makeArithmeticHelpers = (unitName, factor) => { // Return a new date with the proper unit adjusted with the second parameter. // Dates and the setter helpers are generally mutable, but this function will make sure // the arithmetic helpers won't mutate their inputs. - const setTimeInUnits = (date, newComponent) => { + const setter = (date, newComponent) => { const newDate = new ISODate(date.toISOString()); newDate["setUTC" + unitName](newComponent); return newDate; }; - const add = (date, step) => setTimeInUnits(date, getTimeInUnits(date) + (step * factor)); - const sub = (date, step) => setTimeInUnits(date, getTimeInUnits(date) - (step * factor)); + const add = (val, step) => setter(val, getter(val) + (step * factor)); + const sub = (val, step) => setter(val, getter(val) - (step * factor)); + + // Explicit ranges always generate on-step relative to the lower-bound of the range, + // this function encapsulates the logic to do that for dates (requires a loop since steps aren't + // always constant sized). + const getNextStepFromBase = (val, base, step) => { + let nextStep = base; + while (nextStep <= val) { + nextStep = add(nextStep, step); + } + return nextStep; + }; - return {add: add, sub: sub}; + return {add: add, sub: sub, getNextStepFromBase: getNextStepFromBase}; }; /** @@ -46,7 +55,7 @@ const getArithmeticFunctionsForUnit = (unitName) => { case "millisecond": return makeArithmeticHelpers("Milliseconds", 1); case "second": - return makeArithmeticHelpers("Seconds", 1); + return makeArithmeticHelpers("Milliseconds", 1000); case "minute": return makeArithmeticHelpers("Minutes", 1); case "hour": @@ -63,7 +72,17 @@ const getArithmeticFunctionsForUnit = (unitName) => { return makeArithmeticHelpers("FullYear", 1); case null: // missing unit means that we're dealing with numbers rather than dates. case undefined: - return {add: (val, step) => val + step, sub: (val, step) => val - step}; + return { + add: (val, step) => val + step, + sub: (val, step) => val - step, + getNextStepFromBase: (val, base, step) => { + let nextStep = base; + while (nextStep <= val) { + nextStep = nextStep + step; + } + return nextStep; + } + }; } }; @@ -87,7 +106,7 @@ function densifyInJS(stage, docs) { }); const docsWithoutNulls = docs.filter(doc => doc[field] != null); - const {add, sub} = getArithmeticFunctionsForUnit(unit); + const {add, sub, getNextStepFromBase} = getArithmeticFunctionsForUnit(unit); function generateDocuments(min, max, pred) { const docs = []; @@ -100,25 +119,15 @@ function densifyInJS(stage, docs) { return docs; } - // Explicit ranges always generate on-step relative to the lower-bound of the range, - // this function encapsulates the logic to do that for dates (requires a loop since steps aren't - // always constant sized). - const getNextStepFromBase = (val, base, step) => { - let nextStep = base; - while (nextStep <= val) { - nextStep = add(nextStep, step); - } - return nextStep; - }; - if (bounds === "full") { if (docs.length == 0) { return stream; } const minValue = docsWithoutNulls[0][field]; const maxValue = docsWithoutNulls[docsWithoutNulls.length - 1][field]; - return densifyInJS({field: stage.field, range: {step, bounds: [minValue, maxValue], unit}}, + return densifyInJS({field: stage.field, range: {step, unit, bounds: [minValue, maxValue]}}, docs); + } else if (bounds === "partition") { throw new Error("Partitioning not supported by JS densify."); } else if (bounds.length == 2) { @@ -169,17 +178,20 @@ const insertDocumentsOnStep = ({base, min, max, step, addFunc, coll, field}) => insertDocumentsOnPredicate( {base, min, max, pred: i => ((i - min) % step) === 0, addFunc, coll, field}); +const densifyUnits = [null, "millisecond", "second", "day", "month", "quarter", "year"]; + +const interestingSteps = [1, 2, 3, 4, 5, 7, 11, 13]; + function buildErrorString(found, expected) { return "Expected:\n" + tojson(expected) + "\nGot:\n" + tojson(found); } -// Assert that densification in JavaScript and the $densify stage output the same documents. -function testDensifyStage(stage, coll, msg = "") { +function testDensifyStage(stage, coll, msg) { if (stage.range.unit === null) { delete stage.range.unit; } const result = coll.aggregate([{"$densify": stage}]).toArray(); const expected = densifyInJS(stage, coll.find({}).toArray()); - const newMsg = msg + " | stage: " + tojson(stage); - assert.sameMembers(expected, result, newMsg + buildErrorString(result, expected)); + const newMsg = (msg || "") + " | stage: " + tojson(stage); + assert(arrayEq(expected, result), newMsg + buildErrorString(result, expected)); } diff --git a/jstests/aggregation/sources/geonear/requires_geo_index.js b/jstests/aggregation/sources/geonear/requires_geo_index.js index b56c24b45ef..cd8a75c4e29 100644 --- a/jstests/aggregation/sources/geonear/requires_geo_index.js +++ b/jstests/aggregation/sources/geonear/requires_geo_index.js @@ -38,8 +38,8 @@ const geonearWithinLookupPipeline = [ }, ]; -assert.commandWorked(coll.insert({_id: 5, x: 5, geo: [1, 1]})); -assert.commandWorked(from.insert({_id: 1, x: 5, geo: [0, 0]})); +assert.commandWorked(coll.insert({_id: 5, x: 5})); +assert.commandWorked(from.insert({_id: 1, geo: [0, 0]})); // Fail without index. assertErrorCode(from, geonearPipeline, ErrorCodes.IndexNotFound); @@ -50,12 +50,4 @@ assert.commandWorked(from.createIndex({geo: "2dsphere"})); // Run successfully when you have the geospatial index. assert.eq(from.aggregate(geonearPipeline).itcount(), 1); assert.eq(coll.aggregate(geonearWithinLookupPipeline).itcount(), 1); - -// Test that we can run a pipeline with a $geoNear stage followed by a $lookup. -const geonearThenLookupPipeline = [ - {$geoNear: {near: [0, 1], distanceField: "distance", spherical: true}}, - {$lookup: {from: from.getName(), localField: "x", foreignField: "x", as: "new"}}, -]; -assert.commandWorked(coll.createIndex({geo: "2dsphere"})); -assert.eq(coll.aggregate(geonearThenLookupPipeline).itcount(), 1); }()); diff --git a/jstests/aggregation/sources/graphLookup/filter.js b/jstests/aggregation/sources/graphLookup/filter.js index 98433c195ba..c43d849a3a5 100644 --- a/jstests/aggregation/sources/graphLookup/filter.js +++ b/jstests/aggregation/sources/graphLookup/filter.js @@ -6,8 +6,8 @@ load("jstests/libs/fixture_helpers.js"); // For isSharded. -let local = db.local; -let foreign = db.foreign; +var local = db.local; +var foreign = db.foreign; local.drop(); foreign.drop(); @@ -21,15 +21,15 @@ if (FixtureHelpers.isSharded(foreign) && !isShardedLookupEnabled) { return; } -let bulk = foreign.initializeUnorderedBulkOp(); -for (let i = 0; i < 100; i++) { +var bulk = foreign.initializeUnorderedBulkOp(); +for (var i = 0; i < 100; i++) { bulk.insert({_id: i, neighbors: [i - 1, i + 1]}); } assert.commandWorked(bulk.execute()); -assert.commandWorked(local.insert([{starting: 0, foo: 1}, {starting: 1, foo: 2}])); +assert.commandWorked(local.insert({starting: 0})); // Assert that the graphLookup only retrieves ten documents, with _id from 0 to 9. -let res = local +var res = local .aggregate({ $graphLookup: { from: "foreign", @@ -69,7 +69,7 @@ assert.commandWorked(foreign.insert({from: 2, to: 3, shouldBeIncluded: true})); // Assert that the $graphLookup stops exploring when it finds a document that doesn't match the // filter. res = local - .aggregate([{ + .aggregate({ $graphLookup: { from: "foreign", startWith: "$starting", @@ -78,14 +78,14 @@ res = local as: "results", restrictSearchWithMatch: {shouldBeIncluded: true} } - }, {$match: {starting: 0}}]) - .toArray(); + }) + .toArray()[0]; -assert.eq(res[0].results.length, 1, tojson(res)); +assert.eq(res.results.length, 1); // $expr is allowed inside the 'restrictSearchWithMatch' match expression. res = local - .aggregate([{ + .aggregate({ $graphLookup: { from: "foreign", startWith: "$starting", @@ -94,14 +94,14 @@ res = local as: "results", restrictSearchWithMatch: {$expr: {$eq: ["$shouldBeIncluded", true]}} } - }, {$match: {starting: 0}}]) - .toArray(); + }) + .toArray()[0]; -assert.eq(res[0].results.length, 1, tojson(res)); +assert.eq(res.results.length, 1); // $expr within `restrictSearchWithMatch` has access to variables declared at a higher level. res = local - .aggregate([{$sort: {starting: 1}}, { + .aggregate([{ $lookup: { from: "local", let : {foo: true}, @@ -115,36 +115,11 @@ res = local restrictSearchWithMatch: {$expr: {$eq: ["$shouldBeIncluded", "$$foo"]}} } - }, {$sort: {starting: 1}}], + }], as: "array" } - }, {$match: {starting: 0}}]) - .toArray(); - -assert.eq(res[0].array[0].results.length, 1, tojson(res)); - -// $graphLookup which references a let variable defined by $lookup should be treated as correlated. -res = local.aggregate([{ - $lookup: { - from: "local", - let : {foo: "$foo"}, - pipeline: [{ - $graphLookup: { - from: "foreign", - startWith: "$starting", - connectFromField: "to", - connectToField: "from", - as: "results", - restrictSearchWithMatch: - {$expr: {$eq: ["$from", "$$foo"]}} - } - }], - as: "array" - } -}, {$sort: {starting: 1}}]).toArray(); -assert.eq(2, res.length); -assert.eq(1, res[1].starting, tojson(res)); -assert.eq(2, res[1].array.length, tojson(res)); -assert.eq(0, res[1].array[0].results.length, tojson(res)); -assert.eq(0, res[1].array[1].results.length, tojson(res)); + }]) + .toArray()[0]; + +assert.eq(res.array[0].results.length, 1); })(); diff --git a/jstests/aggregation/sources/indexStats/verify_index_stats_output.js b/jstests/aggregation/sources/indexStats/verify_index_stats_output.js index 15cc007672d..0b2dbeba8f9 100644 --- a/jstests/aggregation/sources/indexStats/verify_index_stats_output.js +++ b/jstests/aggregation/sources/indexStats/verify_index_stats_output.js @@ -69,7 +69,6 @@ let shardsFound = []; db.getSiblingDB("config").shards.find().forEach(function(shard) { allShards.push(shard._id); }); -const isShardedCluster = !!allShards.length; for (const indexStats of pausedOutput) { assert.hasFields(indexStats, ["building", "spec"]); @@ -85,8 +84,6 @@ for (const indexStats of pausedOutput) { // names of known shards. if (indexStats.hasOwnProperty("shard")) { shardsFound.push(indexStats["shard"]); - } else { - assert(!isShardedCluster); } } diff --git a/jstests/aggregation/sources/lookup/lookup_non_correlated_prefix.js b/jstests/aggregation/sources/lookup/lookup_non_correlated_prefix.js index a5169ced199..4c497a9d5b6 100644 --- a/jstests/aggregation/sources/lookup/lookup_non_correlated_prefix.js +++ b/jstests/aggregation/sources/lookup/lookup_non_correlated_prefix.js @@ -7,8 +7,7 @@ (function() { "use strict"; -load("jstests/libs/fixture_helpers.js"); // For isSharded. -load("jstests/aggregation/extras/utils.js"); // for arrayEq +load("jstests/libs/fixture_helpers.js"); // For isSharded. const testColl = db.lookup_non_correlated_prefix; testColl.drop(); @@ -89,39 +88,6 @@ cursor.toArray().forEach(user => { assert.eq(user['_id'], joinedDocs[0].owner); }); -// Test for a non-correlated prefix followed by a $facet pipeline that contains a correlated -// variable reference. -cursor = testColl.aggregate([ - { - $lookup: { - as: 'items_check', - from: joinColl.getName(), - let : {id: '$_id'}, - pipeline: [ - {$match: {owner: "user_1"}}, - { - $facet: { - all: [{ - $redact: { - $cond: - {if: {$eq: ["$$id", "user_1"]}, then: "$$KEEP", else: "$$PRUNE"} - } - }], - }, - }, - ], - }, - }, -]); -res = cursor.toArray(); -assert( - arrayEq(res, - [ - {"_id": "user_1", "items_check": [{"all": [{"_id": "item_1", "owner": "user_1"}]}]}, - {"_id": "user_2", "items_check": [{"all": []}]} - ]), - res); - // SERVER-57000: Test handling of lack of correlation (addFields with empty set of columns) assert.doesNotThrow(() => testColl.aggregate([ { diff --git a/jstests/aggregation/sources/lookup/lookup_query_stats.js b/jstests/aggregation/sources/lookup/lookup_query_stats.js index 86574988184..afe418ab555 100644 --- a/jstests/aggregation/sources/lookup/lookup_query_stats.js +++ b/jstests/aggregation/sources/lookup/lookup_query_stats.js @@ -236,12 +236,7 @@ let testQueryExecutorStatsWithCollectionScan = function() { checkExplainOutputForAllVerbosityLevels( localColl, fromColl, - { - totalDocsExamined: localDocCount * foreignDocCount, - totalKeysExamined: 0, - collectionScans: localDocCount, - indexesUsed: [] - }, + {totalDocsExamined: 20, totalKeysExamined: 0, collectionScans: 4, indexesUsed: []}, {allowDiskUse: false}); } }; diff --git a/jstests/aggregation/sources/merge/merge_with_dollar_fields.js b/jstests/aggregation/sources/merge/merge_with_dollar_fields.js deleted file mode 100644 index b08383fd73c..00000000000 --- a/jstests/aggregation/sources/merge/merge_with_dollar_fields.js +++ /dev/null @@ -1,132 +0,0 @@ -// Tests $merge over documents with $-field in it.
-//
-// Sharded collections have special requirements on the join field.
-// @tags: [assumes_unsharded_collection]
-
-(function() {
-"use strict";
-
-load("jstests/libs/collection_drop_recreate.js"); // For assertDropCollection.
-
-const sourceName = 'merge_with_dollar_fields_source';
-const source = db[sourceName];
-const targetName = 'merge_with_dollar_fields_target';
-const target = db[targetName];
-
-const joinField = 'joinField';
-const sourceDoc = {
- $dollar: 1,
- joinField
-};
-const targetDoc = {
- a: 1,
- joinField
-};
-assertDropCollection(db, sourceName);
-assert.commandWorked(source.insert(sourceDoc));
-
-function runTest({whenMatched, whenNotMatched}, targetDocs) {
- assertDropCollection(db, targetName);
- assert.commandWorked(target.createIndex({joinField: 1}, {unique: true}));
- assert.commandWorked(target.insert(targetDocs));
- source.aggregate([
- {$project: {_id: 0}},
- {
- $merge: {
- into: targetName,
- on: joinField,
- whenMatched,
- whenNotMatched,
- }
- }
- ]);
- return target.findOne({}, {_id: 0});
-}
-
-function runTestMatched(mode) {
- return runTest(mode, [targetDoc]);
-}
-
-function runTestNotMatched(mode) {
- return runTest(mode, []);
-}
-
-// TODO: SERVER-76999: Currently $merge may throw 'FailedToParse' error due to non-local updates.
-// We should return consistent results for dollar field documents.
-
-// whenMatched: 'replace', whenNotMatched: 'insert'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'replace', whenNotMatched: 'insert'}),
- [ErrorCodes.DollarPrefixedFieldName, ErrorCodes.FailedToParse]);
-
-try {
- assert.docEq(sourceDoc, runTestNotMatched({whenMatched: 'replace', whenNotMatched: 'insert'}));
-} catch (error) {
- assert.commandFailedWithCode(error, ErrorCodes.FailedToParse);
-}
-
-// whenMatched: 'replace', whenNotMatched: 'fail'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'replace', whenNotMatched: 'fail'}),
- [ErrorCodes.DollarPrefixedFieldName, ErrorCodes.FailedToParse]);
-
-assert.throwsWithCode(() => runTestNotMatched({whenMatched: 'replace', whenNotMatched: 'fail'}),
- [ErrorCodes.MergeStageNoMatchingDocument, ErrorCodes.FailedToParse]);
-
-// whenMatched: 'replace', whenNotMatched: 'discard'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'replace', whenNotMatched: 'discard'}),
- [ErrorCodes.DollarPrefixedFieldName, ErrorCodes.FailedToParse]);
-
-try {
- assert.eq(null, runTestNotMatched({whenMatched: 'replace', whenNotMatched: 'discard'}));
-} catch (error) {
- assert.commandFailedWithCode(error, ErrorCodes.FailedToParse);
-}
-
-// whenMatched: 'merge', whenNotMatched: 'insert'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'merge', whenNotMatched: 'insert'}),
- ErrorCodes.DollarPrefixedFieldName);
-
-assert.docEq(sourceDoc, runTestNotMatched({whenMatched: 'merge', whenNotMatched: 'insert'}));
-
-// whenMatched: 'merge', whenNotMatched: 'fail'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'merge', whenNotMatched: 'fail'}),
- ErrorCodes.DollarPrefixedFieldName);
-
-assert.throwsWithCode(() => runTestNotMatched({whenMatched: 'merge', whenNotMatched: 'fail'}),
- ErrorCodes.MergeStageNoMatchingDocument);
-
-// whenMatched: 'merge', whenNotMatched: 'discard'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'merge', whenNotMatched: 'discard'}),
- ErrorCodes.DollarPrefixedFieldName);
-
-assert.eq(null, runTestNotMatched({whenMatched: 'merge', whenNotMatched: 'discard'}));
-
-// whenMatched: 'keepExisting', whenNotMatched: 'insert'
-assert.docEq(targetDoc, runTestMatched({whenMatched: 'keepExisting', whenNotMatched: 'insert'}));
-
-assert.docEq(sourceDoc, runTestNotMatched({whenMatched: 'keepExisting', whenNotMatched: 'insert'}));
-
-// whenMatched: 'fail', whenNotMatched: 'insert'
-assert.throwsWithCode(() => runTestMatched({whenMatched: 'fail', whenNotMatched: 'insert'}),
- ErrorCodes.DuplicateKey);
-
-assert.docEq(sourceDoc, runTestNotMatched({whenMatched: 'fail', whenNotMatched: 'insert'}));
-
-// whenMatched: 'pipeline', whenNotMatched: 'insert'
-const pipeline = [{$addFields: {b: 1}}];
-const targetDocAddFields = Object.assign({}, targetDoc, {b: 1});
-assert.docEq(targetDocAddFields, runTestMatched({whenMatched: pipeline, whenNotMatched: 'insert'}));
-
-assert.docEq(sourceDoc, runTestNotMatched({whenMatched: pipeline, whenNotMatched: 'insert'}));
-
-// whenMatched: 'pipeline', whenNotMatched: 'fail'
-assert.docEq(targetDocAddFields, runTestMatched({whenMatched: pipeline, whenNotMatched: 'fail'}));
-
-assert.throwsWithCode(() => runTestNotMatched({whenMatched: pipeline, whenNotMatched: 'fail'}),
- ErrorCodes.MergeStageNoMatchingDocument);
-
-// whenMatched: 'pipeline', whenNotMatched: 'discard'
-assert.docEq(targetDocAddFields,
- runTestMatched({whenMatched: pipeline, whenNotMatched: 'discard'}));
-
-assert.eq(null, runTestNotMatched({whenMatched: pipeline, whenNotMatched: 'discard'}));
-}());
diff --git a/jstests/aggregation/sources/merge/mode_merge_fail.js b/jstests/aggregation/sources/merge/mode_merge_fail.js index fcc13f8e871..7235c8e1c7e 100644 --- a/jstests/aggregation/sources/merge/mode_merge_fail.js +++ b/jstests/aggregation/sources/merge/mode_merge_fail.js @@ -97,13 +97,9 @@ const pipeline = [mergeStage]; // and updated. (function testMergeUnorderedBatchUpdate() { const maxBatchSize = 16 * 1024 * 1024; // 16MB - - // Each document is just under 1MB in order to allow for some extra space for writes that need - // to be serialized over the wire in certain cluster configurations. Otherwise, the number of - // modified/unmodified documents would be off by one depending on how our cluster is configured. - const docSize = 1024 * 1023; + const docSize = 1024 * 1024; // 1MB const numDocs = 20; - const maxDocsInBatch = Math.floor(maxBatchSize / docSize); + const maxDocsInBatch = maxBatchSize / docSize; assert(source.drop()); dropWithoutImplicitRecreate(target.getName()); diff --git a/jstests/aggregation/sources/merge/mode_replace_fail.js b/jstests/aggregation/sources/merge/mode_replace_fail.js index cc3df1b1ce7..88582238c8f 100644 --- a/jstests/aggregation/sources/merge/mode_replace_fail.js +++ b/jstests/aggregation/sources/merge/mode_replace_fail.js @@ -90,13 +90,9 @@ const pipeline = [mergeStage]; // and updated. (function testMergeUnorderedBatchUpdate() { const maxBatchSize = 16 * 1024 * 1024; // 16MB - - // Each document is just under 1MB in order to allow for some extra space for writes that need - // to be serialized over the wire in certain cluster configurations. Otherwise, the number of - // modified/unmodified documents would be off by one depending on how our cluster is configured. - const docSize = 1024 * 1023; // 1MB + const docSize = 1024 * 1024; // 1MB const numDocs = 20; - const maxDocsInBatch = Math.floor(maxBatchSize / docSize); + const maxDocsInBatch = maxBatchSize / docSize; assert(source.drop()); dropWithoutImplicitRecreate(target.getName()); diff --git a/jstests/aggregation/sources/multiple_unpack_bucket_error.js b/jstests/aggregation/sources/multiple_unpack_bucket_error.js index b00c6265f9b..5c835b5e16e 100644 --- a/jstests/aggregation/sources/multiple_unpack_bucket_error.js +++ b/jstests/aggregation/sources/multiple_unpack_bucket_error.js @@ -13,48 +13,36 @@ coll.drop(); assert.commandFailedWithCode(db.runCommand({ aggregate: coll.getName(), pipeline: [ - { - $_internalUnpackBucket: - {exclude: [], timeField: 'time', bucketMaxSpanSeconds: NumberInt(3600)} - }, - { - $_internalUnpackBucket: - {exclude: [], timeField: 'time', bucketMaxSpanSeconds: NumberInt(3600)} - } + {$_internalUnpackBucket: {exclude: [], timeField: 'time', bucketMaxSpanSeconds: 3600}}, + {$_internalUnpackBucket: {exclude: [], timeField: 'time', bucketMaxSpanSeconds: 3600}} ], cursor: {} }), - 7183900); + 5348302); // $_unpackBucket is an alias of $_internalUnpackBucket, the same restriction should apply. assert.commandFailedWithCode(db.runCommand({ aggregate: coll.getName(), pipeline: [ {$_unpackBucket: {timeField: 'time'}}, - { - $_internalUnpackBucket: - {exclude: [], timeField: 'time', bucketMaxSpanSeconds: NumberInt(3600)} - } + {$_internalUnpackBucket: {exclude: [], timeField: 'time', bucketMaxSpanSeconds: 3600}} ], cursor: {} }), - 7183900); + 5348302); assert.commandFailedWithCode(db.runCommand({ aggregate: coll.getName(), pipeline: [ - { - $_internalUnpackBucket: - {exclude: [], timeField: 'time', bucketMaxSpanSeconds: NumberInt(3600)} - }, + {$_internalUnpackBucket: {exclude: [], timeField: 'time', bucketMaxSpanSeconds: 3600}}, {$_unpackBucket: {timeField: 'time'}} ], cursor: {} }), - 7183900); + 5348302); assert.commandFailedWithCode(db.runCommand({ aggregate: coll.getName(), pipeline: [{$_unpackBucket: {timeField: 'time'}}, {$_unpackBucket: {timeField: 'time'}}], cursor: {} }), - 7183900); + 5348302); })(); diff --git a/jstests/aggregation/sources/search_stage_error.js b/jstests/aggregation/sources/search_stage_error.js deleted file mode 100644 index d85631cd999..00000000000 --- a/jstests/aggregation/sources/search_stage_error.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Verify that the $search stage errors correctly if enterprise is not enabled. - * @tags: [ - * # $search/$searchMeta cannot be used within a facet - * do_not_wrap_aggregations_in_facets, - * # $search/$searchMeta do not support any read concern other than "local" - * assumes_read_concern_unchanged - * ] - */ -(function() { -"use strict"; - -const coll = db.searchCollector; -coll.drop(); -const buildInfo = assert.commandWorked(db.runCommand({"buildInfo": 1})); -if (buildInfo["modules"].includes("enterprise")) { - // This is a test of behavior without enterprise. - return; -} -assert.commandWorked(coll.insert({"_id": 1, "title": "cakes"})); - -// Check that a query with a $search stage errors without enterprise. -assert.commandFailedWithCode( - db.runCommand({aggregate: coll.getName(), cursor: {}, pipeline: [{$search: {}}]}), [6047401]); - -// Check that a query with a $searchMeta stage errors without enterprise. -assert.commandFailedWithCode( - db.runCommand({aggregate: coll.getName(), cursor: {}, pipeline: [{$searchMeta: {}}]}), - [6047401]); - -// Check that a query with a $listSearchIndexes stage errors without enterprise. -assert.commandFailedWithCode( - coll.runCommand({aggregate: coll.getName(), pipeline: [{$listSearchIndexes: {}}], cursor: {}}), - [6047401]); - -// Check that a query with a $vectorSearch stage errors without enterprise. -assert.commandFailedWithCode( - coll.runCommand({aggregate: coll.getName(), cursor: {}, pipeline: [{$vectorSearch: {}}]}), - [6047401]); -})(); diff --git a/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js b/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js index 4dd4cc90565..dbe509e2c59 100644 --- a/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js +++ b/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js @@ -65,7 +65,6 @@ const windows = { // The list of sort definitions to test. const sortBys = { none: null, - expr: {partitionSeq: {$meta: "randVal"}}, asc: {partitionSeq: 1}, desc: {partitionSeq: -1}, asc_date: {date: 1}, @@ -105,11 +104,18 @@ function constructQuery(wf, window, sortBy, partitionBy) { } // Given an element of each of the lists above, what is the expected -// result. The output should be 'OK' or the expected integer +// result. The output should be 'SKIP', 'OK' or the expected integer // error code. function expectedResult(wfType, windowType, sortType, partitionType) { // Static errors all come first. + // Skip range windows over dates or that are over descending windows. + if (windowType.endsWith('range')) { + if (sortType.endsWith('date') || sortType.startsWith('desc')) { + return 'SKIP'; + } + } + // Derivative and integral require an ascending sort // and an explicit window. if (wfType.startsWith('derivative')) { @@ -123,11 +129,6 @@ function expectedResult(wfType, windowType, sortType, partitionType) { return ErrorCodes.FailedToParse; } - // '$derivative requires a non-expression sortBy'. - if (sortType == "expr") { - return ErrorCodes.FailedToParse; - } - } else if (wfType.startsWith('integral')) { // Integral requires a sort. if (sortType == 'none') { @@ -139,11 +140,6 @@ function expectedResult(wfType, windowType, sortType, partitionType) { return ErrorCodes.FailedToParse; } - // '$integral requires a non-expression sortBy'. - if (sortType == "expr") { - return ErrorCodes.FailedToParse; - } - } else if (wfType.startsWith('expMovingAvg')) { // $expMovingAvg doesn't accept a window. if (windowType != 'none') { @@ -183,23 +179,9 @@ function expectedResult(wfType, windowType, sortType, partitionType) { } // Range based windows require a sort over a single field. - if (windowType.endsWith('range')) { - if (sortType == 'none' || sortType == 'multi') { - // 'Range-based window require sortBy a single field'. - return 5339902; - } - if (sortType == 'expr') { - // 'Range-based bounds require a non-expression sortBy' - return 8947400; - } - if (sortType.startsWith('desc')) { - // 'Range-based bounds require an ascending sortBy'. - return 8947401; - } - if (sortType.endsWith('date') && !partitionType.endsWith('array')) { - // 'For windows that involve date or time ranges, a unit must be provided.' - return 5429413; - } + if (windowType.endsWith('range') && (sortType == 'none' || sortType == 'multi')) { + // 'Range-based window require sortBy a single field'. + return 5339902; } if (partitionType === 'static_array') { @@ -256,6 +238,10 @@ function* makeTests() { expectedResult: expectedResult(wfType, windowType, sortType, partitionType) }; + if (test.expectedResult == 'SKIP') { + continue; + } + yield test; } } @@ -265,16 +251,13 @@ function* makeTests() { // Run all the combinations generated in makeTests. for (const test of makeTests()) { - const errorMsg = "Command was: " + tojson(test.query); if (test.expectedResult == ErrorCodes.OK) { assert.commandWorked( - coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}}), - errorMsg); + coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}})); } else { assert.commandFailedWithCode( coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}}), - test.expectedResult, - errorMsg); + test.expectedResult); } } })(); diff --git a/jstests/aggregation/sources/setWindowFields/derivative.js b/jstests/aggregation/sources/setWindowFields/derivative.js index a57af814b30..b457a1fa097 100644 --- a/jstests/aggregation/sources/setWindowFields/derivative.js +++ b/jstests/aggregation/sources/setWindowFields/derivative.js @@ -192,9 +192,9 @@ function explainUnit(unit) { return coll.runCommand( {explain: {aggregate: coll.getName(), cursor: {}, pipeline: [derivativeStage(unit)]}}); } -assert.commandFailedWithCode(explainUnit('year'), 5490710); -assert.commandFailedWithCode(explainUnit('quarter'), 5490710); -assert.commandFailedWithCode(explainUnit('month'), 5490710); +assert.commandFailedWithCode(explainUnit('year'), 5490704); +assert.commandFailedWithCode(explainUnit('quarter'), 5490704); +assert.commandFailedWithCode(explainUnit('month'), 5490704); assert.commandWorked(explainUnit('week')); assert.commandWorked(explainUnit('day')); assert.commandWorked(explainUnit('hour')); diff --git a/jstests/aggregation/sources/setWindowFields/explain.js b/jstests/aggregation/sources/setWindowFields/explain.js index cae112cb9e5..a070aae7b0e 100644 --- a/jstests/aggregation/sources/setWindowFields/explain.js +++ b/jstests/aggregation/sources/setWindowFields/explain.js @@ -16,8 +16,9 @@ coll.drop(); const bigStr = Array(1025).toString(); // 1KB of ',' const nDocs = 1000; const nPartitions = 50; -// Size was found through logging in 'SpillableCache' class. -const docSize = 1292; +// Initial docSize is 1292, after fields are loaded into Document's cache they are 2332. +// Not const because post-cache doc size changes based on the number of fields accessed. +let docSize = 2332; let bulk = coll.initializeUnorderedBulkOp(); for (let i = 1; i <= nDocs; i++) { @@ -137,6 +138,8 @@ function checkExplainResult(pipeline, expectedFunctionMemUsages, expectedTotalMe // The partition iterator will only hold five documents at once. After they are added to the // removable document executor they will be released. const numDocsHeld = 5; + // This test accesses fewer fields, reduce docSize accordingly. + docSize = 1292; let pipeline = [ { $setWindowFields: { diff --git a/jstests/aggregation/sources/setWindowFields/integral.js b/jstests/aggregation/sources/setWindowFields/integral.js index bb0e962c504..a62033e2b4f 100644 --- a/jstests/aggregation/sources/setWindowFields/integral.js +++ b/jstests/aggregation/sources/setWindowFields/integral.js @@ -115,9 +115,9 @@ function explainUnit(unit) { } }); } -assert.commandFailedWithCode(explainUnit('year'), 5490710); -assert.commandFailedWithCode(explainUnit('quarter'), 5490710); -assert.commandFailedWithCode(explainUnit('month'), 5490710); +assert.commandFailedWithCode(explainUnit('year'), 5490704); +assert.commandFailedWithCode(explainUnit('quarter'), 5490704); +assert.commandFailedWithCode(explainUnit('month'), 5490704); assert.commandWorked(explainUnit('week')); assert.commandWorked(explainUnit('day')); assert.commandWorked(explainUnit('hour')); diff --git a/jstests/aggregation/sources/setWindowFields/memory_limit.js b/jstests/aggregation/sources/setWindowFields/memory_limit.js index a88717d0331..e64781d3eca 100644 --- a/jstests/aggregation/sources/setWindowFields/memory_limit.js +++ b/jstests/aggregation/sources/setWindowFields/memory_limit.js @@ -61,10 +61,6 @@ assert.commandWorked(coll.runCommand({ allowDiskUse: false })); -setParameterOnAllHosts(nonConfigNodes, - "internalDocumentSourceSetWindowFieldsMaxMemoryBytes", - (perDocSize * docsPerPartition) + 1024); - // Test that the query fails with a window function that stores documents. assert.commandFailedWithCode(coll.runCommand({ aggregate: coll.getName(), @@ -78,7 +74,7 @@ assert.commandFailedWithCode(coll.runCommand({ cursor: {}, allowDiskUse: false }), - [5643011, 5414201]); + 5414201); // Reset limit for other tests. setParameterOnAllHosts( nonConfigNodes, "internalDocumentSourceSetWindowFieldsMaxMemoryBytes", 100 * 1024 * 1024); diff --git a/jstests/aggregation/sources/setWindowFields/output_overwrites_existing_data.js b/jstests/aggregation/sources/setWindowFields/output_overwrites_existing_data.js index 3ec0290054f..8301f78bbfc 100644 --- a/jstests/aggregation/sources/setWindowFields/output_overwrites_existing_data.js +++ b/jstests/aggregation/sources/setWindowFields/output_overwrites_existing_data.js @@ -9,6 +9,8 @@ (function() { "use strict"; +// TODO SERVER-63811 Ensure the database exists so we get back non-empty results even in a sharded +// cluster. assert.commandWorked(db[jsTestName()].insert({dummy: 1})); let windowResults = db.aggregate([ diff --git a/jstests/aggregation/sources/setWindowFields/range_wrong_type.js b/jstests/aggregation/sources/setWindowFields/range_wrong_type.js deleted file mode 100644 index 26001c40888..00000000000 --- a/jstests/aggregation/sources/setWindowFields/range_wrong_type.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Test that a window of the form [+N, unbounded] does not trigger a tassert - * on mixed-type input. - * - * Originally intended to reproduce SERVER-71387. - */ -(function() { -"use strict"; - -const coll = db.set_window_fields_range_wrong_type; -coll.drop(); -assert.commandWorked(coll.insert([ - // Numbers sort before strings, so we'll scan {a: 2} first. - {a: 2}, - {a: 'xyz'}, -])); - -const err = assert.throws(() => { - return coll - .aggregate({ - $setWindowFields: { - sortBy: {a: 1}, - output: { - // The lower bound +3 excludes the current document {a: 2}. - // The only remaining document is {a: 'xyz'}. - // We wrongly consider {a: 'xyz'} to be 'within' the lower bound. - // Then, when we search for the upper bound, we are surprised - // to be starting from 'xyz' which is the wrong type. - b: {$max: 5, window: {range: [+3, 'unbounded']}}, - }, - } - }) - .toArray(); -}); -assert.eq(err.code, 5429414, err); -})(); diff --git a/jstests/aggregation/sources/setWindowFields/spill_to_disk.js b/jstests/aggregation/sources/setWindowFields/spill_to_disk.js index b006c1e66d2..24d03721d03 100644 --- a/jstests/aggregation/sources/setWindowFields/spill_to_disk.js +++ b/jstests/aggregation/sources/setWindowFields/spill_to_disk.js @@ -24,12 +24,12 @@ const coll = db[jsTestName()]; coll.drop(); // Doc size was found through logging the size in the SpillableCache. Partition sizes were chosen // arbitrarily. -let avgDocSize = 171; +let avgDocSize = 274; let smallPartitionSize = 6; let largePartitionSize = 21; setParameterOnAllHosts(DiscoverTopology.findNonConfigNodes(db.getMongo()), "internalDocumentSourceSetWindowFieldsMaxMemoryBytes", - avgDocSize * smallPartitionSize + 50); + avgDocSize * smallPartitionSize + 1); seedWithTickerData(coll, 10); diff --git a/jstests/aggregation/sources/shred_documents.js b/jstests/aggregation/sources/shred_documents.js deleted file mode 100644 index 3705cda6391..00000000000 --- a/jstests/aggregation/sources/shred_documents.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Test $_internalShredDocuments parsing and make sure it doesn't modify documents. - * this test assumes {$meta: "indexKey"} will not be missing. - * @tags: [ - * do_not_wrap_aggregations_in_facets - * ] - */ -"use strict"; - -load("jstests/aggregation/extras/utils.js"); - -(function() { -const coll = db[jsTestName()]; -coll.insertMany( - [{a: 1, obj: {a: 1}, arr: [{a: 1}]}, {a: 2, obj: {a: 1}, arr: [{a: 1}]}, {}, {a: 3}]); -coll.createIndex({a: 1}); -assert.commandFailedWithCode(assert.throws(() => coll.aggregate({$_internalShredDocuments: 1})), - 7997500); -assert.commandFailedWithCode( - assert.throws(() => coll.aggregate( - {$_internalShredDocuments: {burnEvidence: true, paperType: "legal"}})), - 7997501); -const assertNoop = (pipeline, position) => { - const expected = coll.aggregate(pipeline).toArray(); - pipeline.splice(position, 0, {$_internalShredDocuments: {}}); - const actual = coll.aggregate(pipeline).toArray(); - assertArrayEq({expected, actual}); -}; -assertNoop([], 0); -assertNoop([{$project: {a: 1}}], 1); -assertNoop([{$addFields: {b: 2, "obj.b": 2, "arr.0.b": 2}}], 1); - -const addFieldGroup = [ - {$addFields: {b: 2, "obj.b": 2, "arr.0.b": 2}}, - {$group: {_id: {a: "$a", b: "$b", obj: "$obj"}}} -]; -assertNoop(addFieldGroup, 0); -assertNoop(addFieldGroup, 1); -assertNoop(addFieldGroup, 2); - -const matchExcludeGroup = - [{$match: {a: 1}}, {$project: {obj: 0}}, {$group: {_id: {a: "$a", b: "$b", obj: "$obj"}}}]; -assertNoop(matchExcludeGroup, 0); -assertNoop(matchExcludeGroup, 1); -assertNoop(matchExcludeGroup, 2); -assertNoop(matchExcludeGroup, 3); - -assertNoop([{$match: {a: 1}}, {$addFields: {key: {$meta: "indexKey"}}}], 1); - -// The shred() function is also used by set windowFields so lets test that too. -const res = coll.aggregate([ - {$match: {a: 1}}, - {$addFields: {key: {$meta: "indexKey"}}}, - {$setWindowFields: {sortBy: {a: 1}, output: {w: {$rank: {}}}}}, - {$limit: 1} - ]) - .toArray(); -assert.eq(1, res.length); -assert(res[0].hasOwnProperty("key")); -assert.eq({a: 1}, res[0]["key"]); -})(); diff --git a/jstests/aggregation/sources/unionWith/unionWith_explain.js b/jstests/aggregation/sources/unionWith/unionWith_explain.js index a600b7fbe0f..60d6a7ae4a4 100644 --- a/jstests/aggregation/sources/unionWith/unionWith_explain.js +++ b/jstests/aggregation/sources/unionWith/unionWith_explain.js @@ -105,18 +105,8 @@ function assertExplainEq(union, regular) { executionStatsIngoredFields), buildErrorString(unionStats, regularStats, "executionStages")); } else if ("stages" in regular) { - // For explains run with the runCommand({explain: ...}) format. - if (regular.stages.length > 1 && "$cursor" in regular.stages[0] && - "executionStats" in regular.stages[0]["$cursor"]) { - assert( - arrayEqWithIgnoredFields(union, - regular.stages, - [...stagesIgnoredFields, ...executionStatsIngoredFields]), - buildErrorString(union, regular, "stages with executionStats")); - } else { - assert(arrayEqWithIgnoredFields(union, regular.stages, stagesIgnoredFields), - buildErrorString(union, regular, "stages")); - } + assert(arrayEqWithIgnoredFields(union, regular.stages, stagesIgnoredFields), + buildErrorString(union, regular, "stages")); } else if ("queryPlanner" in regular) { assert.eq(union.length, 1, "Expected single union stage"); const unionCursor = union[0].$cursor; @@ -133,7 +123,7 @@ function assertExplainEq(union, regular) { function assertExplainMatch(unionExplain, regularExplain) { const unionStage = getUnionWithStage(unionExplain); - assert(unionStage, unionExplain); + assert(unionStage); const unionSubExplain = unionStage.$unionWith.pipeline; assertExplainEq(unionSubExplain, regularExplain); } @@ -143,20 +133,6 @@ function testPipeline(pipeline) { {explain: true}); let queryResult = collB.aggregate(pipeline, {explain: true}); assertExplainMatch(unionResult, queryResult); - - // Alternative explain invocation. This is a regression test for SERVER-89344. - if (!FixtureHelpers.isMongos(db)) { - unionResult = db.runCommand({ - explain: { - "aggregate": collA.getName(), - "pipeline": [{$unionWith: {coll: collB.getName(), pipeline: pipeline}}], - "cursor": {} - } - }); - queryResult = db.runCommand( - {explain: {"aggregate": collB.getName(), "pipeline": pipeline, "cursor": {}}}); - assertExplainMatch(unionResult, queryResult); - } } testPipeline([{$addFields: {bump: true}}]); diff --git a/jstests/aggregation/spill_to_disk.js b/jstests/aggregation/spill_to_disk.js index 568d973e988..e89a1a8ef31 100644 --- a/jstests/aggregation/spill_to_disk.js +++ b/jstests/aggregation/spill_to_disk.js @@ -10,7 +10,6 @@ // @tags: [ // requires_collstats, // requires_pipeline_optimization, -// requires_persistence, // ] (function() { 'use strict'; @@ -24,37 +23,11 @@ load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. const coll = db.spill_to_disk; coll.drop(); -// Sets the set parameter named 'paramName' to the given 'memoryLimit' on each primary node in the -// cluster, and returns the old value. -function setMemoryParamHelper(paramName, memoryLimit) { - const commandResArr = FixtureHelpers.runCommandOnEachPrimary({ - db: db.getSiblingDB("admin"), - cmdObj: { - setParameter: 1, - [paramName]: memoryLimit, - } - }); - assert.gt(commandResArr.length, 0, "Setting memory limit on primaries failed"); - const oldMemoryLimit = assert.commandWorked(commandResArr[0]).was; - return oldMemoryLimit; -} - -// Verifies that the given 'groupStats' (an extract from SBE "executionStats" explain output) show -// evidence of spilling to disk. -function assertSpillingOccurredInSbeExplain(groupStats) { - assert(groupStats); - assert(groupStats.hasOwnProperty("usedDisk"), groupStats); - assert(groupStats.usedDisk, groupStats); - assert.gt(groupStats.numSpills, 0, groupStats); - assert.gt(groupStats.spilledRecords, 0, groupStats); - assert.gt(groupStats.spilledDataStorageSize, 0, groupStats); -} - const sharded = FixtureHelpers.isSharded(coll); const memoryLimitMB = sharded ? 200 : 100; -const isSbeEnabled = checkSBEEnabled(db); +const isSBELookupEnabled = checkSBEEnabled(db, ["featureFlagSBELookupPushdown"]); const bigStr = Array(1024 * 1024 + 1).toString(); // 1MB of ',' for (let i = 0; i < memoryLimitMB + 1; i++) @@ -77,7 +50,7 @@ function test({pipeline, expectedCodes, canSpillToDisk}) { assert.eq(new DBCommandCursor(coll.getDB(), res).itcount(), coll.count()); // all tests output one doc per input doc - if (isSbeEnabled) { + if (isSBELookupEnabled) { const explain = db.runCommand({ explain: {aggregate: coll.getName(), pipeline: pipeline, cursor: {}, allowDiskUse: true} @@ -86,7 +59,11 @@ function test({pipeline, expectedCodes, canSpillToDisk}) { if (hashAggGroups.length > 0) { assert.eq(hashAggGroups.length, 1, explain); const hashAggGroup = hashAggGroups[0]; - assertSpillingOccurredInSbeExplain(hashAggGroup); + assert(hashAggGroup, explain); + assert(hashAggGroup.hasOwnProperty("usedDisk"), hashAggGroup); + assert(hashAggGroup.usedDisk, hashAggGroup); + assert.gt(hashAggGroup.spilledRecords, 0, hashAggGroup); + assert.gt(hashAggGroup.spilledBytesApprox, 0, hashAggGroup); } } } else { @@ -232,178 +209,6 @@ for (const op of ['$firstN', '$lastN', '$minN', '$maxN', '$topN', '$bottomN']) { // don't leave large collection laying around assert(coll.drop()); -// Test spilling to disk for various accumulators in a $group stage . The data has 5 groups of 10 -// documents each. We configure a low memory limit for SBE's hash aggregation stage in order to -// encourage spilling. -const numGroups = 5; -const docsPerGroup = 10; -let counter = 0; -for (let i = 0; i < numGroups; ++i) { - for (let j = 0; j < docsPerGroup; ++j) { - const doc = { - _id: counter++, - a: i, - b: 100 * i + j, - c: 100 * i + j % 5, - obj: {a: i, b: j}, - random: Math.random() - }; - assert.commandWorked(coll.insert(doc)); - } -} - -function setHashGroupMemoryParameters(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 results = coll.aggregate(pipeline).toArray(); - - if (ignoreArrayOrder) { - assert(arrayEq(results, expectedOutput)); - } else { - assert.eq(results, expectedOutput); - } - - if (isSbeEnabled) { - const explain = coll.explain("executionStats").aggregate(pipeline); - const groupStages = getSbePlanStages(explain, "group"); - assert.eq(groupStages.length, 1, groupStages); - assertSpillingOccurredInSbeExplain(groupStages[0]); - } -} - -function testSpillingForVariousAccumulators() { - testAccumulator({ - accumulator: {$first: "$b"}, - sortInputBy: "_id", - expectedOutput: [ - {_id: 0, acc: 0}, - {_id: 1, acc: 100}, - {_id: 2, acc: 200}, - {_id: 3, acc: 300}, - {_id: 4, acc: 400} - ] - - }); - - testAccumulator({ - accumulator: {$last: "$b"}, - sortInputBy: "_id", - expectedOutput: [ - {_id: 0, acc: 9}, - {_id: 1, acc: 109}, - {_id: 2, acc: 209}, - {_id: 3, acc: 309}, - {_id: 4, acc: 409} - ] - }); - - testAccumulator({ - accumulator: {$min: "$b"}, - sortInputBy: "random", - expectedOutput: [ - {_id: 0, acc: 0}, - {_id: 1, acc: 100}, - {_id: 2, acc: 200}, - {_id: 3, acc: 300}, - {_id: 4, acc: 400} - ] - }); - - testAccumulator({ - accumulator: {$max: "$b"}, - sortInputBy: "random", - expectedOutput: [ - {_id: 0, acc: 9}, - {_id: 1, acc: 109}, - {_id: 2, acc: 209}, - {_id: 3, acc: 309}, - {_id: 4, acc: 409} - ] - }); - - testAccumulator({ - accumulator: {$sum: "$b"}, - sortInputBy: "random", - expectedOutput: [ - {_id: 0, acc: 45}, - {_id: 1, acc: 1045}, - {_id: 2, acc: 2045}, - {_id: 3, acc: 3045}, - {_id: 4, acc: 4045} - ] - }); - - testAccumulator({ - accumulator: {$avg: "$b"}, - sortInputBy: "random", - expectedOutput: [ - {_id: 0, acc: 4.5}, - {_id: 1, acc: 104.5}, - {_id: 2, acc: 204.5}, - {_id: 3, acc: 304.5}, - {_id: 4, acc: 404.5} - ] - }); - - testAccumulator({ - accumulator: {$addToSet: "$c"}, - sortInputBy: "random", - expectedOutput: [ - {_id: 0, acc: [0, 1, 2, 3, 4]}, - {_id: 1, acc: [100, 101, 102, 103, 104]}, - {_id: 2, acc: [200, 201, 202, 203, 204]}, - {_id: 3, acc: [300, 301, 302, 303, 304]}, - {_id: 4, acc: [400, 401, 402, 403, 404]}, - ], - // Since the accumulator produces sets, the resulting arrays may be in any order. - ignoreArrayOrder: true, - }); - - testAccumulator({ - accumulator: {$push: "$c"}, - sortInputBy: "_id", - expectedOutput: [ - {_id: 0, acc: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]}, - {_id: 1, acc: [100, 101, 102, 103, 104, 100, 101, 102, 103, 104]}, - {_id: 2, acc: [200, 201, 202, 203, 204, 200, 201, 202, 203, 204]}, - {_id: 3, acc: [300, 301, 302, 303, 304, 300, 301, 302, 303, 304]}, - {_id: 4, acc: [400, 401, 402, 403, 404, 400, 401, 402, 403, 404]}, - ], - }); - - testAccumulator({ - accumulator: {$mergeObjects: "$obj"}, - sortInputBy: "_id", - expectedOutput: [ - {_id: 0, acc: {a: 0, b: 9}}, - {_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}} - ], - }); -} - -(function() { -const kMemLimit = 100; -let oldMemSettings = setHashGroupMemoryParameters(kMemLimit); -try { - testSpillingForVariousAccumulators(); -} finally { - setHashGroupMemoryParameters(oldMemSettings); -} -})(); - -assert(coll.drop()); - // Test spill to disk for $lookup const localColl = db.lookup_spill_local_hj; const foreignColl = db.lookup_spill_foreign_hj; @@ -416,8 +221,16 @@ function setupCollections(localRecords, foreignRecords, foreignField) { } function setHashLookupParameters(memoryLimit) { - return setMemoryParamHelper( - "internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill", memoryLimit); + const commandResArr = FixtureHelpers.runCommandOnEachPrimary({ + db: db.getSiblingDB("admin"), + cmdObj: { + setParameter: 1, + internalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill: memoryLimit, + } + }); + assert.gt(commandResArr.length, 0, "Setting memory limit on primaries failed."); + const oldMemoryLimit = assert.commandWorked(commandResArr[0]).was; + return oldMemoryLimit; } /** @@ -445,7 +258,7 @@ function runTest_MultipleLocalForeignRecords({ const results = localColl.aggregate(pipeline, {allowDiskUse: true}).toArray(); const explain = localColl.explain('executionStats').aggregate(pipeline, {allowDiskUse: true}); // If sharding is enabled, '$lookup' is not pushed down to SBE. - if (isSbeEnabled && !sharded) { + if (isSBELookupEnabled && !sharded) { const hLookups = getSbePlanStages(explain, 'hash_lookup'); assert.eq(hLookups.length, 1, explain); const hLookup = hLookups[0]; diff --git a/jstests/aggregation/unwind_sort.js b/jstests/aggregation/unwind_sort.js deleted file mode 100644 index 858320bec0d..00000000000 --- a/jstests/aggregation/unwind_sort.js +++ /dev/null @@ -1,27 +0,0 @@ -// Test that we can sort on fields, produces by unwind - -const coll = db.agg_unwind_sort; -coll.drop(); -assert.commandWorked(coll.insertOne({a: [3, 4, 5]})); -assert.commandWorked(coll.insertOne({a: [1, 2]})); - -let result = coll.aggregate([{$unwind: "$a"}, {$sort: {a: 1}}]).toArray(); -assert.eq([1, 2, 3, 4, 5], result.map(function(z) { - return z.a; -})); -result = coll.aggregate([{$unwind: "$a"}, {$sort: {a: -1}}]).toArray(); -assert.eq([5, 4, 3, 2, 1], result.map(function(z) { - return z.a; -})); -result = - coll.aggregate([{$unwind: {path: "$a", includeArrayIndex: "i"}}, {$sort: {i: 1}}]).toArray(); -assert.eq([NumberLong(0), NumberLong(0), NumberLong(1), NumberLong(1), NumberLong(2)], - result.map(function(z) { - return z.i; - })); -result = - coll.aggregate([{$unwind: {path: "$a", includeArrayIndex: "i"}}, {$sort: {i: -1}}]).toArray(); -assert.eq([NumberLong(2), NumberLong(1), NumberLong(1), NumberLong(0), NumberLong(0)], - result.map(function(z) { - return z.i; - })); diff --git a/jstests/aggregation/variables/search_meta.js b/jstests/aggregation/variables/search_meta.js index bcbeef233ed..ba9fe1430ae 100644 --- a/jstests/aggregation/variables/search_meta.js +++ b/jstests/aggregation/variables/search_meta.js @@ -46,7 +46,7 @@ const response = db.runCommand({ cursor: {} }); if (!response.ok) { - assert.commandFailedWithCode(response, [31082, 6047401] /* mongos or community */); + assert.commandFailedWithCode(response, [31082, 40324] /* community or mongos */); } else { assert.eq(response.cursor.firstBatch, []); } |
