Skip to content

Commit

Permalink
fix($all): Add regex support to the $all query. Fixes #190
Browse files Browse the repository at this point in the history
  • Loading branch information
kofrasa committed Sep 22, 2021
1 parent 23e3f73 commit 4d418a1
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 12 deletions.
39 changes: 27 additions & 12 deletions src/operators/_predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,35 @@ export function $exists(a: AnyVal, b: AnyVal, options?: Options): boolean {
/**
* Matches arrays that contain all elements specified in the query.
*
* @param a
* @param b
* @param values
* @param queries
* @returns boolean
*/
export function $all(a: RawArray, b: RawArray, options?: Options): boolean {
let matched = false;
if (isArray(a) && isArray(b)) {
for (let i = 0, len = b.length; i < len; i++) {
if (isObject(b[i]) && inArray(Object.keys(b[i]), "$elemMatch")) {
matched = matched || $elemMatch(a, b[i]["$elemMatch"], options);
} else {
// order of arguments matter
return intersection(b, a, options?.hashFunction).length === len;
}
export function $all(
values: RawArray,
queries: RawArray,
options?: Options
): boolean {
if (
!isArray(values) ||
!isArray(queries) ||
!values.length ||
!queries.length
) {
return false;
}

let matched = true;
for (const query of queries) {
// no need to check all the queries.
if (!matched) break;
if (isObject(query) && inArray(Object.keys(query), "$elemMatch")) {
matched = matched && $elemMatch(values, query["$elemMatch"], options);
} else if (query instanceof RegExp) {
matched =
matched && values.some((s) => typeof s === "string" && query.test(s));
} else {
matched = matched && values.some((value) => query === value);
}
}
return matched;
Expand Down
111 changes: 111 additions & 0 deletions test/query_operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const obj = Object.assign({}, samples.personData, { _id: new ObjectId(idStr) });

useOperators(OperatorType.QUERY, { $where });

interface UserResult {
user: { username: string };
}

test("Comparison, Evaluation, and Element Operators", (t) => {
const queries = [
[{ _id: new ObjectId(idStr) }, "can match against user-defined types"],
Expand Down Expand Up @@ -65,6 +69,10 @@ test("Comparison, Evaluation, and Element Operators", (t) => {
{ "languages.spoken": { $all: ["french", "english"] } },
"can check that all values exists in array with $all",
],
[
{ "languages.spoken": { $all: [/french/, /english/] } },
"can check that all values exists in array with $all using regex",
],
[
{ date: { year: 2013, month: 9, day: 25 } },
"can match field with object values",
Expand Down Expand Up @@ -177,6 +185,109 @@ test("Match $all with $elemMatch on nested elements", (t) => {
t.ok(result === 1, "can match using $all with $elemMatch on nested elements");
});

test("Match $all with regex", (t) => {
t.plan(3);

const data = [
{
user: {
username: "User1",
projects: ["foo", "bar"],
},
},
{
user: {
username: "User2",
projects: ["foo", "baz"],
},
},
{
user: {
username: "User3",
projects: ["fizz", "buzz"],
},
},
{
user: {
username: "User4",
projects: [],
},
},
];
const criteria = {
"user.projects": {
$all: ["foo", /^ba/],
},
};
// It should return two user objects
const results = find(data, criteria).all();

t.equal(
results.length,
2,
"can match using $all with regex mixed with strings"
);
t.equal(
(results[0] as UserResult).user.username,
"User1",
"returns user1 using $all with regex"
);
t.equal(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(results[1] as UserResult).user.username,
"User2",
"returns user2 using $all with regex"
);
});

test("Match $all with strings, numbers and empty lists", (t) => {
t.plan(3);

const data = [
{
user: {
username: "User1",
projects: ["foo", 1],
},
},
{
user: {
username: "User2",
projects: ["foo", 2, "1"],
},
},
{
user: {
username: "User3",
projects: [],
},
},
];
const criteria = {
"user.projects": {
$all: ["foo", 1],
},
};
// It should return two user objects
const results = find(data, criteria).all();

t.equal(results.length, 1, "can match using $all with strings and numbers");
t.equal(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(results[0] as UserResult).user.username,
"User1",
"returns user1 using $all with strings and numbers"
);

criteria["user.projects"].$all = [];

t.equal(
find(data, criteria).count(),
0,
"match $all with an empty query returns no items"
);
});

test("Projection $elemMatch operator", (t) => {
const data = [
{
Expand Down

0 comments on commit 4d418a1

Please sign in to comment.