diff --git a/.gitignore b/.gitignore index 80995a9..01355d6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ node_modules .parcel-cache package-lock.json -ontologies \ No newline at end of file +ontologies + +next-movie-database +react-movie-database \ No newline at end of file diff --git a/docs/v2/filtering.md b/docs/v2/filtering.md index 6e422e3..7f789ad 100644 --- a/docs/v2/filtering.md +++ b/docs/v2/filtering.md @@ -7,8 +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`, `$in`, `$notIn`, and `$filter`. Each is illustrated below with -examples. +`$langMatches`, `$in`, `$notIn`, `$filter`, and `$id`. Each is illustrated below +with examples. ### Simple example @@ -100,3 +100,20 @@ await Persons.find({ }, }); ``` + +### Restricting the results to specific entities + +Sometimes it is useful to query for specific entities by their IRI, and also +restrict the results further by filtering out some properties. For example, +query for an entity, but match only its label with a specific language. + +```typescript +await Persons.find({ + where: { + $id: "https://example.org/Ada_Lovelace", // the only entity matched + name: { + $langMatches: "en", // FILTER LANGMATCHES(LANG(?value), "en") + }, + }, +}); +``` diff --git a/library/lens/query_builder.ts b/library/lens/query_builder.ts index 97692a5..7064c94 100644 --- a/library/lens/query_builder.ts +++ b/library/lens/query_builder.ts @@ -180,6 +180,11 @@ export class QueryBuilder { } getSearchQuery(where: SearchSchema, limit: number, offset: number) { + if (where.$id) { + const ids = Array.isArray(where.$id) ? where.$id : [where.$id]; + return this.getByIrisQuery(ids, where); + } + const selectSubQuery = SELECT.DISTINCT` ${this.df.variable!("iri")} `.WHERE` @@ -199,7 +204,7 @@ export class QueryBuilder { return query; } - getByIrisQuery(iris: IRI[]) { + getByIrisQuery(iris: IRI[], where?: SearchSchema) { const query = CONSTRUCT` ${this.getResourceSignature()} ${this.getShape(Flags.UnwrapOptional | Flags.IgnoreInverse)} @@ -207,7 +212,7 @@ export class QueryBuilder { VALUES ?iri { ${iris.map(this.df.namedNode)} } - ${this.getShape(Flags.IncludeTypes)} + ${this.getShape(Flags.IncludeTypes, where)} `.build(); return query; diff --git a/library/schema/interface.ts b/library/schema/interface.ts index 43da55a..c781cf1 100644 --- a/library/schema/interface.ts +++ b/library/schema/interface.ts @@ -1,3 +1,4 @@ +import { IRI } from "../rdf.ts"; import type { SupportedDataTypes } from "./data_types.ts"; import type { Property, Schema } from "./schema.ts"; import type { SearchFilters } from "./search.ts"; @@ -61,7 +62,7 @@ type ConvertProperty = T extends Property /** Object that contains IRI of an entity */ export type Identity = { - $id: string; + $id: IRI; }; /** @@ -121,7 +122,7 @@ export type SchemaUpdateInterface = }; type ConvertSearchPropertySchema = T extends - { "@schema": Schema } ? Unite> + { "@schema": Schema } ? Unite> : IsInverse extends true ? never : SearchFilters>; @@ -137,13 +138,19 @@ type InverseProperties = InversePropertiesMap< T >[keyof InversePropertiesMap]; +type SchemaSearchInterfaceProperties = { + [X in Exclude>]?: T[X] extends + ValidPropertyDefinition ? ConvertSearchProperty + : never; +}; + /** * Describes a shape of data for updating an entity, according to its data schema. * * See {@link Lens.prototype.find} for usage example. */ -export type SchemaSearchInterface = { - [X in Exclude>]?: T[X] extends - ValidPropertyDefinition ? ConvertSearchProperty - : never; -}; +export type SchemaSearchInterface = + & { + $id?: IRI | IRI[]; + } + & SchemaSearchInterfaceProperties; diff --git a/tests/e2e/search.test.ts b/tests/e2e/search.test.ts index d7663b7..8039088 100644 --- a/tests/e2e/search.test.ts +++ b/tests/e2e/search.test.ts @@ -248,3 +248,34 @@ Deno.test("E2E / Search / $notIn", async () => { assertEquals(results.length, 1); assertEquals(results[0].name, "Stanley Kubrick"); }); + +Deno.test("E2E / Search / $id", async () => { + const { Directors } = init(); + + const results = await Directors.find({ + where: { + $id: x.StevenSpielberg, + }, + }); + + assertEquals(results.length, 1); + assertEquals(results[0].name, "Steven Spielberg"); +}); + +Deno.test("E2E / Search / $id and property", async () => { + const { Directors } = init(); + + const results = await Directors.find({ + where: { + $id: [x.StanleyKubrick], + movies: { + $in: ["The Shining", "Jaws", "Pulp Fiction"], + }, + }, + }); + + assertEquals(results.length, 1); + assertEquals(results[0].name, "Stanley Kubrick"); + assertEquals(results[0].movies.length, 1); + assertEquals(results[0].movies[0], "The Shining"); +}); diff --git a/tests/schema.test.ts b/tests/schema.test.ts index cb272b8..0bd7361 100644 --- a/tests/schema.test.ts +++ b/tests/schema.test.ts @@ -18,6 +18,7 @@ import { type SchemaSearchInterface, type SchemaUpdateInterface, } from "../library/schema/mod.ts"; +import { IRI } from "../library/rdf.ts"; type ArrayUpdate = { $set?: T[]; @@ -76,6 +77,7 @@ Deno.test("Schema / Full schema", () => { }; type ThingSearchType = { + $id?: IRI | IRI[]; required?: PropertySearch; optional?: PropertySearch; array?: PropertySearch; @@ -233,6 +235,7 @@ Deno.test("Schema / Basic datatypes", () => { }; type PrototypeSearchInterface = { + $id?: IRI | IRI[]; default?: PropertySearch; string?: PropertySearch; number?: PropertySearch; @@ -294,6 +297,7 @@ Deno.test("Schema / Optional", () => { }; type PrototypeSearchInterface = { + $id?: IRI | IRI[]; optional?: PropertySearch; }; @@ -343,6 +347,7 @@ Deno.test("Schema / Array", () => { }; type PrototypeSearchInterface = { + $id?: IRI | IRI[]; array?: PropertySearch; optionalArray?: PropertySearch; }; @@ -399,6 +404,7 @@ Deno.test("Schema / Multilang", () => { }; type PrototypeSearchInterface = { + $id?: IRI | IRI[]; multilang?: PropertySearch; multilangArray?: PropertySearch; }; @@ -447,8 +453,9 @@ Deno.test("Schema / Inverse", () => { isPropertyOf?: string; }; - // deno-lint-ignore ban-types - type PrototypeSearchInterface = {}; + type PrototypeSearchInterface = { + $id?: IRI | IRI[]; + }; const PrototypeSchema: ExpandedSchema = { "@type": [], @@ -498,6 +505,7 @@ Deno.test("Schema / Nested schema", () => { }; type PrototypeSearchInterface = { + $id?: IRI | IRI[]; nested?: { nestedValue?: PropertySearch; };