Skip to content

Commit

Permalink
Added support to search by $id (#99)
Browse files Browse the repository at this point in the history
Resolves #93.
  • Loading branch information
karelklima authored Jan 11, 2024
1 parent 718bbb3 commit a8360ec
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 14 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ node_modules
.parcel-cache

package-lock.json
ontologies
ontologies

next-movie-database
react-movie-database
21 changes: 19 additions & 2 deletions docs/v2/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")
},
},
});
```
9 changes: 7 additions & 2 deletions library/lens/query_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -199,15 +204,15 @@ export class QueryBuilder {
return query;
}

getByIrisQuery(iris: IRI[]) {
getByIrisQuery(iris: IRI[], where?: SearchSchema) {
const query = CONSTRUCT`
${this.getResourceSignature()}
${this.getShape(Flags.UnwrapOptional | Flags.IgnoreInverse)}
`.WHERE`
VALUES ?iri {
${iris.map(this.df.namedNode)}
}
${this.getShape(Flags.IncludeTypes)}
${this.getShape(Flags.IncludeTypes, where)}
`.build();

return query;
Expand Down
21 changes: 14 additions & 7 deletions library/schema/interface.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -61,7 +62,7 @@ type ConvertProperty<T extends ValidPropertyDefinition> = T extends Property

/** Object that contains IRI of an entity */
export type Identity = {
$id: string;
$id: IRI;
};

/**
Expand Down Expand Up @@ -121,7 +122,7 @@ export type SchemaUpdateInterface<T extends Schema> =
};

type ConvertSearchPropertySchema<T extends Property> = T extends
{ "@schema": Schema } ? Unite<SchemaSearchInterface<T["@schema"]>>
{ "@schema": Schema } ? Unite<SchemaSearchInterfaceProperties<T["@schema"]>>
: IsInverse<T> extends true ? never
: SearchFilters<ConvertPropertyType<T>>;

Expand All @@ -137,13 +138,19 @@ type InverseProperties<T extends Schema> = InversePropertiesMap<
T
>[keyof InversePropertiesMap<T>];

type SchemaSearchInterfaceProperties<T extends Schema> = {
[X in Exclude<keyof T, "@type" | InverseProperties<T>>]?: T[X] extends
ValidPropertyDefinition ? ConvertSearchProperty<T[X]>
: 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<T extends Schema> = {
[X in Exclude<keyof T, "@type" | InverseProperties<T>>]?: T[X] extends
ValidPropertyDefinition ? ConvertSearchProperty<T[X]>
: never;
};
export type SchemaSearchInterface<T extends Schema> =
& {
$id?: IRI | IRI[];
}
& SchemaSearchInterfaceProperties<T>;
31 changes: 31 additions & 0 deletions tests/e2e/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
12 changes: 10 additions & 2 deletions tests/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
type SchemaSearchInterface,
type SchemaUpdateInterface,
} from "../library/schema/mod.ts";
import { IRI } from "../library/rdf.ts";

type ArrayUpdate<T> = {
$set?: T[];
Expand Down Expand Up @@ -76,6 +77,7 @@ Deno.test("Schema / Full schema", () => {
};

type ThingSearchType = {
$id?: IRI | IRI[];
required?: PropertySearch<string>;
optional?: PropertySearch<string>;
array?: PropertySearch<string>;
Expand Down Expand Up @@ -233,6 +235,7 @@ Deno.test("Schema / Basic datatypes", () => {
};

type PrototypeSearchInterface = {
$id?: IRI | IRI[];
default?: PropertySearch<string>;
string?: PropertySearch<string>;
number?: PropertySearch<number>;
Expand Down Expand Up @@ -294,6 +297,7 @@ Deno.test("Schema / Optional", () => {
};

type PrototypeSearchInterface = {
$id?: IRI | IRI[];
optional?: PropertySearch<string>;
};

Expand Down Expand Up @@ -343,6 +347,7 @@ Deno.test("Schema / Array", () => {
};

type PrototypeSearchInterface = {
$id?: IRI | IRI[];
array?: PropertySearch<string>;
optionalArray?: PropertySearch<string>;
};
Expand Down Expand Up @@ -399,6 +404,7 @@ Deno.test("Schema / Multilang", () => {
};

type PrototypeSearchInterface = {
$id?: IRI | IRI[];
multilang?: PropertySearch<string>;
multilangArray?: PropertySearch<string>;
};
Expand Down Expand Up @@ -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": [],
Expand Down Expand Up @@ -498,6 +505,7 @@ Deno.test("Schema / Nested schema", () => {
};

type PrototypeSearchInterface = {
$id?: IRI | IRI[];
nested?: {
nestedValue?: PropertySearch<string>;
};
Expand Down

0 comments on commit a8360ec

Please sign in to comment.