// Aggregation $substr tests. t = db.jstests_aggregation_substr; t.drop(); t.save( {} ); function assertSubstring( expected, str, offset, len ) { assert.eq( expected, t.aggregate( { $project:{ a:{ $substr:[ str, offset, len ] } } } ).toArray()[ 0 ].a ); } function assertArgsException( args ) { assert.commandFailed(t.runCommand('aggregate', {pipeline: [{$substr: 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( '', 'abcd', -1 /* unsigned */, 4 ); 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 /* unsigned */ ); // 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 ); // 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: { $substr: [ { $substr: [ { $substr: [ { $substr:[ 'abcdefghij', 1, 6 ] }, 2, 5 ] }, 0, 3 ] }, 1, 1 ] } } } ).toArray()[ 0 ].a );