Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support to search by $id #99

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading