Skip to content

Commit

Permalink
feat: Implement $top and $bottom accumulator operators.
Browse files Browse the repository at this point in the history
  • Loading branch information
kofrasa committed Dec 13, 2022
1 parent 053db02 commit 9c4e914
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/operators/accumulator/bottom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/#mongodb-group-grp.-bottom
import { Options } from "../../core";
import { AnyVal, RawObject } from "../../types";
import { $bottomN } from "./bottomN";

/**
* Returns the bottom element within a group according to the specified sort order.
*
* @param {Array} collection The input array
* @param {Object} expr The right-hand side expression value of the operator
* @param {Options} options The options to use for this operation
* @returns {*}
*/
export function $bottom(
collection: RawObject[],
expr: { sortBy: Record<string, number>; output: AnyVal },
options?: Options
): AnyVal[] {
return $bottomN(collection, { ...expr, n: 1 }, options);
}
2 changes: 2 additions & 0 deletions src/operators/accumulator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export * from "./accumulator";
export * from "./addToSet";
export * from "./avg";
export * from "./bottom";
export * from "./bottomN";
export * from "./count";
export * from "./covariancePop";
Expand All @@ -18,4 +19,5 @@ export * from "./push";
export * from "./stdDevPop";
export * from "./stdDevSamp";
export * from "./sum";
export * from "./top";
export * from "./topN";
20 changes: 20 additions & 0 deletions src/operators/accumulator/top.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/#mongodb-group-grp.-top
import { Options } from "../../core";
import { AnyVal, RawObject } from "../../types";
import { $topN } from "./topN";

/**
* Returns the top element within a group according to the specified sort order.
*
* @param {Array} collection The input array
* @param {Object} expr The right-hand side expression value of the operator
* @param {Options} options The options to use for this operation
* @returns {*}
*/
export function $top(
collection: RawObject[],
expr: { sortBy: Record<string, number>; output: AnyVal },
options?: Options
): AnyVal[] {
return $topN(collection, { ...expr, n: 1 }, options);
}
120 changes: 120 additions & 0 deletions test/operators/accumulator/bottom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as samples from "../../support";

const docs = [
{ playerId: "PlayerA", gameId: "G1", score: 31 },
{ playerId: "PlayerB", gameId: "G1", score: 33 },
{ playerId: "PlayerC", gameId: "G1", score: 99 },
{ playerId: "PlayerD", gameId: "G1", score: 1 },
{ playerId: "PlayerA", gameId: "G2", score: 10 },
{ playerId: "PlayerB", gameId: "G2", score: 14 },
{ playerId: "PlayerC", gameId: "G2", score: 66 },
{ playerId: "PlayerD", gameId: "G2", score: 80 },
];

samples.runTestPipeline("operators/accumulator/bottom", [
{
message: "Null and Missing Values",
input: [
{ playerId: "PlayerA", gameId: "G1", score: 1 },
{ playerId: "PlayerB", gameId: "G1", score: 2 },
{ playerId: "PlayerC", gameId: "G1", score: 3 },
{ playerId: "PlayerD", gameId: "G1" },
{ playerId: "PlayerE", gameId: "G1", score: null },
],
pipeline: [
{
$group: {
_id: "$gameId",
playerId: {
$bottom: {
output: ["$playerId", "$score"],
sortBy: { score: -1 },
},
},
},
},
],
expected: [
{
_id: "G1",
playerId: [["PlayerE", null]],
},
],
},
{
message: "Data Type Sort Ordering",
input: [
{ playerId: "PlayerA", gameId: "G1", score: 1 },
{ playerId: "PlayerB", gameId: "G1", score: "2" },
{ playerId: "PlayerC", gameId: "G1", score: "" },
],
pipeline: [
{
$group: {
_id: "$gameId",
playerId: {
$bottom: {
output: ["$playerId", "$score"],
sortBy: { score: -1 },
},
},
},
},
],
expected: [
{
_id: "G1",
playerId: [["PlayerA", 1]],
},
],
},

{
message: "Finding the Three Lowest Score Documents Across Multiple Games",
input: docs,
pipeline: [
{
$group: {
_id: "$gameId",
playerId: {
$bottom: {
output: ["$playerId", "$score"],
sortBy: { score: -1 },
},
},
},
},
],
expected: [
{
_id: "G1",
playerId: [["PlayerD", 1]],
},
{
_id: "G2",
playerId: [["PlayerA", 10]],
},
],
},
{
message: "Computing n Based on the Group Key for $group",
input: docs,
pipeline: [
{
$group: {
_id: { gameId: "$gameId" },
gamescores: {
$bottom: {
output: "$score",
sortBy: { score: -1 },
},
},
},
},
],
expected: [
{ _id: { gameId: "G1" }, gamescores: [1] },
{ _id: { gameId: "G2" }, gamescores: [10] },
],
},
]);
92 changes: 92 additions & 0 deletions test/operators/accumulator/top.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as samples from "../../support";

const docs = [
{ playerId: "PlayerA", gameId: "G1", score: 31 },
{ playerId: "PlayerB", gameId: "G1", score: 33 },
{ playerId: "PlayerC", gameId: "G1", score: 99 },
{ playerId: "PlayerD", gameId: "G1", score: 1 },
{ playerId: "PlayerA", gameId: "G2", score: 10 },
{ playerId: "PlayerB", gameId: "G2", score: 14 },
{ playerId: "PlayerC", gameId: "G2", score: 66 },
{ playerId: "PlayerD", gameId: "G2", score: 80 },
];
samples.runTestPipeline("operators/accumulator/top", [
{
message: "Null and Missing Values",
input: [
{ playerId: "PlayerA", gameId: "G1", score: 1 },
{ playerId: "PlayerB", gameId: "G1", score: 2 },
{ playerId: "PlayerC", gameId: "G1", score: 3 },
{ playerId: "PlayerD", gameId: "G1" },
{ playerId: "PlayerE", gameId: "G1", score: null },
],
pipeline: [
{
$group: {
_id: "$gameId",
playerId: {
$top: {
output: ["$playerId", "$score"],
sortBy: { score: 1 },
},
},
},
},
],
expected: [
{
_id: "G1",
playerId: [["PlayerD", undefined]],
},
],
},
{
message: "Data Type Sort Ordering",
input: [
{ playerId: "PlayerA", gameId: "G1", score: 1 },
{ playerId: "PlayerB", gameId: "G1", score: "2" },
{ playerId: "PlayerC", gameId: "G1", score: "" },
],
pipeline: [
{
$group: {
_id: "$gameId",
playerId: {
$top: {
output: ["$playerId", "$score"],
sortBy: { score: -1 },
},
},
},
},
],
expected: [
{
_id: "G1",
playerId: [["PlayerB", "2"]],
},
],
},

{
message: "Find the Top Score Across Multiple Games",
input: docs,
pipeline: [
{
$group: {
_id: "$gameId",
playerId: {
$top: {
output: ["$playerId", "$score"],
sortBy: { score: -1 },
},
},
},
},
],
expected: [
{ _id: "G1", playerId: [["PlayerC", 99]] },
{ _id: "G2", playerId: [["PlayerD", 80]] },
],
},
]);

0 comments on commit 9c4e914

Please sign in to comment.