// Aggregation $substrBytes tests. t = db.jstests_aggregation_substr; t.drop(); t.save({}); function assertSubstring(expected, str, offset, len) { assert.eq(expected, t.aggregate({$project: {a: {$substrBytes: [str, offset, len]}}}).toArray()[0].a); } function assertArgsException(args) { assert.commandFailed(t.runCommand('aggregate', {pipeline: [{$substrBytes: args}]})); } function assertException(str, offset, len) { assertArgsException([str, offset, len]); } // Wrong number of arguments. assertArgsException([]); assertArgsException(['foo']); assertArgsException(['foo', 1]); assertArgsException(['foo', 1, 1, 1]); // Basic offset / length checks. assertSubstring('abcd', 'abcd', 0, 4); assertSubstring('abcd', 'abcd', 0, 5); assertSubstring('a', 'abcd', 0, 1); assertSubstring('ab', 'abcd', 0, 2); assertSubstring('b', 'abcd', 1, 1); assertSubstring('d', 'abcd', 3, 1); assertSubstring('', 'abcd', 4, 1); assertSubstring('', 'abcd', 3, 0); assertSubstring('cd', 'abcd', 2, -1); // Passing a negative number for the start position should return an error. assertException('abcd', -1, 4); assertException('abcd', -1, 0); assertException('abcd', -10, 0); // See server6186.js for additional offset / length checks. // Additional numeric types for offset / length. assertSubstring('bc', 'abcd', 1, 2); assertSubstring('bc', 'abcd', 1.0, 2.0); assertSubstring('bc', 'abcd', NumberInt(1), NumberInt(2)); assertSubstring('bc', 'abcd', NumberLong(1), NumberLong(2)); assertSubstring('bc', 'abcd', NumberInt(1), NumberLong(2)); assertSubstring('bc', 'abcd', NumberLong(1), NumberInt(2)); // Integer component is used. assertSubstring('bc', 'abcd', 1.2, 2.2); assertSubstring('bc', 'abcd', 1.9, 2.9); assertSubstring('cd', 'abcd', 2, -1); assertSubstring('abcd', 'abcd', 0, -1); // Any negative number for length will return the rest of the string. assertSubstring('cd', 'abcd', 2, -5); assertSubstring('', 'abcd', 4, -1); assertSubstring('', 'abcd', 10, -1); // Non numeric types for offset / length. assertException('abcd', false, 2); assertException('abcd', 1, true); assertException('abcd', 'q', 2); assertException('abcd', 1, 'r'); assertException('abcd', null, 3); assertException('abcd', 1, undefined); // String coercion. assertSubstring('123', 123, 0, 3); assertSubstring('2', 123, 1, 1); assertSubstring('1970', new Date(0), 0, 4); assertSubstring('', null, 0, 4); assertException(/abc/, 0, 4); // Field path like string. assertSubstring('$a', 'a$a', 1, 2); // Multi byte utf-8. assertSubstring('\u0080', '\u0080', 0, 2); assertException('\u0080', 0, 1); assertException('\u0080', 1, 1); assertSubstring('\u0080', '\u0080\u20ac', 0, 2); assertSubstring('\u20ac', '\u0080\u20ac', 2, 3); assertException('\u0080\u20ac', 1, 3); assertException('\u0080\u20ac', 1, 4); assertException('\u0080\u20ac', 0, 3); assertSubstring('\u0044\u20ac', '\u0080\u0044\u20ac', 2, 4); assertSubstring('\u0044', '\u0080\u0044\u20ac', 2, 1); // The four byte utf-8 character 𝌆 (have to represent in surrogate halves). assertSubstring('\uD834\uDF06', '\uD834\uDF06', 0, 4); assertException('\uD834\uDF06', '\uD834\uDF06', 1, 4); assertException('\uD834\uDF06', '\uD834\uDF06', 0, 3); // Operands from document. t.drop(); t.save({x: 'a', y: 'abc', z: 'abcde', a: 0, b: 1, c: 2, d: 3, e: 4, f: 5}); assertSubstring('a', '$x', '$a', '$b'); assertSubstring('a', '$x', '$a', '$f'); assertSubstring('b', '$y', '$b', '$b'); assertSubstring('b', '$z', '$b', '$b'); assertSubstring('bcd', '$z', '$b', '$d'); assertSubstring('cde', '$z', '$c', '$f'); assertSubstring('c', '$y', '$c', '$f'); // Computed operands. assertSubstring('cde', '$z', {$add: ['$b', '$b']}, {$add: ['$c', '$d']}); assertSubstring('cde', '$z', {$add: ['$b', 1]}, {$add: [2, '$d']}); // Nested. assert.eq( 'e', t.aggregate({ $project: { a: { $substrBytes: [ { $substrBytes: [{$substrBytes: [{$substrBytes: ['abcdefghij', 1, 6]}, 2, 5]}, 0, 3] }, 1, 1 ] } } }) .toArray()[0] .a);