Skip to content

Commit

Permalink
Added support for pagination into find method
Browse files Browse the repository at this point in the history
  • Loading branch information
karelklima committed Nov 22, 2023
1 parent 3078db0 commit 6859294
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 38 deletions.
11 changes: 9 additions & 2 deletions library/lens/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,15 @@ export class Lens<S extends SchemaPrototype, I = SchemaInterface<S>> {
return this.decode(graph);
}

async find(where?: string | RDF.Quad[], limit?: number) {
const q = this.queryBuilder.getQuery(where, limit);
async find(
options: { where?: string | RDF.Quad[]; take?: number; skip?: number } = {},
) {
const { where, take, skip } = {
take: 1000,
skip: 0,
...options,
};
const q = this.queryBuilder.getQuery(where, take, skip);
// TODO: console.log(q);
const graph = await this.engine.queryGraph(q);
return this.decode(graph);
Expand Down
20 changes: 6 additions & 14 deletions library/lens/query_builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Property, Schema } from "../schema/mod.ts";
import { type Schema } from "../schema/mod.ts";
import { getSchemaProperties } from "../schema/mod.ts";
import {
CONSTRUCT,
Expand All @@ -13,19 +13,19 @@ import rdf from "../namespaces/rdf.ts";

import { encode } from "../encoder.ts";

import type { Entity } from "./types.ts";
import { type Entity } from "./types.ts";
import { QueryHelper } from "./query_helper.ts";

export class QueryBuilder {
private readonly schema: Schema;
private readonly schemaProperties: Record<string, Property>;
private readonly context: Context;
private readonly takeDefault: number;
private readonly df: RDF.DataFactory;

constructor(schema: Schema, context: Context) {
this.schema = schema;
this.schemaProperties = getSchemaProperties(this.schema);
this.context = context;
this.takeDefault = 1000;
this.df = new DataFactory();
}

Expand All @@ -37,14 +37,6 @@ export class QueryBuilder {
);
}

private getTypesSignature() {
return this.df.quad(
this.df.variable!("iri"),
this.df.namedNode(rdf.type),
this.df.variable!("iri_type"),
);
}

private entitiesToQuads(entities: Entity[]) {
const quadArrays = entities.map((entity) =>
encode(entity, this.schema, this.context)
Expand Down Expand Up @@ -113,13 +105,13 @@ export class QueryBuilder {
return SELECT`(count(?iri) as ?count)`.WHERE`${quads}`.build();
}

getQuery(where?: string | RDF.Quad[], limit = 1000) {
getQuery(where?: string | RDF.Quad[], limit = this.takeDefault, offset = 0) {
const selectSubQuery = SELECT.DISTINCT`
${this.df.variable!("iri")}
`.WHERE`
${this.getShape(false, true)}
${where}
`.LIMIT(limit).build();
`.LIMIT(limit).OFFSET(offset).build();

const query = CONSTRUCT`
${this.getResourceSignature()}
Expand Down
1 change: 1 addition & 0 deletions library/rdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
export type LDkitContext = {
graph?: string;
language?: string;
take?: number;
};

export type Context =
Expand Down
27 changes: 5 additions & 22 deletions tests/lens.test.ts → tests/lens_common.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { assert, assertEquals, Comunica, equal } from "./test_deps.ts";

import {
createStore,
createStoreContext,
emptyStore,
ttl,
x,
} from "./test_utils.ts";
import { initStore, ttl, x } from "./test_utils.ts";

import { createLens } from "../library/lens/mod.ts";
import { rdf, xsd } from "../library/namespaces/mod.ts";
Expand Down Expand Up @@ -82,22 +76,11 @@ const Kubrick = createDirector("StanleyKubrick", "Stanley Kubrick");
const engine = new Comunica();
const _ = new DataFactory();

const init = () => {
const store = createStore();
export const init = () => {
const { store, context, assertStore, empty } = initStore();
store.addQuads(defaultStoreContent);
const context = createStoreContext(store, {
sources: [{ type: "rdfjsSource", value: store }],
});
const directors = createLens(Director, context, engine);
const movies = createLens(Movie, context, engine);
const assertStore = (turtle: string) => {
const storeQuads = store.getQuads(null, null, null, null);
const expectedQuads = ttl(turtle);
assertEquals(storeQuads, expectedQuads);
};
const empty = async () => {
await emptyStore(store);
};
return { directors, movies, assertStore, empty };
};

Expand Down Expand Up @@ -128,7 +111,7 @@ Deno.test("Resource / Get multiple resources by IRI", async () => {
Deno.test("Resource / Get resource by string condition", async () => {
const { directors } = init();
const condition = `?iri <${x.name}> "Quentin Tarantino" .`;
const result = await directors.find(condition);
const result = await directors.find({ where: condition });

assertEquals(result.length, 1);
assertEquals(result[0], Tarantino);
Expand All @@ -141,7 +124,7 @@ Deno.test("Resource / Get resource by quad condition", async () => {
_.namedNode(x.name),
_.literal("Quentin Tarantino"),
);
const result = await directors.find([condition]);
const result = await directors.find({ where: [condition] });

assertEquals(result.length, 1);
assertEquals(result[0], Tarantino);
Expand Down
74 changes: 74 additions & 0 deletions tests/lens_pagination.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { assertEquals, Comunica } from "./test_deps.ts";

import { initStore, x } from "./test_utils.ts";

import { createLens } from "../library/lens/mod.ts";
import { xsd } from "../library/namespaces/mod.ts";
import { DataFactory } from "../library/rdf.ts";

import { type SchemaInterface } from "../library/schema/mod.ts";

const engine = new Comunica();
const _ = new DataFactory();

const Item = {
index: x.index,
} as const;

type ItemType = SchemaInterface<typeof Item>;

const range = (start: number, end: number) =>
[...Array(1 + end - start).keys()].map((i) => i + start);

const defaultStoreContent = range(0, 99).map((i) =>
_.quad(
_.namedNode(x.Item + i),
_.namedNode(x.index),
_.literal(i.toString(), xsd.integer),
)
);

const assertContainsRange = (items: ItemType[], start: number, end: number) => {
const actual = items.map((item) => item.index);
const expected = range(start, end).map((it) => it.toString());
assertEquals(actual, expected);
};

const init = () => {
const { store, context, assertStore, empty } = initStore();
store.addQuads(defaultStoreContent);
const items = createLens(Item, context, engine);
return { items, assertStore, empty };
};

Deno.test("Lens / Pagination / Take", async () => {
const { items } = init();

const result1 = await items.find({ take: 1 });
assertContainsRange(result1, 0, 0);

const result10 = await items.find({ take: 10 });
assertContainsRange(result10, 0, 9);

const result100 = await items.find({ take: 100 });
assertContainsRange(result100, 0, 99);

const result1000 = await items.find({ take: 1000 });
assertContainsRange(result1000, 0, 99); // extra items should be ignored
});

Deno.test("Lens / Pagination / Skip", async () => {
const { items } = init();

const result0 = await items.find({ take: 10, skip: 0 });
assertContainsRange(result0, 0, 9);

const result10 = await items.find({ take: 10, skip: 10 });
assertContainsRange(result10, 10, 19);

const result90 = await items.find({ take: 10, skip: 90 });
assertContainsRange(result90, 90, 99);

const result100 = await items.find({ take: 10, skip: 100 });
assertEquals(result100, []); // extra items should be ignored
});
16 changes: 16 additions & 0 deletions tests/test_utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { assertEquals } from "./test_deps.ts";

import {
type Context,
DataFactory,
Expand Down Expand Up @@ -98,3 +100,17 @@ export const emptyStore = (store: N3.Store) => {
stream.on("end", resolve);
});
};

export const initStore = () => {
const store = createStore();
const context = createStoreContext(store);
const assertStore = (turtle: string) => {
const storeQuads = store.getQuads(null, null, null, null);
const expectedQuads = ttl(turtle);
assertEquals(storeQuads, expectedQuads);
};
const empty = async () => {
await emptyStore(store);
};
return { store, context, assertStore, empty };
};

0 comments on commit 6859294

Please sign in to comment.