// Test $replaceOne aggregation expressions. /** * @tags: [ * requires_fcv_83 * ] */ import "jstests/libs/query/sbe_assert_error_override.js"; import {testExpression} from "jstests/aggregation/extras/utils.js"; const coll = db.replace_one; coll.drop(); assert.commandWorked(coll.insert({})); function runAndAssert(args, result) { // Test with constant-folding optimization. testExpression(coll, {$replaceOne: args}, result); coll.drop(); // Insert args as document. const document = {}; if (args.input != "$missing") { document.input = args.input; } if (args.find != "$missing") { document.find = args.find; } if (args.replacement != "$missing") { document.replacement = args.replacement; } assert.commandWorked(coll.insertOne(document)); // Test again with fields from document. assert.eq( coll .aggregate([ { $project: { result: {$replaceOne: {input: "$input", find: "$find", replacement: "$replacement"}}, }, }, ]) .toArray()[0].result, result, ); // Clean up. coll.drop(); assert.commandWorked(coll.insertOne({})); } function runAndAssertThrows(args, code) { const error = assert.throws(() => coll.aggregate([{$project: {args, result: {$replaceOne: args}}}]).toArray()); assert.commandFailedWithCode(error, code); } // Test find one. runAndAssert({input: "albatross", find: "ross", replacement: "rachel"}, "albatrachel"); runAndAssert({input: "albatross", find: "", replacement: "one "}, "one albatross"); runAndAssert({input: "albatross", find: "", replacement: ""}, "albatross"); runAndAssert({input: "", find: "", replacement: "foo"}, "foo"); runAndAssert({input: "", find: "", replacement: ""}, ""); // Test find none. runAndAssert({input: "albatross", find: "rachel", replacement: "ross"}, "albatross"); runAndAssert({input: "", find: "rachel", replacement: "ross"}, ""); runAndAssert({input: "", find: "rachel", replacement: ""}, ""); // Test finding many only replaces first occurrence. runAndAssert({input: "an antelope is an animal", find: "an", replacement: ""}, " antelope is an animal"); runAndAssert({input: "an antelope is an animal", find: "an", replacement: "this"}, "this antelope is an animal"); // Test that any combination of null and non-null arguments returns null. runAndAssert({input: null, find: null, replacement: null}, null); runAndAssert({input: "a", find: null, replacement: null}, null); runAndAssert({input: null, find: "b", replacement: null}, null); runAndAssert({input: null, find: null, replacement: "c"}, null); runAndAssert({input: "a", find: "b", replacement: null}, null); runAndAssert({input: null, find: "b", replacement: "c"}, null); runAndAssert({input: "a", find: "b", replacement: null}, null); // Test that combinations of missing and non-missing arguments returns null. runAndAssert({input: "$missing", find: "$missing", replacement: "$missing"}, null); runAndAssert({input: "a", find: "$missing", replacement: "$missing"}, null); runAndAssert({input: "$missing", find: "b", replacement: "$missing"}, null); runAndAssert({input: "$missing", find: "$missing", replacement: "c"}, null); runAndAssert({input: "a", find: "b", replacement: "$missing"}, null); runAndAssert({input: "a", find: "$missing", replacement: "c"}, null); runAndAssert({input: "$missing", find: "b", replacement: "c"}, null); runAndAssert({input: null, find: null, replacement: "$missing"}, null); runAndAssert({input: null, find: "$missing", replacement: null}, null); runAndAssert({input: "$missing", find: null, replacement: null}, null); // Basic regex replacement. runAndAssert({input: "123-456-7890", find: /\d{3}/, replacement: "xxx"}, "xxx-456-7890"); runAndAssert({input: "hello1world2", find: /\d/, replacement: "X"}, "helloXworld2"); // No match. runAndAssert({input: "hello world", find: /xyz/, replacement: "abc"}, "hello world"); // Empty input and pattern. runAndAssert({input: "", find: /.*/, replacement: "replaced"}, "replaced"); // No match. runAndAssert({input: "line1\nline2", find: /^line/m, replacement: "start: "}, "start: 1\nline2"); // Empty input and pattern. runAndAssert({input: "FooBar", find: /foobar/i, replacement: "MATCHED"}, "MATCHED"); // RegEx groupings. runAndAssert({input: "helloworld", find: /([aeiou]+)/, replacement: "X"}, "hXlloworld"); runAndAssert({input: "123-456-7890", find: /(\d{3})/, replacement: "xxx"}, "xxx-456-7890"); runAndAssert({input: "123.456.7890", find: /([0-9]+)(\.)/, replacement: "x"}, "x456.7890"); runAndAssert({input: "helloworld", find: /(([aeiou]+)l)/, replacement: "X"}, "hXloworld"); // // Reset and test that if any input is not a string, replaceOne fails with an error. // assert(coll.drop()); assert.commandWorked( coll.insertOne({ obj_field: {a: 1, b: 1, c: {d: 2}}, arr_field1: [1, 2, 3, "c"], arr_field2: ["aaaaa"], int_field: 1, dbl_field: 1.0, null_field: null, str_field: "foo", regex_field: /.*/, }), ); // replacement is not a string const invalidReplacementCode = 10503902; // find is not a string or regex const invalidFindCode = 10503903; // input is not a string const invalidInputCode = 10503904; runAndAssertThrows({input: "$obj_field", find: "$str_field", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$arr_field1", find: "$str_field", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$int_field", find: "$regex_field", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$dbl_field", find: "$regex_field", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$str_field", find: "$obj_field", replacement: "$str_field"}, invalidFindCode); runAndAssertThrows({input: "$str_field", find: "$arr_field1", replacement: "$str_field"}, invalidFindCode); runAndAssertThrows({input: "$str_field", find: "$int_field", replacement: "$str_field"}, invalidFindCode); runAndAssertThrows({input: "$str_field", find: "$dbl_field", replacement: "$str_field"}, invalidFindCode); runAndAssertThrows({input: "$str_field", find: "$str_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows({input: "$str_field", find: "$str_field", replacement: "$arr_field1"}, invalidReplacementCode); runAndAssertThrows({input: "$str_field", find: "$regex_field", replacement: "$int_field"}, invalidReplacementCode); runAndAssertThrows({input: "$str_field", find: "$regex_field", replacement: "$dbl_field"}, invalidReplacementCode); runAndAssertThrows({input: "$str_field", find: "$arr_field2", replacement: "$dbl_field"}, invalidFindCode); runAndAssertThrows({input: "$obj_field", find: "$arr_field2", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$int_field", find: "$arr_field2", replacement: "$dbl_field"}, invalidInputCode); runAndAssertThrows({input: "$arr_field2", find: "$arr_field2", replacement: "$arr_field2"}, invalidInputCode); // // Test always throws when invalid fields are given, even if some fields are also null or missing. // runAndAssertThrows({input: "$obj_field", find: "$null_field", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$missing_field", replacement: "$str_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$str_field", replacement: "$null_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$str_field", replacement: "$missing_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$missing_field", replacement: "$null_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$null_field", replacement: "$missing_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$missing_field", replacement: "$missing_field"}, invalidInputCode); runAndAssertThrows({input: "$obj_field", find: "$null_field", replacement: "$null_field"}, invalidInputCode); runAndAssertThrows({input: "$null_field", find: "$obj_field", replacement: "$str_field"}, invalidFindCode); runAndAssertThrows({input: "$missing_field", find: "$obj_field", replacement: "$str_field"}, invalidFindCode); runAndAssertThrows({input: "$str_field", find: "$obj_field", replacement: "$null_field"}, invalidFindCode); runAndAssertThrows({input: "$str_field", find: "$obj_field", replacement: "$missing_field"}, invalidFindCode); runAndAssertThrows({input: "$missing_field", find: "$obj_field", replacement: "$null_field"}, invalidFindCode); runAndAssertThrows({input: "$null_field", find: "$obj_field", replacement: "$missing_field"}, invalidFindCode); runAndAssertThrows({input: "$missing_field", find: "$obj_field", replacement: "$missing_field"}, invalidFindCode); runAndAssertThrows({input: "$null_field", find: "$obj_field", replacement: "$null_field"}, invalidFindCode); runAndAssertThrows({input: "$null_field", find: "$str_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows({input: "$missing_field", find: "$str_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows({input: "$str_field", find: "$null_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows({input: "$str_field", find: "$missing_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows({input: "$missing_field", find: "$null_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows({input: "$null_field", find: "$missing_field", replacement: "$obj_field"}, invalidReplacementCode); runAndAssertThrows( {input: "$missing_field", find: "$missing_field", replacement: "$obj_field"}, invalidReplacementCode, ); runAndAssertThrows({input: "$null_field", find: "$null_field", replacement: "$obj_field"}, invalidReplacementCode);