327 lines
13 KiB
JavaScript
327 lines
13 KiB
JavaScript
/**
|
|
* 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");
|
|
|
|
//
|
|
// Unsupported conversions.
|
|
//
|
|
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"},
|
|
|
|
{_id: 13, input: 1.9, target: "minKey"},
|
|
{_id: 14, input: 1.9, target: "missing"},
|
|
{_id: 15, input: 1.9, target: "object"},
|
|
{_id: 16, input: 1.9, target: "array"},
|
|
{_id: 17, input: 1.9, target: "binData"},
|
|
{_id: 18, input: 1.9, target: "undefined"},
|
|
{_id: 19, input: 1.9, target: "null"},
|
|
{_id: 20, input: 1.9, target: "regex"},
|
|
{_id: 21, input: 1.9, target: "dbPointer"},
|
|
{_id: 22, input: 1.9, target: "javascript"},
|
|
{_id: 23, input: 1.9, target: "symbol"},
|
|
{_id: 24, input: 1.9, target: "javascriptWithScope"},
|
|
{_id: 25, input: 1.9, target: "timestamp"},
|
|
{_id: 26, input: 1.9, target: "maxKey"},
|
|
];
|
|
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);
|
|
});
|
|
}());
|