UNPKG

@jahed/sparql-engine

Version:

SPARQL query engine for servers and web browsers.

413 lines 16.9 kB
// SPDX-License-Identifier: MIT import { addMilliseconds, isAfter, isBefore, isEqual, subMilliseconds, } from "date-fns"; import { isNull } from "lodash-es"; import { parseISO8601 } from "../../utils/date.js"; import { asJS, createBoolean, createDate, createDecimal, createInteger, createLangLiteral, createTypedLiteral, literalIsBoolean, literalIsDate, literalIsNumeric, RDF, shallowCloneTerm, termIsBNode, termIsIRI, termIsLiteral, UNBOUND, XSD_integer, } from "../../utils/rdf.js"; function digestOperation(hashType) { return async (v) => { return RDF.literal(toHex(await crypto.subtle.digest(hashType, new TextEncoder().encode(v.value)))); }; } function toHex(buffer) { const result = []; for (const byte of new Uint8Array(buffer)) { result.push(byte.toString(16).padStart(2, "0")); } return result.join(""); } /** * Implementation of SPARQL operations found in FILTERS * All arguments are pre-compiled from string to an intermediate representation. * All possible intermediate representation are gathered in the `src/rdf-terms.js` file, * and are used to represents RDF Terms. * Each SPARQL operation is also expected to return the same kind of intermediate representation. */ export default { /* COALESCE function https://www.w3.org/TR/sparql11-query/#func-coalesce */ coalesce: function (baseValue, defaultValue) { if (!isNull(baseValue)) { return baseValue; } else if (!isNull(defaultValue)) { return defaultValue; } return UNBOUND; }, /* IF function https://www.w3.org/TR/sparql11-query/#func-if */ if: function (booleanValue, valueIfTrue, valueIfFalse) { if (isNull(booleanValue) || isNull(valueIfTrue) || isNull(valueIfFalse)) { throw new SyntaxError(`SPARQL expression error: some arguments of an IF function are unbound. Got IF(${booleanValue}, ${valueIfTrue}, ${valueIfFalse})`); } if (termIsLiteral(booleanValue) && (literalIsBoolean(booleanValue) || literalIsNumeric(booleanValue))) { return asJS(booleanValue.value, booleanValue.datatype.value) ? valueIfTrue : valueIfFalse; } throw new SyntaxError(`SPARQL expression error: you are using an IF function whose first argument is expected to be a boolean, but instead got ${booleanValue}`); }, /* XQuery & XPath functions https://www.w3.org/TR/sparql11-query/#OperatorMapping */ "+": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createDate(addMilliseconds(valueA, valueB.getTime())); } return createTypedLiteral(valueA + valueB, a.datatype.value); } return RDF.literal(asJS(a.value, null) + asJS(b.value, null)); }, "-": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createDate(subMilliseconds(valueA, valueB.getTime())); } return createTypedLiteral(valueA - valueB, a.datatype.value); } throw new SyntaxError(`SPARQL expression error: cannot substract non-Literals ${a} and ${b}`); }, "*": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createDate(new Date(valueA.getTime() * valueB.getTime())); } return createTypedLiteral(valueA * valueB, a.datatype.value); } throw new SyntaxError(`SPARQL expression error: cannot multiply non-Literals ${a} and ${b}`); }, "/": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createDate(new Date(valueA.getTime() / valueB.getTime())); } return createTypedLiteral(valueA / valueB, a.datatype.value); } throw new SyntaxError(`SPARQL expression error: cannot divide non-Literals ${a} and ${b}`); }, "=": function (a, b) { return createBoolean(a.equals(b)); }, "!=": function (a, b) { return createBoolean(!a.equals(b)); }, "<": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createBoolean(isBefore(valueA, valueB)); } return createBoolean(valueA < valueB); } return createBoolean(a.value < b.value); }, "<=": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createBoolean(isEqual(valueA, valueB) || isBefore(valueA, valueB)); } return createBoolean(valueA <= valueB); } return createBoolean(a.value <= b.value); }, ">": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createBoolean(isAfter(valueA, valueB)); } return createBoolean(valueA > valueB); } return createBoolean(a.value > b.value); }, ">=": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { const valueA = asJS(a.value, a.datatype.value); const valueB = asJS(b.value, b.datatype.value); if (literalIsDate(a) && literalIsDate(b)) { return createBoolean(isEqual(valueA, valueB) || isAfter(valueA, valueB)); } return createBoolean(valueA >= valueB); } return createBoolean(a.value >= b.value); }, "!": function (a) { if (termIsLiteral(a) && literalIsBoolean(a)) { return createBoolean(!asJS(a.value, a.datatype.value)); } throw new SyntaxError(`SPARQL expression error: cannot compute the negation of a non boolean literal ${a}`); }, "&&": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b) && literalIsBoolean(a) && literalIsBoolean(b)) { return createBoolean(asJS(a.value, a.datatype.value) && asJS(b.value, b.datatype.value)); } throw new SyntaxError(`SPARQL expression error: cannot compute the conjunction of non boolean literals ${a} and ${b}`); }, "||": function (a, b) { if (termIsLiteral(a) && termIsLiteral(b) && literalIsBoolean(a) && literalIsBoolean(b)) { return createBoolean(asJS(a.value, a.datatype.value) || asJS(b.value, b.datatype.value)); } throw new SyntaxError(`SPARQL expression error: cannot compute the disjunction of non boolean literals ${a} and ${b}`); }, /* SPARQL Functional forms https://www.w3.org/TR/sparql11-query/#func-forms */ bound: function (a) { return createBoolean(!isNull(a)); }, sameterm: function (a, b) { return createBoolean(a.value === b.value); }, in: function (a, b) { return createBoolean(b.some((elt) => a.equals(elt))); }, notin: function (a, b) { return createBoolean(!b.some((elt) => a.equals(elt))); }, /* Functions on RDF Terms https://www.w3.org/TR/sparql11-query/#func-rdfTerms */ isiri: function (a) { return createBoolean(termIsIRI(a)); }, isblank: function (a) { return createBoolean(termIsBNode(a)); }, isliteral: function (a) { return createBoolean(termIsLiteral(a)); }, isnumeric: function (a) { return createBoolean(termIsLiteral(a) && literalIsNumeric(a)); }, str: function (a) { return RDF.literal(a.value); }, lang: function (a) { if (termIsLiteral(a)) { return RDF.literal(a.language.toLowerCase()); } return RDF.literal(""); }, datatype: function (a) { if (termIsLiteral(a)) { return a.datatype; } return RDF.literal(""); }, iri: function (a) { return RDF.namedNode(a.value); }, bnode: function (a) { if (a === undefined) { return RDF.blankNode(); } return RDF.blankNode(a.value); }, strdt: function (x, datatype) { return createTypedLiteral(x.value, datatype.value); }, strlang: function (x, lang) { return createLangLiteral(x.value, lang.value); }, uuid: function () { return RDF.namedNode(`urn:uuid:${crypto.randomUUID()}`); }, struuid: function () { return RDF.literal(crypto.randomUUID()); }, /* Functions on Strings https://www.w3.org/TR/sparql11-query/#func-strings */ strlen: function (a) { return createInteger(a.value.length); }, substr: function (str, index, length) { const indexValue = asJS(index.value, XSD_integer); if (indexValue < 1) { throw new SyntaxError("SPARQL SUBSTR error: the index of the first character in a string is 1 (according to the SPARQL W3C specs)"); } let value = str.value.substring(indexValue - 1); if (length !== undefined) { const lengthValue = asJS(length.value, XSD_integer); value = value.substring(0, lengthValue); } return shallowCloneTerm(str, value); }, ucase: function (a) { return shallowCloneTerm(a, a.value.toUpperCase()); }, lcase: function (a) { return shallowCloneTerm(a, a.value.toLowerCase()); }, strstarts: function (term, substring) { const a = term.value; const b = substring.value; return createBoolean(a.startsWith(b)); }, strends: function (term, substring) { const a = term.value; const b = substring.value; return createBoolean(a.endsWith(b)); }, contains: function (term, substring) { const a = term.value; const b = substring.value; return createBoolean(a.indexOf(b) >= 0); }, strbefore: function (term, token) { const index = term.value.indexOf(token.value); const value = index > -1 ? term.value.substring(0, index) : ""; return shallowCloneTerm(term, value); }, strafter: function (str, token) { const index = str.value.indexOf(token.value); const value = index > -1 ? str.value.substring(index + token.value.length) : ""; return shallowCloneTerm(str, value); }, encode_for_uri: function (a) { return RDF.literal(encodeURIComponent(a.value)); }, concat: function (a, b) { if (termIsLiteral(a) && termIsLiteral(b)) { return shallowCloneTerm(a, a.value + b.value); } return RDF.literal(a.value + b.value); }, langmatches: function (langTag, langRange) { // Implements https://tools.ietf.org/html/rfc4647#section-3.3.1 const tag = langTag.value.toLowerCase(); const range = langRange.value.toLowerCase(); const test = tag === range || range === "*" || tag.substr(1, range.length + 1) === range + "-"; return createBoolean(test); }, regex: function (subject, pattern, flags) { const regexp = flags === undefined ? new RegExp(pattern.value) : new RegExp(pattern.value, flags.value); return createBoolean(regexp.test(subject.value)); }, replace: function (arg, pattern, replacement, flags) { const regexp = flags === undefined ? new RegExp(pattern.value) : new RegExp(pattern.value, flags.value); const newValue = arg.value.replace(regexp, replacement.value); if (termIsIRI(arg)) { return RDF.namedNode(newValue); } else if (termIsBNode(arg)) { return RDF.blankNode(newValue); } return shallowCloneTerm(arg, newValue); }, /* Functions on Numerics https://www.w3.org/TR/sparql11-query/#func-numerics */ abs: function (a) { if (termIsLiteral(a) && literalIsNumeric(a)) { return createInteger(Math.abs(asJS(a.value, a.datatype.value))); } throw new SyntaxError(`SPARQL expression error: cannot compute the absolute value of the non-numeric term ${a}`); }, round: function (a) { if (termIsLiteral(a) && literalIsNumeric(a)) { return createInteger(Math.round(asJS(a.value, a.datatype.value))); } throw new SyntaxError(`SPARQL expression error: cannot compute the rounded value of the non-numeric term ${a}`); }, ceil: function (a) { if (termIsLiteral(a) && literalIsNumeric(a)) { return createInteger(Math.ceil(asJS(a.value, a.datatype.value))); } throw new SyntaxError(`SPARQL expression error: cannot compute Math.ceil on the non-numeric term ${a}`); }, floor: function (a) { if (termIsLiteral(a) && literalIsNumeric(a)) { return createInteger(Math.floor(asJS(a.value, a.datatype.value))); } throw new SyntaxError(`SPARQL expression error: cannot compute Math.floor on the non-numeric term ${a}`); }, /* Functions on Dates and Times https://www.w3.org/TR/sparql11-query/#func-date-time */ now: function () { return createDate(new Date()); }, year: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { return createInteger(Number(parseISO8601(a.value).year)); } throw new SyntaxError(`SPARQL expression error: cannot compute the year of the RDF Term ${a}, as it is not a date`); }, month: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { const value = asJS(a.value, a.datatype.value); return createInteger(Number(parseISO8601(a.value).month)); } throw new SyntaxError(`SPARQL expression error: cannot compute the month of the RDF Term ${a}, as it is not a date`); }, day: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { return createInteger(Number(parseISO8601(a.value).day)); } throw new SyntaxError(`SPARQL expression error: cannot compute the day of the RDF Term ${a}, as it is not a date`); }, hours: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { return createInteger(Number(parseISO8601(a.value).hour)); } throw new SyntaxError(`SPARQL expression error: cannot compute the hours of the RDF Term ${a}, as it is not a date`); }, minutes: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { return createInteger(Number(parseISO8601(a.value).minute)); } throw new SyntaxError(`SPARQL expression error: cannot compute the minutes of the RDF Term ${a}, as it is not a date`); }, seconds: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { return createDecimal(Number(parseISO8601(a.value).second)); } throw new SyntaxError(`SPARQL expression error: cannot compute the seconds of the RDF Term ${a}, as it is not a date`); }, tz: function (a) { if (termIsLiteral(a) && literalIsDate(a)) { return RDF.literal(parseISO8601(a.value).timezone); } throw new SyntaxError(`SPARQL expression error: cannot compute the timezone of the RDF Term ${a}, as it is not a date`); }, /* Hash Functions https://www.w3.org/TR/sparql11-query/#func-hash */ md5: async function (a) { return RDF.literal((await import("js-md5")).md5.hex(a.value)); }, sha1: digestOperation("SHA-1"), sha256: digestOperation("SHA-256"), sha384: digestOperation("SHA-384"), sha512: digestOperation("SHA-512"), }; //# sourceMappingURL=sparql-operations.js.map