diff options
author | Justin Seyster <justin.seyster@mongodb.com> | 2018-03-05 18:15:32 -0500 |
---|---|---|
committer | Justin Seyster <justin.seyster@mongodb.com> | 2018-03-06 11:24:06 -0500 |
commit | 34e83ee29028ba17beedb4f2a00dc622751a3e51 (patch) | |
tree | 090f6b2fccb19b82abf86693704778173645eba8 /jstests | |
parent | 02b3f51e5a7e505b9dd6ebccc2c7632259767661 (diff) | |
download | mongo-34e83ee29028ba17beedb4f2a00dc622751a3e51.tar.gz |
SERVER-33447 jstest for $convert
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/aggregation/expressions/convert.js | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/jstests/aggregation/expressions/convert.js b/jstests/aggregation/expressions/convert.js new file mode 100644 index 00000000000..362cf7b1923 --- /dev/null +++ b/jstests/aggregation/expressions/convert.js @@ -0,0 +1,311 @@ +/** + * Tests behavior of $convert aggregation operator. + */ +(function() { + "use strict"; + + const coll = db.expression_convert; + function populateCollection(documentList) { + coll.drop(); + var bulk = coll.initializeOrderedBulkOp(); + documentList.forEach(doc => bulk.insert(doc)); + assert.writeOK(bulk.execute()); + } + + // + // One test document for each possible conversion. Edge cases for these conversions are tested + // in expression_convert_test.cpp. + // + var conversionTestDocs = [ + {_id: 0, input: 1.9, target: "double", expected: 1.9}, + {_id: 1, input: 1.9, target: "string", expected: "1.9"}, + {_id: 2, input: 1.9, target: "bool", expected: true}, + {_id: 3, input: 1.9, target: "date", expected: ISODate("1970-01-01T00:00:00.001Z")}, + {_id: 4, input: 1.9, target: "int", expected: NumberInt(1)}, + {_id: 5, input: 1.9, target: "long", expected: NumberLong(1)}, + {_id: 6, input: 1.9, target: "decimal", expected: NumberDecimal(1.9)}, + + {_id: 7, input: "1.9", target: "double", expected: 1.9}, + {_id: 8, input: "str", target: "string", expected: "str"}, + { + _id: 9, + input: "0123456789abcdef01234567", + target: "objectId", + expected: ObjectId("0123456789abcdef01234567") + }, + {_id: 10, input: "", target: "bool", expected: true}, + { + _id: 11, + input: "1970-01-01T00:00:00.001Z", + target: "date", + expected: ISODate("1970-01-01T00:00:00.001Z") + }, + {_id: 12, input: "1", target: "int", expected: NumberInt(1)}, + {_id: 13, input: "1", target: "long", expected: NumberLong(1)}, + {_id: 14, input: "1.9", target: "decimal", expected: NumberDecimal("1.9")}, + + { + _id: 15, + input: ObjectId("0123456789abcdef01234567"), + target: "string", + expected: "0123456789abcdef01234567" + }, + {_id: 16, input: ObjectId("0123456789abcdef01234567"), target: "bool", expected: true}, + { + _id: 17, + input: ObjectId("0123456789abcdef01234567"), + target: "objectId", + expected: ObjectId("0123456789abcdef01234567") + }, + { + _id: 18, + input: ObjectId("0123456789abcdef01234567"), + target: "date", + expected: ISODate("1970-08-09T22:25:43Z") + }, + + {_id: 19, input: false, target: "double", expected: 0.0}, + {_id: 20, input: false, target: "string", expected: "false"}, + {_id: 21, input: false, target: "bool", expected: false}, + {_id: 22, input: false, target: "int", expected: NumberInt(0)}, + {_id: 23, input: false, target: "long", expected: NumberLong(0)}, + {_id: 24, input: false, target: "decimal", expected: NumberDecimal(0)}, + + {_id: 25, input: ISODate("1970-01-01T00:00:00.123Z"), target: "double", expected: 123.0}, + { + _id: 26, + input: ISODate("1970-01-01T00:00:00.123Z"), + target: "string", + expected: "1970-01-01T00:00:00.123Z" + }, + {_id: 27, input: ISODate("1970-01-01T00:00:00.123Z"), target: "bool", expected: true}, + { + _id: 28, + input: ISODate("1970-01-01T00:00:00.123Z"), + target: "date", + expected: ISODate("1970-01-01T00:00:00.123Z") + }, + { + _id: 29, + input: ISODate("1970-01-01T00:00:00.123Z"), + target: "long", + expected: NumberLong(123) + }, + { + _id: 30, + input: ISODate("1970-01-01T00:00:00.123Z"), + target: "decimal", + expected: NumberDecimal("123") + }, + + {_id: 31, input: NumberInt(1), target: "double", expected: 1.0}, + {_id: 32, input: NumberInt(1), target: "string", expected: "1"}, + {_id: 33, input: NumberInt(1), target: "bool", expected: true}, + {_id: 34, input: NumberInt(1), target: "int", expected: NumberInt(1)}, + {_id: 35, input: NumberInt(1), target: "long", expected: NumberLong(1)}, + {_id: 36, input: NumberInt(1), target: "decimal", expected: NumberDecimal("1")}, + + {_id: 37, input: NumberLong(1), target: "double", expected: 1.0}, + {_id: 38, input: NumberLong(1), target: "string", expected: "1"}, + {_id: 39, input: NumberLong(1), target: "bool", expected: true}, + { + _id: 40, + input: NumberLong(1), + target: "date", + expected: ISODate("1970-01-01T00:00:00.001Z") + }, + {_id: 41, input: NumberLong(1), target: "int", expected: NumberInt(1)}, + {_id: 42, input: NumberLong(1), target: "long", expected: NumberLong(1)}, + {_id: 43, input: NumberLong(1), target: "decimal", expected: NumberDecimal("1")}, + + {_id: 44, input: NumberDecimal("1.9"), target: "double", expected: 1.9}, + {_id: 45, input: NumberDecimal("1.9"), target: "string", expected: "1.9"}, + {_id: 46, input: NumberDecimal("1.9"), target: "bool", expected: true}, + { + _id: 47, + input: NumberDecimal("1.9"), + target: "date", + expected: ISODate("1970-01-01T00:00:00.001Z") + }, + {_id: 48, input: NumberDecimal("1.9"), target: "int", expected: NumberInt(1)}, + {_id: 49, input: NumberDecimal("1.9"), target: "long", expected: NumberLong(1)}, + {_id: 50, input: NumberDecimal("1.9"), target: "decimal", expected: NumberDecimal("1.9")}, + + {_id: 51, input: MinKey, target: "bool", expected: true}, + {_id: 52, input: {foo: 1, bar: 2}, target: "bool", expected: true}, + {_id: 53, input: [1, 2], target: "bool", expected: true}, + { + _id: 54, + input: BinData(0, "BBBBBBBBBBBBBBBBBBBBBBBBBBBB"), + target: "bool", + expected: true + }, + {_id: 55, input: /B*/, target: "bool", expected: true}, + {_id: 56, input: new DBRef("db.test", "oid"), target: "bool", expected: true}, + {_id: 57, input: function() {}, target: "bool", expected: true}, + // Symbol and CodeWScope are not supported from JavaScript, so we can't test them here. + {_id: 58, input: new Timestamp(1 / 1000, 1), target: "bool", expected: true}, + {_id: 59, input: MinKey, target: "bool", expected: true} + ]; + populateCollection(conversionTestDocs); + + // Test $convert on each document. + var pipeline = [ + { + $project: { + output: {$convert: {to: "$target", input: "$input"}}, + target: "$target", + expected: "$expected" + } + }, + {$addFields: {outputType: {$type: "$output"}}}, + {$sort: {_id: 1}} + ]; + var aggResult = coll.aggregate(pipeline).toArray(); + assert.eq(aggResult.length, conversionTestDocs.length); + + aggResult.forEach(doc => { + assert.eq(doc.output, doc.expected, "Unexpected conversion: _id = " + doc._id); + assert.eq(doc.outputType, doc.target, "Conversion to incorrect type: _id = " + doc._id); + }); + + // Test each conversion using the shorthand $toBool, $toString, etc. syntax. + pipeline = [ + { + $project: { + output: { + $switch: { + branches: [ + {case: {$eq: ["$target", "double"]}, then: {$toDouble: "$input"}}, + {case: {$eq: ["$target", "string"]}, then: {$toString: "$input"}}, + {case: {$eq: ["$target", "objectId"]}, then: {$toObjectId: "$input"}}, + {case: {$eq: ["$target", "bool"]}, then: {$toBool: "$input"}}, + {case: {$eq: ["$target", "date"]}, then: {$toDate: "$input"}}, + {case: {$eq: ["$target", "int"]}, then: {$toInt: "$input"}}, + {case: {$eq: ["$target", "long"]}, then: {$toLong: "$input"}}, + {case: {$eq: ["$target", "decimal"]}, then: {$toDecimal: "$input"}} + ] + } + }, + target: "$target", + expected: "$expected" + } + }, + {$addFields: {outputType: {$type: "$output"}}}, + {$sort: {_id: 1}} + ]; + aggResult = coll.aggregate(pipeline).toArray(); + assert.eq(aggResult.length, conversionTestDocs.length); + + aggResult.forEach(doc => { + assert.eq(doc.output, doc.expected, "Unexpected conversion: _id = " + doc._id); + assert.eq(doc.outputType, doc.target, "Conversion to incorrect type: _id = " + doc._id); + }); + + // Test a $convert expression with "onError" to make sure that error handling still allows an + // error in the "input" expression to propagate. + assert.throws(function() { + coll.aggregate([{ + $project: + {output: {$convert: {to: "string", input: {$divide: [1, 0]}, onError: "ERROR"}}} + }]); + }, [], "Pipeline should have failed"); + + // + // One test document for each conversion that is _not_ supported. + // + var illegalConversionTestDocs = [ + {_id: 0, input: 1.9, target: "objectId"}, + + {_id: 1, input: ObjectId("0123456789abcdef01234567"), target: "double"}, + {_id: 2, input: ObjectId("0123456789abcdef01234567"), target: "int"}, + {_id: 3, input: ObjectId("0123456789abcdef01234567"), target: "long"}, + {_id: 4, input: ObjectId("0123456789abcdef01234567"), target: "decimal"}, + + {_id: 5, input: false, target: "objectId"}, + {_id: 6, input: false, target: "date"}, + + {_id: 7, input: ISODate("1970-01-01T00:00:00.123Z"), target: "objectId"}, + {_id: 8, input: ISODate("1970-01-01T00:00:00.123Z"), target: "int"}, + + {_id: 9, input: NumberInt(1), target: "objectId"}, + {_id: 10, input: NumberInt(1), target: "date"}, + + {_id: 11, input: NumberLong(1), target: "objectId"}, + + {_id: 12, input: NumberDecimal("1.9"), target: "objectId"}, + ]; + populateCollection(illegalConversionTestDocs); + + // Test each document to ensure that the conversion throws an error. + illegalConversionTestDocs.forEach(doc => { + pipeline = [ + {$match: {_id: doc._id}}, + {$project: {output: {$convert: {to: "$target", input: "$input"}}}} + ]; + + assert.throws(function() { + coll.aggregate(pipeline); + }, [], "Conversion should have failed: _id = " + doc._id); + }); + + // Test that each illegal conversion uses the 'onError' value. + pipeline = [ + {$project: {output: {$convert: {to: "$target", input: "$input", onError: "ERROR"}}}}, + {$sort: {_id: 1}} + ]; + var aggResult = coll.aggregate(pipeline).toArray(); + assert.eq(aggResult.length, illegalConversionTestDocs.length); + + aggResult.forEach(doc => { + assert.eq(doc.output, "ERROR", "Unexpected result: _id = " + doc._id); + }); + + // Test that, when onError is missing, the missing value propagates to the result. + pipeline = [ + { + $project: { + _id: false, + output: {$convert: {to: "$target", input: "$input", onError: "$$REMOVE"}} + } + }, + {$sort: {_id: 1}} + ]; + var aggResult = coll.aggregate(pipeline).toArray(); + assert.eq(aggResult.length, illegalConversionTestDocs.length); + + aggResult.forEach(doc => { + assert.eq(doc, {}); + }); + + // + // One test document for each "nullish" value. + // + var nullTestDocs = + [{_id: 0, input: null}, {_id: 1, input: undefined}, {_id: 2, /* input is missing */}]; + populateCollection(nullTestDocs); + + // Test that all nullish inputs result in the 'onNull' output. + pipeline = [ + {$project: {output: {$convert: {to: "int", input: "$input", onNull: "NULL"}}}}, + {$sort: {_id: 1}} + ]; + var aggResult = coll.aggregate(pipeline).toArray(); + assert.eq(aggResult.length, nullTestDocs.length); + + aggResult.forEach(doc => { + assert.eq(doc.output, "NULL", "Unexpected result: _id = " + doc._id); + }); + + // Test that all nullish inputs result in the 'onNull' output _even_ if 'to' is nullish. + pipeline = [ + {$project: {output: {$convert: {to: null, input: "$input", onNull: "NULL"}}}}, + {$sort: {_id: 1}} + ]; + var aggResult = coll.aggregate(pipeline).toArray(); + assert.eq(aggResult.length, nullTestDocs.length); + + aggResult.forEach(doc => { + assert.eq(doc.output, "NULL", "Unexpected result: _id = " + doc._id); + }); +}()); |