Skip to content

Commit

Permalink
WIP: finalize quoted triples impl
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensworks committed May 30, 2023
1 parent 43da728 commit 6aab82f
Show file tree
Hide file tree
Showing 19 changed files with 454 additions and 30 deletions.
41 changes: 41 additions & 0 deletions lib/OrderUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { QuadTermName } from 'rdf-terms';
import { QUAD_TERM_NAMES } from 'rdf-terms';
import type { ITermDictionary } from './dictionary/ITermDictionary';
import type { QuadPatternTerms } from './PatternTerm';
import * as RDF from '@rdfjs/types';

Check failure on line 5 in lib/OrderUtils.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 5 in lib/OrderUtils.ts

View workflow job for this annotation

GitHub Actions / lint

`@rdfjs/types` import should occur before type import of `rdf-terms`

export const QUAD_TERM_NAMES_INVERSE: Record<QuadTermName, number> =
<any>Object.fromEntries(QUAD_TERM_NAMES.map((value, key) => [ value, key ]));
Expand Down Expand Up @@ -94,3 +95,43 @@ export function encodeOptionalTerms<E>(

return <(E | undefined)[]> encodedTerms;
}

/**
* Convert a quad patter to a `QuadPatternTerms` type.
* @param subject The subject.
* @param predicate The predicate.
* @param object The object.
* @param graph The graph.
* @param quotedPatterns If the index supports quoted triple filtering.
* @return Tuple A tuple of QuadPatternTerms
* and a boolean indicating if post-filtering will be needed on quoted triples.
* This boolean can only be true if `quotedPatterns` is false, and a quoted triple pattern was present.
*/
export function quadToPattern(
subject: RDF.Term | null | undefined,
predicate: RDF.Term | null | undefined,
object: RDF.Term | null | undefined,
graph: RDF.Term | null | undefined,
quotedPatterns: boolean,
): [ QuadPatternTerms, boolean ] {
let requireQuotedTripleFiltering = false;
const quadPatternTerms = <QuadPatternTerms>
[ subject || undefined, predicate || undefined, object || undefined, graph || undefined ]
.map(term => {
if (term) {
if (term.termType === 'Variable') {
return;
}
if (term.termType === 'Quad') {
if (quotedPatterns) {
return term;
}
requireQuotedTripleFiltering = true;
return;
}
}
return term;
});

return [ quadPatternTerms, requireQuotedTripleFiltering ];
}
2 changes: 1 addition & 1 deletion lib/PatternTerm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type * as RDF from '@rdfjs/types';
/**
* A term that can be used for lookup in an index.
*/
export type PatternTerm = RDF.NamedNode | RDF.BlankNode | RDF.Literal | RDF.DefaultGraph | undefined;
export type PatternTerm = RDF.NamedNode | RDF.BlankNode | RDF.Literal | RDF.DefaultGraph | RDF.Quad | undefined;

/**
* Four pattern terms.
Expand Down
22 changes: 6 additions & 16 deletions lib/RdfStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { TermDictionaryNumberRecordFullTerms } from './dictionary/TermDictionary
import type { IRdfStoreIndex } from './index/IRdfStoreIndex';
import { RdfStoreIndexNestedRecord } from './index/RdfStoreIndexNestedRecord';
import type { IRdfStoreOptions } from './IRdfStoreOptions';
import { getBestIndex, orderQuadComponents } from './OrderUtils';
import { getBestIndex, orderQuadComponents, quadToPattern } from './OrderUtils';
import type { EncodedQuadTerms, QuadPatternTerms } from './PatternTerm';

/**
Expand Down Expand Up @@ -216,23 +216,13 @@ export class RdfStore<E = any, Q extends RDF.BaseQuad = RDF.Quad> implements RDF
object?: RDF.Term | null,
graph?: RDF.Term | null,
): IterableIterator<Q> {
let requireQuotedTripleFiltering = false;
// Check if our dictionary and our indexes have quoted pattern support
const indexesSupportQuotedPatterns = Boolean(this.dictionary.features.quotedTriples) &&
Object.values(this.indexesWrapped).every(wrapped => wrapped.index.features.quotedTripleFiltering);

// Construct a quad pattern array
const quadComponents: QuadPatternTerms = <QuadPatternTerms>
[ subject || undefined, predicate || undefined, object || undefined, graph || undefined ]
.map(term => {
if (term) {
if (term.termType === 'Variable') {
return;
}
if (term.termType === 'Quad') {
requireQuotedTripleFiltering = true;
return;
}
}
return term;
});
const [ quadComponents, requireQuotedTripleFiltering ] =
quadToPattern(subject, predicate, object, graph, indexesSupportQuotedPatterns);

// Determine the best index for this pattern
const indexWrapped = this.indexesWrapped[getBestIndex(this.indexesWrappedComponentOrders, quadComponents)];
Expand Down
19 changes: 19 additions & 0 deletions lib/dictionary/ITermDictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import type * as RDF from '@rdfjs/types';
* and decode values of type E into RDF terms.
*/
export interface ITermDictionary<E> {
/**
* A record indicating supported features of this index.
*/
features: {
/**
* If true, this dictionary implements the `findQuotedTriples` method.
*/
quotedTriples?: boolean;
};
/**
* Encode the given RDF term.
* Multiple invocations of this method with the same term MUST return the same value.
Expand All @@ -23,4 +32,14 @@ export interface ITermDictionary<E> {
* @param encoding An encoded RDF term.
*/
decode: (encoding: E) => RDF.Term;
/**
* Find all quoted triples in this dictionary that match with the given triple pattern.
* @param quotedTriplePattern A triple pattern to match with quoted triples.
*/
findQuotedTriples: (quotedTriplePattern: RDF.Quad) => IterableIterator<RDF.Term>;
/**
* Find all encoded quoted triples in this dictionary that match with the given triple pattern.
* @param quotedTriplePattern A triple pattern to match with quoted triples.
*/
findQuotedTriplesEncoded: (quotedTriplePattern: RDF.Quad) => IterableIterator<E>;
}
9 changes: 9 additions & 0 deletions lib/dictionary/TermDictionaryNumberMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class TermDictionaryNumberMap implements ITermDictionary<number> {
private readonly dictionary: Map<string, number> = new Map();
private readonly reverseDictionary: Map<number, string> = new Map();
private readonly dataFactory: RDF.DataFactory;
public readonly features = { quotedTriples: false };

public constructor(dataFactory: RDF.DataFactory = new DataFactory()) {
this.dataFactory = dataFactory;
Expand Down Expand Up @@ -39,4 +40,12 @@ export class TermDictionaryNumberMap implements ITermDictionary<number> {
}
return stringToTerm(string, this.dataFactory);
}

public findQuotedTriples(quotedTriplePattern: RDF.Quad): IterableIterator<RDF.Term> {
throw new Error('findQuotedTriples is not supported');
}

public findQuotedTriplesEncoded(quotedTriplePattern: RDF.Quad): IterableIterator<number> {
throw new Error('findQuotedTriplesEncoded is not supported');
}
}
9 changes: 9 additions & 0 deletions lib/dictionary/TermDictionaryNumberRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class TermDictionaryNumberRecord implements ITermDictionary<number> {
private readonly dictionary: Record<string, number> = {};
private readonly reverseDictionary: Record<number, string> = {};
private readonly dataFactory: RDF.DataFactory;
public readonly features = { quotedTriples: false };

public constructor(dataFactory: RDF.DataFactory = new DataFactory()) {
this.dataFactory = dataFactory;
Expand Down Expand Up @@ -39,4 +40,12 @@ export class TermDictionaryNumberRecord implements ITermDictionary<number> {
}
return stringToTerm(string, this.dataFactory);
}

public findQuotedTriples(quotedTriplePattern: RDF.Quad): IterableIterator<RDF.Term> {
throw new Error('findQuotedTriples is not supported');
}

public findQuotedTriplesEncoded(quotedTriplePattern: RDF.Quad): IterableIterator<number> {
throw new Error('findQuotedTriplesEncoded is not supported');
}
}
9 changes: 9 additions & 0 deletions lib/dictionary/TermDictionaryNumberRecordFullTerms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class TermDictionaryNumberRecordFullTerms implements ITermDictionary<numb
private readonly dictionary: Record<string, number> = {};
private readonly reverseDictionary: Record<number, RDF.Term> = {};
private readonly dataFactory: RDF.DataFactory;
public readonly features = { quotedTriples: false };

public constructor(dataFactory: RDF.DataFactory = new DataFactory()) {
this.dataFactory = dataFactory;
Expand Down Expand Up @@ -41,4 +42,12 @@ export class TermDictionaryNumberRecordFullTerms implements ITermDictionary<numb
}
return string;
}

public findQuotedTriples(quotedTriplePattern: RDF.Quad): IterableIterator<RDF.Term> {
throw new Error('findQuotedTriples is not supported');
}

public findQuotedTriplesEncoded(quotedTriplePattern: RDF.Quad): IterableIterator<number> {
throw new Error('findQuotedTriplesEncoded is not supported');
}
}
65 changes: 62 additions & 3 deletions lib/dictionary/TermDictionaryQuoted.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type * as RDF from '@rdfjs/types';
import { DataFactory } from 'rdf-data-factory';
import { RdfStoreIndexNestedRecord } from '../index/RdfStoreIndexNestedRecord';
import { encodeOptionalTerms } from '../OrderUtils';
import type { EncodedQuadTerms, QuadPatternTerms } from '../PatternTerm';
import { encodeOptionalTerms, quadToPattern } from '../OrderUtils';
import type { EncodedQuadTerms, QuadPatternTerms, PatternTerm } from '../PatternTerm';
import type { ITermDictionary } from './ITermDictionary';

/**
Expand All @@ -18,6 +18,7 @@ export class TermDictionaryQuoted implements ITermDictionary<number> {
private readonly quotedTriplesDictionary: number[][] = [];
private readonly quotedTriplesReverseDictionary: RdfStoreIndexNestedRecord<number, number>;
private readonly dataFactory: RDF.DataFactory;
public readonly features = { quotedTriples: true };

public constructor(
rawTermDictionary: ITermDictionary<number>,
Expand All @@ -28,7 +29,7 @@ export class TermDictionaryQuoted implements ITermDictionary<number> {
indexCombinations: [[ 'subject', 'predicate', 'object' ]],
// Not required
indexConstructor: <any> undefined,
dictionary: this.plainTermDictionary,
dictionary: this,
dataFactory,
});
this.dataFactory = dataFactory;
Expand Down Expand Up @@ -108,4 +109,62 @@ export class TermDictionaryQuoted implements ITermDictionary<number> {
// Term comes from the plain terms dictionary
return this.plainTermDictionary.decode(encoding);
}

public * findQuotedTriples(quotedTriplePattern: RDF.Quad): IterableIterator<RDF.Term> {
for (const termEncoded of this.findQuotedTriplesEncoded(quotedTriplePattern)) {
yield this.decode(termEncoded);
}
}

public * findQuotedTriplesEncoded(quotedTriplePattern: RDF.Quad): IterableIterator<number> {
const [ patternIn, requireQuotedTripleFiltering ] = quadToPattern(
quotedTriplePattern.subject,
quotedTriplePattern.predicate,
quotedTriplePattern.object,
quotedTriplePattern.graph,
true,
);
const patternInEncoded = [ // TODO: inline below

Check failure on line 127 in lib/dictionary/TermDictionaryQuoted.ts

View workflow job for this annotation

GitHub Actions / lint

Expected comment to be above code

Check failure on line 127 in lib/dictionary/TermDictionaryQuoted.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected comment inline with code
patternIn[0] !== undefined ? this.encodeOptional(patternIn[0]) : undefined,
patternIn[1] !== undefined ? this.encodeOptional(patternIn[1]) : undefined,
patternIn[2] !== undefined ? this.encodeOptional(patternIn[2]) : undefined,
patternIn[3] !== undefined ? this.encodeOptional(patternIn[3]) : undefined,
];

// If the pattern contains quoted patterns (i.e, with variables in sub-quads),
// materialize all sub-patterns
// TODO: transform this to iterable if this works
const patterns: QuadPatternTerms[] = [];
// TODO: Avoid decoding
const matchingS: (RDF.Term | undefined)[] = patternIn[0]?.termType === 'Quad' ?
[ ...this.findQuotedTriples(patternIn[0]) ] :
[ patternIn[0] ];
const matchingP: (RDF.Term | undefined)[] = patternIn[1]?.termType === 'Quad' ?
[ ...this.findQuotedTriples(patternIn[1]) ] :
[ patternIn[1] ];
const matchingO: (RDF.Term | undefined)[] = patternIn[2]?.termType === 'Quad' ?
[ ...this.findQuotedTriples(patternIn[2]) ] :
[ patternIn[2] ];
const matchingG: (RDF.Term | undefined)[] = patternIn[3]?.termType === 'Quad' ?
[ ...this.findQuotedTriples(patternIn[3]) ] :
[ patternIn[3] ];
for (const termS of matchingS) {
for (const termP of matchingP) {
for (const termO of matchingO) {
for (const termG of matchingG) {
patterns.push([ <PatternTerm> termS, <PatternTerm> termP, <PatternTerm> termO, <PatternTerm> termG ]);
}
}
}
}

// Find all terms matching the pattern
for (const pattern of patterns) {
// eslint-disable-next-line unicorn/no-array-callback-reference
for (const term of this.quotedTriplesReverseDictionary.find(pattern)) {
yield TermDictionaryQuoted.BITMASK | this.quotedTriplesReverseDictionary
.getEncoded(<EncodedQuadTerms<number>>encodeOptionalTerms(<QuadPatternTerms>term, this))!;
}
}
}
}
9 changes: 9 additions & 0 deletions lib/dictionary/TermDictionarySymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { ITermDictionary } from './ITermDictionary';
*/
export class TermDictionarySymbol implements ITermDictionary<symbol> {
private readonly dataFactory: RDF.DataFactory;
public readonly features = { quotedTriples: false };

public constructor(dataFactory: RDF.DataFactory = new DataFactory()) {
this.dataFactory = dataFactory;
Expand All @@ -28,4 +29,12 @@ export class TermDictionarySymbol implements ITermDictionary<symbol> {
}
return stringToTerm(string.slice(5), this.dataFactory);
}

public findQuotedTriples(quotedTriplePattern: RDF.Quad): IterableIterator<RDF.Term> {
throw new Error('findQuotedTriples is not supported');
}

public findQuotedTriplesEncoded(quotedTriplePattern: RDF.Quad): IterableIterator<symbol> {
throw new Error('findQuotedTriplesEncoded is not supported');
}
}
11 changes: 11 additions & 0 deletions lib/index/IRdfStoreIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import type { EncodedQuadTerms, QuadPatternTerms, QuadTerms } from '../PatternTe
* It maps quads to values of a certain type V.
*/
export interface IRdfStoreIndex<E, V> {
/**
* A record indicating supported features of this index.
*/
features: {
/**
* If true, this index supports passing quad patterns with quoted quad patterns in the `find` method.
* If false, such quoted quad patterns can not be passed, and must be replaced by `undefined`
* and filtered by the upper store afterwards.
*/
quotedTripleFiltering?: boolean;
};
/**
* Set the value for a key (an encoded quad) in the index.
* @param key An array of encoded terms, ordered in the component order of this index.
Expand Down
3 changes: 3 additions & 0 deletions lib/index/RdfStoreIndexNestedMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type { IRdfStoreIndex } from './IRdfStoreIndex';
export class RdfStoreIndexNestedMap<E, V> implements IRdfStoreIndex<E, V> {
private readonly dictionary: ITermDictionary<E>;
private readonly nestedMap: NestedMapActual<E, V>;
public readonly features = {
quotedTripleFiltering: false,
};

public constructor(options: IRdfStoreOptions<E>) {
this.dictionary = options.dictionary;
Expand Down
25 changes: 25 additions & 0 deletions lib/index/RdfStoreIndexNestedMapRecursive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as RDF from '@rdfjs/types';
import { QUAD_TERM_NAMES } from 'rdf-terms';
import type { ITermDictionary } from '../dictionary/ITermDictionary';
import type { IRdfStoreOptions } from '../IRdfStoreOptions';
import { encodeOptionalTerms } from '../OrderUtils';
Expand All @@ -12,6 +13,9 @@ import type { IRdfStoreIndex } from './IRdfStoreIndex';
export class RdfStoreIndexNestedMapRecursive<E, V> implements IRdfStoreIndex<E, V> {
private readonly dictionary: ITermDictionary<E>;
private readonly nestedMap: NestedMapActual<E, V>;
public readonly features = {
quotedTripleFiltering: true,
};

public constructor(options: IRdfStoreOptions<E>) {
this.dictionary = options.dictionary;
Expand Down Expand Up @@ -112,6 +116,17 @@ export class RdfStoreIndexNestedMapRecursive<E, V> implements IRdfStoreIndex<E,
partialQuad[index] = this.dictionary.decode(key);
yield * this.findInner(index + 1, terms, <NestedMapActual<E, V>>subMap, partialQuad);
}
} else if (currentTerm.termType === 'Quad' && this.quadHasVariables(currentTerm)) {
const quotedTriplesEncoded: IterableIterator<E> = this.dictionary.findQuotedTriplesEncoded(currentTerm);
// Below, we perform a type of inner (hash) join between quotedTriplesEncoded and map (with hash on map)
// TODO: investigate different types of join?!?
for (const quotedTripleEncoded of quotedTriplesEncoded) {
const subMap = map.get(quotedTripleEncoded);
if (subMap) {
partialQuad[index] = this.dictionary.decode(quotedTripleEncoded);
yield * this.findInner(index + 1, terms, <NestedMapActual<E, V>>subMap, partialQuad);
}
}
} else {
// If the current term is defined, find one matching map for the current term.
const encodedTerm = this.dictionary.encodeOptional(currentTerm);
Expand Down Expand Up @@ -167,6 +182,16 @@ export class RdfStoreIndexNestedMapRecursive<E, V> implements IRdfStoreIndex<E,

return count;
}

public quadHasVariables(currentTerm: RDF.Quad): boolean { // TODO: move?

Check failure on line 186 in lib/index/RdfStoreIndexNestedMapRecursive.ts

View workflow job for this annotation

GitHub Actions / lint

Expected comment to be above code

Check failure on line 186 in lib/index/RdfStoreIndexNestedMapRecursive.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected comment inline with code
for (const component of QUAD_TERM_NAMES) {
const subTerm = currentTerm[component];
if (subTerm.termType === 'Variable' || (subTerm.termType === 'Quad' && this.quadHasVariables(subTerm))) {
return true;
}
}
return false;
}
}

export type NestedMap<E, V> = NestedMapActual<E, V> | V;
Expand Down

0 comments on commit 6aab82f

Please sign in to comment.