@jahed/sparql-engine
Version:
SPARQL query engine for servers and web browsers.
316 lines • 9.95 kB
JavaScript
import { isNull, isUndefined } from "lodash-es";
import { stringToTerm, termToString } from "rdf-string";
import { isVariable } from "../utils/rdf.js";
/**
* A set of mappings from a variable to a RDF Term.
* @abstract
*/
export class Bindings {
_properties;
constructor() {
this._properties = new Map();
}
/**
* Get metadata attached to the set using a key
* @param key - Metadata key
* @return The metadata associated with the given key
*/
getProperty(key) {
return this._properties.get(key);
}
/**
* Check if a metadata with a given key is attached to the set
* @param key - Metadata key
* @return Tur if the metadata exists, False otherwise
*/
hasProperty(key) {
return this._properties.has(key);
}
/**
* Attach metadata to the set
* @param key - Key associated to the value
* @param value - Value to attach
*/
setProperty(key, value) {
this._properties.set(key, value);
}
/**
* Serialize the set of mappings as a plain JS Object
* @return The set of mappings as a plain JS Object
*/
toObject() {
return this.reduce((acc, variable, value) => {
acc[variable] = value;
return acc;
}, {});
}
/**
* Serialize the set of mappings as a string
* @return The set of mappings as a string
*/
toString() {
const value = this.reduce((acc, variable, value) => {
return `${acc} ${variable} -> ${termToString(value)},`;
}, "{");
return value.substring(0, value.length - 1) + " }";
}
/**
* Creates a deep copy of the set of mappings
* @return A deep copy of the set
*/
clone() {
const cloned = this.empty();
// copy properties then values
if (this._properties.size > 0) {
this._properties.forEach((value, key) => {
cloned.setProperty(key, value);
});
}
this.forEach((variable, value) => {
cloned.set(variable, value);
});
return cloned;
}
/**
* Test the equality between two sets of mappings
* @param other - A set of mappings
* @return True if the two sets are equal, False otherwise
*/
equals(other) {
if (this.size !== other.size) {
return false;
}
for (let variable in other.variables()) {
if (!this.has(variable) || this.get(variable) !== other.get(variable)) {
return false;
}
}
return true;
}
/**
* Bound a triple pattern using the set of mappings, i.e., substitute variables in the triple pattern
* @param triple - Triple pattern
* @return An new, bounded triple pattern
*/
bound(triple) {
const newTriple = Object.assign({}, triple);
if (isVariable(triple.subject) && this.has(triple.subject.value)) {
newTriple.subject = this.get(triple.subject.value);
}
if (isVariable(triple.predicate) && this.has(triple.predicate.value)) {
newTriple.predicate = this.get(triple.predicate.value);
}
if (isVariable(triple.object) && this.has(triple.object.value)) {
newTriple.object = this.get(triple.object.value);
}
return newTriple;
}
/**
* Creates a new bindings with additionnal mappings
* @param values - Pairs [variable, value] to add to the set
* @return A new Bindings with the additionnal mappings
*/
extendMany(values) {
const cloned = this.clone();
values.forEach((v) => {
cloned.set(v[0], v[1]);
});
return cloned;
}
/**
* Perform the union of the set of mappings with another set
* @param other - Set of mappings
* @return The Union set of mappings
*/
union(other) {
const cloned = this.clone();
other.forEach((variable, value) => {
cloned.set(variable, value);
});
return cloned;
}
/**
* Perform the intersection of the set of mappings with another set
* @param other - Set of mappings
* @return The intersection set of mappings
*/
intersection(other) {
const res = this.empty();
this.forEach((variable, value) => {
if (other.has(variable) && other.get(variable) === value) {
res.set(variable, value);
}
});
return res;
}
/**
* Performs a set difference with another set of mappings, i.e., A.difference(B) returns all mappings that are in A and not in B.
* @param other - Set of mappings
* @return The results of the set difference
*/
difference(other) {
return this.filter((variable, value) => {
return !other.has(variable) || value !== other.get(variable);
});
}
/**
* Test if the set of bindings is a subset of another set of mappings.
* @param other - Superset of mappings
* @return Ture if the set of bindings is a subset of another set of mappings, False otherwise
*/
isSubset(other) {
return Array.from(this.variables()).every((v) => {
return other.has(v) && other.get(v) === this.get(v);
});
}
/**
* Creates a new set of mappings using a function to transform the current set
* @param mapper - Transformation function (variable, value) => [string, string]
* @return A new set of mappings
*/
map(mapper) {
const result = this.empty();
this.forEach((variable, value) => {
let [newVar, newValue] = mapper(variable, value);
if (!(isNull(newVar) ||
isUndefined(newVar) ||
isNull(newValue) ||
isUndefined(newValue))) {
result.set(newVar, newValue);
}
});
return result;
}
/**
* Same as map, but only transform variables
* @param mapper - Transformation function
* @return A new set of mappings
*/
mapVariables(mapper) {
return this.map((variable, value) => [mapper(variable, value), value]);
}
/**
* Same as map, but only transform values
* @param mapper - Transformation function
* @return A new set of mappings
*/
mapValues(mapper) {
return this.map((variable, value) => [variable, mapper(variable, value)]);
}
/**
* Filter mappings from the set of mappings using a predicate function
* @param predicate - Predicate function
* @return A new set of mappings
*/
filter(predicate) {
return this.map((variable, value) => {
if (predicate(variable, value)) {
return [variable, value];
}
return [null, null];
});
}
/**
* Reduce the set of mappings to a value which is the accumulated result of running each element in collection thru a reducing function, where each successive invocation is supplied the return value of the previous.
* @param reducer - Reducing function
* @param start - Value used to start the accumulation
* @return The accumulated value
*/
reduce(reducer, start) {
let acc = start;
this.forEach((variable, value) => {
acc = reducer(acc, variable, value);
});
return acc;
}
/**
* Test if some mappings in the set pass a predicate function
* @param predicate - Function to test for each mapping
* @return True if some mappings in the set some the predicate function, False otheriwse
*/
some(predicate) {
let res = false;
this.forEach((variable, value) => {
res = res || predicate(variable, value);
});
return res;
}
/**
* Test if every mappings in the set pass a predicate function
* @param predicate - Function to test for each mapping
* @return True if every mappings in the set some the predicate function, False otheriwse
*/
every(predicate) {
let res = true;
this.forEach((variable, value) => {
res = res && predicate(variable, value);
});
return res;
}
}
/**
* A set of mappings from a variable to a RDF Term, implements using a HashMap
*/
export class BindingBase extends Bindings {
_content;
constructor() {
super();
this._content = new Map();
}
get size() {
return this._content.size;
}
get isEmpty() {
return this.size === 0;
}
/**
* Creates a set of mappings from a plain Javascript Object
* @param obj - Source object to turn into a set of mappings
* @return A set of mappings
*/
static fromObject(obj) {
const res = new BindingBase();
for (let key in obj) {
res.set(key, obj[key]);
}
return res;
}
static fromValuePatternRow(row) {
const res = new BindingBase();
for (let variableString in row) {
const v = row[variableString];
if (v) {
res.set(stringToTerm(variableString).value, v);
}
}
return res;
}
variables() {
return this._content.keys();
}
values() {
return this._content.values();
}
get(variable) {
if (this._content.has(variable)) {
return this._content.get(variable);
}
return null;
}
has(variable) {
return this._content.has(variable);
}
set(variable, value) {
this._content.set(variable, value);
}
clear() {
this._content.clear();
}
empty() {
return new BindingBase();
}
forEach(callback) {
this._content.forEach((value, variable) => callback(variable, value));
}
}
//# sourceMappingURL=bindings.js.map