From 6f40ab11c712f37359d7ad10389fde7d313a30a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Kl=C3=ADma?= Date: Fri, 30 Sep 2022 17:20:29 +0200 Subject: [PATCH] Implemented custom bindings factory (#25) Resolves #22 --- library/rdf.ts | 119 +++++++++++++++++++++++++++++-------- specs/engine.test.ts | 1 - specs/rdf.test.ts | 63 ++++++++++++++++++++ specs/treeiterator.test.ts | 1 - 4 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 specs/rdf.test.ts diff --git a/library/rdf.ts b/library/rdf.ts index 81ab860..c444d46 100644 --- a/library/rdf.ts +++ b/library/rdf.ts @@ -1,14 +1,3 @@ -import type { - Bindings, - BlankNode, - Literal, - NamedNode, - Quad, - Term, - Variable, -} from "https://esm.sh/rdf-js@4.0.2"; -export type { Bindings, BlankNode, Literal, NamedNode, Quad, Term, Variable }; - import type * as RDF from "https://esm.sh/rdf-js@4.0.2"; export type { RDF }; @@ -18,8 +7,6 @@ export { fromRdf, toRdf } from "https://esm.sh/rdf-literal@1.3.0"; import { DataFactory } from "https://esm.sh/rdf-data-factory@1.1.1"; export { DataFactory }; -import { BindingsFactory as ComunicaBindingsFactory } from "https://esm.sh/@comunica/bindings-factory@2.2.0"; - import type { IDataSource, IQueryContextCommon, @@ -43,11 +30,11 @@ export type IQueryEngine = RDF.StringSparqlQueryable< export type Iri = string; -export type Node = Map; +export type Node = Map; export type Graph = Map; -export const quadsToGraph = (quads: Quad[]) => { +export const quadsToGraph = (quads: RDF.Quad[]) => { const graph: Graph = new Map(); for (const quad of quads) { const s = quad.subject.value; @@ -69,6 +56,7 @@ export declare namespace RDFJSON { datatype?: string; }; type Bindings = Record; + type Triple = [Iri, Iri, Term]; type SparqlResultsJsonFormat = { head: { vars?: string[]; @@ -86,7 +74,7 @@ export declare namespace RDFJSON { fromJson(jsonBindings: Bindings): RDF.Bindings; } interface QuadFactory { - fromJson(jsonRdf: [Iri, Iri, Term]): RDF.Quad; + fromJson(jsonRdf: Triple): RDF.Quad; } } @@ -116,17 +104,98 @@ export class TermFactory implements RDFJSON.TermFactory { } } -export class BindingsFactory extends ComunicaBindingsFactory - implements RDFJSON.BindingsFactory { - protected readonly localDataFactory: RDF.DataFactory; +export class ReadOnlyBindings implements RDF.Bindings { + public readonly type = "bindings"; + + protected readonly dataFactory: RDF.DataFactory; + protected readonly entries: Map; + protected readonly variables: Map; + + constructor( + bindings: Map, + dataFactory: RDF.DataFactory = new DataFactory(), + ) { + this.entries = bindings; + this.dataFactory = dataFactory; + this.variables = new Map(); + for (const variable of bindings.keys()) { + this.variables.set(variable.value, variable); + } + } + + has(key: string | RDF.Variable) { + const stringKey = typeof key === "string" ? key : key.value; + const variableKey = this.variables.get(stringKey); + return this.entries.has(variableKey!); + } + + get(key: string | RDF.Variable) { + const stringKey = typeof key === "string" ? key : key.value; + const variableKey = this.variables.get(stringKey); + return this.entries.get(variableKey!); + } + + set(_key: string | RDF.Variable, _value: RDF.Term): RDF.Bindings { + throw new Error("Method not implemented."); + } + + delete(_key: string | RDF.Variable): RDF.Bindings { + throw new Error("Method not implemented."); + } + + keys() { + return this.entries.keys(); + } + + values() { + return this.entries.values(); + } + + forEach(fn: (value: RDF.Term, key: RDF.Variable) => unknown) { + return this.entries.forEach(fn); + } + + get size() { + return this.entries.size; + } + + [Symbol.iterator]() { + return this.entries.entries(); + } + + equals(_other: RDF.Bindings | null | undefined): boolean { + throw new Error("Method not implemented."); + } + + filter(_fn: (value: RDF.Term, key: RDF.Variable) => boolean): RDF.Bindings { + throw new Error("Method not implemented."); + } + + map(_fn: (value: RDF.Term, key: RDF.Variable) => RDF.Term): RDF.Bindings { + throw new Error("Method not implemented."); + } + + merge(_other: RDF.Bindings): RDF.Bindings | undefined { + throw new Error("Method not implemented."); + } + + mergeWith( + _merger: (self: RDF.Term, other: RDF.Term, key: RDF.Variable) => RDF.Term, + _other: RDF.Bindings, + ): RDF.Bindings { + throw new Error("Method not implemented."); + } +} + +export class BindingsFactory implements RDFJSON.BindingsFactory { + protected readonly dataFactory: RDF.DataFactory; protected readonly termFactory: RDFJSON.TermFactory; constructor( dataFactory: RDF.DataFactory = new DataFactory(), - termFactory: RDFJSON.TermFactory = new TermFactory(), + termFactory: RDFJSON.TermFactory = new TermFactory(dataFactory), ) { - super(dataFactory); - this.localDataFactory = dataFactory; + this.dataFactory = dataFactory; this.termFactory = termFactory; } @@ -135,11 +204,11 @@ export class BindingsFactory extends ComunicaBindingsFactory [varName, jsonTerm], ) => { return [ - this.localDataFactory.variable!(varName), + this.dataFactory.variable!(varName), this.termFactory.fromJson(jsonTerm), ] as [RDF.Variable, RDF.Term]; }); - return this.bindings(bindingsEntries) as unknown as RDF.Bindings; + return new ReadOnlyBindings(new Map(bindingsEntries), this.dataFactory); } } @@ -148,7 +217,7 @@ export class QuadFactory implements RDFJSON.QuadFactory { protected readonly termFactory: RDFJSON.TermFactory; constructor( dataFactory: RDF.DataFactory = new DataFactory(), - termFactory: RDFJSON.TermFactory = new TermFactory(), + termFactory: RDFJSON.TermFactory = new TermFactory(dataFactory), ) { this.dataFactory = dataFactory; this.termFactory = termFactory; diff --git a/specs/engine.test.ts b/specs/engine.test.ts index 59121e3..00ec9a0 100644 --- a/specs/engine.test.ts +++ b/specs/engine.test.ts @@ -16,7 +16,6 @@ Deno.test("Boolean query with FALSE outcome", async () => { "ASK { ?s ?o }", context, ); - console.log("RESPONSE", response); assertEquals(response, false); }); diff --git a/specs/rdf.test.ts b/specs/rdf.test.ts new file mode 100644 index 0000000..728fddd --- /dev/null +++ b/specs/rdf.test.ts @@ -0,0 +1,63 @@ +import { assert, assertEquals } from "./test_deps.ts"; +import { + BindingsFactory, + DataFactory, + QuadFactory, + type RDF, + type RDFJSON, +} from "../library/rdf.ts"; + +Deno.test("RDF / Quad Factory", () => { + const df = new DataFactory(); + const quadFactory = new QuadFactory(df); + + const q = (s: string, p: string, o: RDF.Quad_Object) => { + return df.quad(df.namedNode(s), df.namedNode(p), o); + }; + + const equalQuads = (triple: RDFJSON.Triple, quad: RDF.Quad) => { + const createdQuad = quadFactory.fromJson(triple); + assertEquals(createdQuad, quad); + }; + + equalQuads( + ["s", "p", { type: "literal", value: "o" }], + q("s", "p", df.literal("o")), + ); + + equalQuads( + ["s", "p", { type: "uri", value: "o" }], + q("s", "p", df.namedNode("o")), + ); +}); + +Deno.test("RDF / Bindings Factory", () => { + const df = new DataFactory(); + const bindingsFactory = new BindingsFactory(df); + + const equalBindings = ( + jsonBindings: RDFJSON.Bindings, + bindingsEntries: [string, RDF.Term][], + ) => { + const createdBindings = bindingsFactory.fromJson(jsonBindings); + const bindings = new Map(bindingsEntries); + assertEquals(createdBindings.size, bindings.size); + bindings.forEach((term, variable) => { + assert(createdBindings.has(variable)); + assertEquals(term, createdBindings.get(variable)); + assertEquals(term, createdBindings.get(df.variable(variable))); + }); + createdBindings.forEach((term, variable) => { + assertEquals(term, bindings.get(variable.value)); + }); + }; + + equalBindings( + { + "var": { type: "literal", value: "v" }, + }, + [ + ["var", df.literal("v")], + ], + ); +}); diff --git a/specs/treeiterator.test.ts b/specs/treeiterator.test.ts index 908e5c3..01599b3 100644 --- a/specs/treeiterator.test.ts +++ b/specs/treeiterator.test.ts @@ -4,7 +4,6 @@ import { type Tree, TreeIterator } from "../library/asynciterator.ts"; async function assertIterator(input: Tree, output: unknown[]) { const i = new TreeIterator(input); const result = await i.toArray(); - console.log("RESULT", result); assertEquals(result, output); }