Skip to content

Commit

Permalink
feat: Implement $firstN and $lastN array expression operators.
Browse files Browse the repository at this point in the history
  • Loading branch information
kofrasa committed Dec 14, 2022
1 parent 31eb907 commit 4f0ec93
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/operators/_predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function $eq(a: AnyVal, b: AnyVal, options?: PredicateOptions): boolean {
// check
if (a instanceof Array) {
const eq = isEqual.bind(null, b) as Callback<boolean>;
return a.some(eq) || flatten(a, options.depth).some(eq);
return a.some(eq) || flatten(a, options?.depth).some(eq);
}

return false;
Expand Down
15 changes: 9 additions & 6 deletions src/operators/expression/array/first.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Array Expression Operators: https://docs.mongodb.com/manual/reference/operator/aggregation/#array-expression-operators

import { computeValue, Options } from "../../../core";
import { ComputeOptions, computeValue, Options } from "../../../core";
import { AnyVal, RawObject } from "../../../types";
import { assert, isArray } from "../../../util";
import { assert, isArray, isNil } from "../../../util";
import { $first as __first } from "../../accumulator";

/**
* Returns the first element in an array.
Expand All @@ -16,9 +17,11 @@ export function $first(
expr: AnyVal,
options?: Options
): AnyVal {
const arr = computeValue(obj, expr, null, options) as AnyVal[];
if (arr == null) return null;
const copts = ComputeOptions.init(options);
if (obj instanceof Array) return __first(obj, expr, copts.udpate());

const arr = computeValue(obj, expr, null, options) as RawObject[];
if (isNil(arr)) return null;
assert(isArray(arr), "Must resolve to an array/null or missing");
if (arr.length > 0) return arr[0];
return undefined;
return __first(arr, "$$this", options);
}
31 changes: 31 additions & 0 deletions src/operators/expression/array/firstN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/#mongodb-expression-exp.-firstN

import { computeValue, Options } from "../../../core";
import { AnyVal, RawObject } from "../../../types";
import { assert, isArray, isNil } from "../../../util";
import { $firstN as __firstN } from "../../accumulator/firstN";

interface InputExpr {
n: AnyVal;
input: AnyVal;
}

/**
* Returns a specified number of elements from the beginning of an array.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
export function $firstN(
obj: RawObject,
expr: InputExpr,
options?: Options
): AnyVal {
// first try the accumulator if input is an array.
if (obj instanceof Array) return __firstN(obj, expr, options);
const { input, n } = computeValue(obj, expr, null, options) as InputExpr;
if (isNil(input)) return null;
assert(isArray(input), "Must resolve to an array/null or missing");
return __firstN(input as RawObject[], { n, input: "$$this" }, options);
}
2 changes: 2 additions & 0 deletions src/operators/expression/array/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ export * from "./arrayToObject";
export * from "./concatArrays";
export * from "./filter";
export * from "./first";
export * from "./firstN";
export * from "./in";
export * from "./indexOfArray";
export * from "./isArray";
export * from "./last";
export * from "./lastN";
export * from "./map";
export * from "./nin";
export * from "./range";
Expand Down
15 changes: 9 additions & 6 deletions src/operators/expression/array/last.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Array Expression Operators: https://docs.mongodb.com/manual/reference/operator/aggregation/#array-expression-operators

import { computeValue, Options } from "../../../core";
import { ComputeOptions, computeValue, Options } from "../../../core";
import { AnyVal, RawObject } from "../../../types";
import { assert, isArray } from "../../../util";
import { assert, isArray, isNil } from "../../../util";
import { $last as __last } from "../../accumulator";

/**
* Returns the last element in an array.
Expand All @@ -12,9 +13,11 @@ import { assert, isArray } from "../../../util";
* @return {*}
*/
export function $last(obj: RawObject, expr: AnyVal, options?: Options): AnyVal {
const arr = computeValue(obj, expr, null, options) as AnyVal[];
if (arr == null) return null;
const copts = ComputeOptions.init(options);
if (obj instanceof Array) return __last(obj, expr, copts.udpate());

const arr = computeValue(obj, expr, null, options) as RawObject[];
if (isNil(arr)) return null;
assert(isArray(arr), "Must resolve to an array/null or missing");
if (arr.length > 0) return arr[arr.length - 1];
return undefined;
return __last(arr, "$$this", options);
}
31 changes: 31 additions & 0 deletions src/operators/expression/array/lastN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN-array-element/#mongodb-expression-exp.-lastN

import { computeValue, Options } from "../../../core";
import { AnyVal, RawObject } from "../../../types";
import { assert, isArray, isNil } from "../../../util";
import { $lastN as __lastN } from "../../accumulator/lastN";

interface InputExpr {
n: AnyVal;
input: AnyVal;
}

/**
* Returns a specified number of elements from the end of an array.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
export function $lastN(
obj: RawObject,
expr: InputExpr,
options?: Options
): AnyVal {
// first try the accumulator if input is an array.
if (obj instanceof Array) return __lastN(obj, expr, options);
const { input, n } = computeValue(obj, expr, null, options) as InputExpr;
if (isNil(input)) return null;
assert(isArray(input), "Must resolve to an array/null or missing");
return __lastN(input as RawObject[], { n, input: "$$this" }, options);
}
17 changes: 17 additions & 0 deletions test/operators/accumulator/lastN.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,21 @@ samples.runTestPipeline("operators/accumulator/lastN", [
{ _id: { gameId: "G2" }, gamescores: [80] },
],
},
{
message: "Using $lastN as an Aggregation Expression",
input: [{ array: [10, 20, 30, 40] }],
pipeline: [
{
$project: {
lastThreeElements: {
$lastN: {
input: "$array",
n: 3,
},
},
},
},
],
expected: [{ lastThreeElements: [20, 30, 40] }],
},
]);

0 comments on commit 4f0ec93

Please sign in to comment.