jacoscaz / quadstore

A LevelDB-backed graph database for JS runtimes (Node.js, Deno, browsers, ...) supporting SPARQL queries and the RDF/JS interface.

Home Page:https://github.com/jacoscaz/quadstore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Quadstore appears to match string literals by "begins with" rather than "equals"

Peeja opened this issue · comments

Quadstore seems to be doing something odd. When I use the literal "ab" in a query, it matches any string literal which begins with "ab", not just "ab" itself—that is, it also matches "abc" and "abcd". This behavior is present when using SPARQL queries through quadstore-communica, but also in a raw store.match(), which is presumably where the behavior originates.

I'm seeing this in both 12.0.2 and 13.0.0-alpha.3.

The Jest test below reproduces and illustrates the issue, comparing the results with n3's equivalents.

Jest Test
import { QueryEngine as RdfEngine } from "@comunica/query-sparql-rdfjs";
import { test, describe, expect } from "@jest/globals";
import { MemoryLevel } from "memory-level";
import { DataFactory as DF, Store as N3Store } from "n3";
import { Quadstore } from "quadstore";
import { Engine as QuadstoreEngine } from "quadstore-comunica";

import type * as RDF from "@rdfjs/types";

/**
 * Read all results from an RDF Stream and return them as a promise of an array.
 */
export const readAll = <R>(stream: RDF.ResultStream<R>) =>
  new Promise<R[]>((resolve) => {
    const quads: R[] = [];
    stream
      .on("data", (result: R) => {
        quads.push(result);
      })
      .on("end", () => {
        resolve(quads);
      });
  });

const values = ["a", "ab", "abc", "abcd"];

const quads = values.map((str) =>
  DF.quad(
    DF.namedNode(`http://example.com/subject/${str}`),
    DF.namedNode("http://example.com/predicate"),
    DF.literal(str),
    DF.defaultGraph()
  )
);

const initStores = async () => {
  const n3Store = new N3Store();

  const quadstore = new Quadstore({
    backend: new MemoryLevel(),
    dataFactory: DF,
  });

  await quadstore.open();

  await quadstore.multiPut(quads);
  n3Store.addQuads(quads);
  return { quadstore, n3Store };
};

describe("Quadstore should return the same results as N3.js", () => {
  test("using match()", async () => {
    const { quadstore, n3Store } = await initStores();

    const findWithMatch = async (store: RDF.Store, v: string) =>
      (
        await readAll(
          store.match(undefined, undefined, DF.literal(v), undefined)
        )
      ).map((quad) => quad.subject.value);

    expect(await findWithMatch(quadstore, "ab")).toStrictEqual(
      await findWithMatch(n3Store, "ab")
    );

    /*
      - Expected  - 0
      + Received  + 2
      
        Array [
          "http://example.com/subject/ab",
      +   "http://example.com/subject/abc",
      +   "http://example.com/subject/abcd",
        ]
    */
  });

  test("using a query", async () => {
    const { quadstore, n3Store } = await initStores();

    const rdfEngine = new RdfEngine();
    const quadstoreEngine = new QuadstoreEngine(quadstore);

    const findWithQuery = async (
      engine: RDF.StringSparqlQueryable<RDF.SparqlResultSupport>,
      context: RDF.QueryStringContext,
      v: string
    ) => {
      const bindingsStream = await engine.queryBindings(
        `SELECT * { ?s <http://example.com/predicate> "${v}" . }`,
        context
      );

      return (await readAll(bindingsStream)).map(
        (bindings) => bindings.get("s")?.value
      );
    };

    expect(await findWithQuery(quadstoreEngine, {}, "ab")).toStrictEqual(
      await findWithQuery(rdfEngine, { sources: [n3Store] }, "ab")
    );

    /*
      - Expected  - 0
      + Received  + 2
      
        Array [
          "http://example.com/subject/ab",
      +   "http://example.com/subject/abc",
      +   "http://example.com/subject/abcd",
        ]
    */
  });
});

No worries, enjoy your break!

PR merged and released in 12.1.0!