From 718bbb328502f51f243dbc2cf231d1008a50bdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Kl=C3=ADma?= Date: Thu, 11 Jan 2024 22:22:50 +0100 Subject: [PATCH] Added `$in` and `$notIn` search operators (#98) Resolves #97. --- docs/v2/filtering.md | 16 +++++++++++++++- library/lens/search_helper.ts | 20 ++++++++++++++++++++ library/schema/search.ts | 2 ++ tests/e2e/search.test.ts | 31 +++++++++++++++++++++++++++++++ tests/schema.test.ts | 2 ++ 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/docs/v2/filtering.md b/docs/v2/filtering.md index 5bf225c..6e422e3 100644 --- a/docs/v2/filtering.md +++ b/docs/v2/filtering.md @@ -7,7 +7,8 @@ for controlled data retrieval. LDkit allows various search and filtering operations like `$equals`, `$not`, `$contains`, `$strStarts`, `$strEnds`, `$gt`, `$lt`, `$gte`, `$lte`, `$regex`, -`$langMatches`, and `$filter`. Each is illustrated below with examples. +`$langMatches`, `$in`, `$notIn`, and `$filter`. Each is illustrated below with +examples. ### Simple example @@ -70,6 +71,19 @@ await Persons.find({ }); ``` +### Array functions + +```typescript +await Persons.find({ + where: { + name: { + $in: ["Ada", "Alan"], // FILTER (?value IN ("Ada", "Alan")) + $notIn: ["Ada", "Alan"], // FILTER (?value NOT IN ("Ada", "Alan")) + }, + }, +}); +``` + ### Custom filtering On top of the above, it is possible to specify a custom filter function using diff --git a/library/lens/search_helper.ts b/library/lens/search_helper.ts index 6205eb7..297d196 100644 --- a/library/lens/search_helper.ts +++ b/library/lens/search_helper.ts @@ -34,6 +34,7 @@ export class SearchHelper { this.processStringFunctions(); this.processRegex(); this.processLangMatches(); + this.processArrayFunctions(); this.processFilter(); } @@ -102,6 +103,25 @@ export class SearchHelper { ); } + private processArrayFunctions() { + const map = { + $in: "IN", + $notIn: "NOT IN", + }; + + for (const [key, func] of Object.entries(map)) { + const value = this.searchSchema[key]; + if (value === undefined) { + continue; + } + + const values = (value as unknown[]).map((v) => $`${this.encode(v)}`); + this.addFilter( + $`${this.df.variable(this.varName)} ${func} (${values.join(", ")})`, + ); + } + } + private processFilter() { const value = this.searchSchema.$filter; if (value === undefined) { diff --git a/library/schema/search.ts b/library/schema/search.ts index d66009b..58568b1 100644 --- a/library/schema/search.ts +++ b/library/schema/search.ts @@ -12,6 +12,8 @@ export type SearchFilters = T | { $lte?: T; $regex?: string; $langMatches?: string; + $in?: T[]; + $notIn?: T[]; $filter?: SparqlValue; }; diff --git a/tests/e2e/search.test.ts b/tests/e2e/search.test.ts index 911b458..d7663b7 100644 --- a/tests/e2e/search.test.ts +++ b/tests/e2e/search.test.ts @@ -217,3 +217,34 @@ Deno.test("E2E / Search / $filter", async () => { assertEquals(results.length, 1); assertEquals(results[0].name, "Quentin Tarantino"); }); + +Deno.test("E2E / Search / $in", async () => { + const { Directors } = init(); + + const results = await Directors.find({ + where: { + name: { + $in: ["Quentin Tarantino", "Steven Spielberg"], + }, + }, + }); + + assertEquals(results.length, 2); + assertEquals(results[0].name, "Quentin Tarantino"); + assertEquals(results[1].name, "Steven Spielberg"); +}); + +Deno.test("E2E / Search / $notIn", async () => { + const { Directors } = init(); + + const results = await Directors.find({ + where: { + name: { + $notIn: ["Quentin Tarantino", "Steven Spielberg"], + }, + }, + }); + + assertEquals(results.length, 1); + assertEquals(results[0].name, "Stanley Kubrick"); +}); diff --git a/tests/schema.test.ts b/tests/schema.test.ts index 1166307..cb272b8 100644 --- a/tests/schema.test.ts +++ b/tests/schema.test.ts @@ -37,6 +37,8 @@ type PropertySearch = T | { $lte?: T; $regex?: string; $langMatches?: string; + $in?: T[]; + $notIn?: T[]; $filter?: SparqlValue; };