338 lines
9.4 KiB
JavaScript
338 lines
9.4 KiB
JavaScript
/**
|
|
* Test that $shift works as a window function.
|
|
*/
|
|
(function() {
|
|
"use strict";
|
|
|
|
load("jstests/aggregation/extras/utils.js"); // documentEq
|
|
|
|
const coll = db[jsTestName()];
|
|
coll.drop();
|
|
|
|
const nDocs = 10;
|
|
for (let i = 0; i < nDocs; i++) {
|
|
assert.commandWorked(coll.insert({
|
|
one: i,
|
|
partition: i % 2,
|
|
partitionSeq: Math.trunc(i / 2),
|
|
}));
|
|
}
|
|
const lastDoc = nDocs - 1;
|
|
const lastDocInPartition = nDocs / 2 - 1;
|
|
|
|
const origDocs = coll.find().sort({_id: 1});
|
|
function verifyResults(results, valueFunction) {
|
|
for (let i = 0; i < results.length; i++) {
|
|
// Use Object.assign to make a copy instead of pass a reference.
|
|
const correctDoc = valueFunction(i, Object.assign({}, origDocs[i]));
|
|
assert(documentEq(correctDoc, results[i]),
|
|
"Got: " + tojson(results[i]) + "\nExpected: " + tojson(correctDoc) +
|
|
"\n at position " + i + "\n");
|
|
}
|
|
}
|
|
|
|
// Run an unpartitioned shift query using the specified offset and default expression.
|
|
function runShiftQuery(shiftBy, defaultVal) {
|
|
return coll
|
|
.aggregate([
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {a: {$shift: {by: shiftBy, output: "$one", default: defaultVal}}}
|
|
}
|
|
},
|
|
{$sort: {_id: 1}}
|
|
])
|
|
.toArray();
|
|
}
|
|
|
|
// Run an unpartitioned shift query using the specified offset and the default default expression.
|
|
function runShiftQueryWithoutDefault(shiftBy) {
|
|
return coll
|
|
.aggregate([
|
|
{
|
|
$setWindowFields:
|
|
{sortBy: {one: 1}, output: {a: {$shift: {by: shiftBy, output: "$one"}}}}
|
|
},
|
|
{$sort: {_id: 1}}
|
|
])
|
|
.toArray();
|
|
}
|
|
|
|
// Test left shift with default.
|
|
let result = runShiftQuery(-1, -10);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.one == 0)
|
|
baseObj.a = -10;
|
|
else
|
|
baseObj.a = baseObj.one - 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test left shift without default.
|
|
result = runShiftQueryWithoutDefault(-1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.one == 0)
|
|
baseObj.a = null;
|
|
else
|
|
baseObj.a = baseObj.one - 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test 0 shift with default.
|
|
result = runShiftQuery(0);
|
|
verifyResults(result, function(num, baseObj) {
|
|
baseObj.a = baseObj.one;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test 0 shift without default.
|
|
result = runShiftQueryWithoutDefault(0);
|
|
verifyResults(result, function(num, baseObj) {
|
|
baseObj.a = baseObj.one;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test right shift with default.
|
|
result = runShiftQuery(1, -10);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.one == lastDoc)
|
|
baseObj.a = -10;
|
|
else
|
|
baseObj.a = baseObj.one + 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test right shift without default.
|
|
result = runShiftQueryWithoutDefault(1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.one == lastDoc)
|
|
baseObj.a = null;
|
|
else
|
|
baseObj.a = baseObj.one + 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Run an unpartitioned shift query using the specified offset with descending order.
|
|
function runShiftQueryDescending(shiftBy) {
|
|
return coll
|
|
.aggregate([
|
|
{
|
|
$setWindowFields:
|
|
{sortBy: {one: -1}, output: {a: {$shift: {by: shiftBy, output: "$one"}}}}
|
|
},
|
|
{$sort: {_id: 1}}
|
|
])
|
|
.toArray();
|
|
}
|
|
|
|
// Test right shift with descending sort.
|
|
result = runShiftQueryDescending(1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.one == 0)
|
|
baseObj.a = null;
|
|
else
|
|
baseObj.a = baseObj.one - 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test 0 shift with descending sort.
|
|
result = runShiftQueryDescending(0);
|
|
verifyResults(result, function(num, baseObj) {
|
|
baseObj.a = baseObj.one;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test left shift with descending sort.
|
|
result = runShiftQueryDescending(-1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.one == lastDoc)
|
|
baseObj.a = null;
|
|
else
|
|
baseObj.a = baseObj.one + 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Run a shift query partitioned over "$partition" using the specified shift and default
|
|
// default expression.
|
|
//
|
|
// Partitioning is odd/even.
|
|
function runPartitionedShiftQuery(shiftBy) {
|
|
return coll
|
|
.aggregate([
|
|
{
|
|
$setWindowFields: {
|
|
partitionBy: "$partition",
|
|
sortBy: {one: 1},
|
|
output: {a: {$shift: {by: shiftBy, output: "$one"}}}
|
|
}
|
|
},
|
|
{$sort: {_id: 1}}
|
|
])
|
|
.toArray();
|
|
}
|
|
|
|
// Test partitioned left shift.
|
|
result = runPartitionedShiftQuery(-1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.partitionSeq == 0)
|
|
baseObj.a = null;
|
|
else
|
|
// partitioning is even/odd.
|
|
baseObj.a = baseObj.one - 2;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test partitioned right shift.
|
|
result = runPartitionedShiftQuery(1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.partitionSeq == lastDocInPartition)
|
|
baseObj.a = null;
|
|
else
|
|
// partitioning is even/odd.
|
|
baseObj.a = baseObj.one + 2;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test partitioned 0 shift.
|
|
result = runPartitionedShiftQuery(0);
|
|
verifyResults(result, function(num, baseObj) {
|
|
baseObj.a = baseObj.one;
|
|
return baseObj;
|
|
});
|
|
|
|
// Run a shift query partitioned over "$partition" using the specified shift and default
|
|
// default expression with a descending sort.
|
|
//
|
|
// Partitioning is odd/even.
|
|
function runPartitionedShiftQueryDescending(shiftBy) {
|
|
return coll
|
|
.aggregate([
|
|
{
|
|
$setWindowFields: {
|
|
partitionBy: "$partition",
|
|
sortBy: {one: -1},
|
|
output: {a: {$shift: {by: shiftBy, output: "$one"}}}
|
|
}
|
|
},
|
|
{$sort: {_id: 1}}
|
|
])
|
|
.toArray();
|
|
}
|
|
|
|
// Test partitioned left shift with descending sort.
|
|
result = runPartitionedShiftQueryDescending(-1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.partitionSeq == lastDocInPartition)
|
|
baseObj.a = null;
|
|
else
|
|
// partitioning is even/odd.
|
|
baseObj.a = baseObj.one + 2;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test partitioned right shift with descending sort.
|
|
result = runPartitionedShiftQueryDescending(1);
|
|
verifyResults(result, function(num, baseObj) {
|
|
if (baseObj.partitionSeq == 0)
|
|
baseObj.a = null;
|
|
else
|
|
// partitioning is even/odd.
|
|
baseObj.a = baseObj.one - 2;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test partitioned 0 shift with descending sort.
|
|
result = runPartitionedShiftQuery(0);
|
|
verifyResults(result, function(num, baseObj) {
|
|
baseObj.a = baseObj.one;
|
|
return baseObj;
|
|
});
|
|
|
|
/* Parsing tests */
|
|
|
|
// "by" is required.
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline: [{$setWindowFields: {sortBy: {one: 1}, output: {a: {$shift: {output: "$one"}}}}}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
|
|
// Can't accept a string for "by".
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline:
|
|
[{$setWindowFields: {sortBy: {one: 1}, output: {a: {$shift: {by: "1", output: "$one"}}}}}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
|
|
// Can't accept an expression for "by".
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline: [{
|
|
$setWindowFields:
|
|
{sortBy: {one: 1}, output: {a: {$shift: {by: {$sum: [1, 1]}, output: "$one"}}}}
|
|
}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
|
|
// Can't accept a float for "by".
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline:
|
|
[{$setWindowFields: {sortBy: {one: 1}, output: {a: {$shift: {by: 1.1, output: "$one"}}}}}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
|
|
// Can't accept a float for "by" ... unless it converts to int without loss of precision.
|
|
assert.commandWorked(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline:
|
|
[{$setWindowFields: {sortBy: {one: 1}, output: {a: {$shift: {by: 1.0, output: "$one"}}}}}],
|
|
cursor: {}
|
|
}));
|
|
|
|
// "output" is required.
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline: [{$setWindowFields: {sortBy: {one: 1}, output: {a: {$shift: {by: 1}}}}}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
|
|
// "default" must evaluate to a constant.
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline: [{
|
|
$setWindowFields:
|
|
{sortBy: {one: 1}, output: {a: {$shift: {by: 1, output: "$one", default: "$one"}}}}
|
|
}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
|
|
// "default" may be an arbitrary expression as long as it evaluates to a constant.
|
|
assert.commandWorked(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline: [{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {a: {$shift: {by: 1, output: "$one", default: {$add: [1, 1]}}}}
|
|
}
|
|
}],
|
|
cursor: {}
|
|
}));
|
|
|
|
// "sortBy" is required for $shift.
|
|
assert.commandFailedWithCode(coll.runCommand({
|
|
aggregate: coll.getName(),
|
|
pipeline: [{$setWindowFields: {output: {a: {$shift: {by: 1, output: "$one"}}}}}],
|
|
cursor: {}
|
|
}),
|
|
ErrorCodes.FailedToParse);
|
|
})();
|