sparqlalgebrajs
Advanced tools
Comparing version 0.5.0 to 0.5.1
#! /usr/bin/env node | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var minimist = require("minimist"); | ||
var sparqlAlgebra_1 = require("../lib/sparqlAlgebra"); | ||
var args = minimist(process.argv.slice(2), { | ||
const minimist = require("minimist"); | ||
const sparqlAlgebra_1 = require("../lib/sparqlAlgebra"); | ||
const args = minimist(process.argv.slice(2), { | ||
boolean: ['q'], | ||
@@ -17,2 +17,3 @@ alias: { q: 'quads' } | ||
} | ||
console.log(JSON.stringify(sparqlAlgebra_1.translate(args._[0], args.q), null, 2)); | ||
console.log(JSON.stringify(sparqlAlgebra_1.default(args._[0], args.q), null, 2)); | ||
//# sourceMappingURL=sparqlalgebrajs.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const sparqlAlgebra_1 = require("./lib/sparqlAlgebra"); | ||
exports.translate = sparqlAlgebra_1.translate; | ||
const Algebra = require("./lib/algebra"); | ||
var sparqlAlgebra_1 = require("./lib/sparqlAlgebra"); | ||
exports.translate = sparqlAlgebra_1.default; | ||
var Algebra = require("./lib/algebra"); | ||
exports.Algebra = Algebra; | ||
//# sourceMappingURL=index.js.map |
@@ -6,2 +6,3 @@ import * as rdfjs from "rdf-js"; | ||
BGP: string; | ||
CONSTRUCT: string; | ||
DESC: string; | ||
@@ -47,2 +48,3 @@ DISTINCT: string; | ||
export interface Operation { | ||
[key: string]: any; | ||
type: string; | ||
@@ -85,3 +87,3 @@ } | ||
type: 'aggregate'; | ||
aggregate: string; | ||
aggregator: string; | ||
separator?: string; | ||
@@ -97,2 +99,6 @@ expression: Expression; | ||
} | ||
export interface Construct extends Single { | ||
type: 'construct'; | ||
template: Pattern[]; | ||
} | ||
export interface Distinct extends Single { | ||
@@ -112,3 +118,3 @@ type: 'distinct'; | ||
type: 'graph'; | ||
graph: rdfjs.Term; | ||
name: rdfjs.Term; | ||
} | ||
@@ -129,3 +135,3 @@ export interface Group extends Single { | ||
type: 'leftjoin'; | ||
expression: Expression; | ||
expression?: Expression; | ||
} | ||
@@ -156,3 +162,3 @@ export interface Link extends Operation { | ||
object: rdfjs.Term; | ||
graph?: rdfjs.Term; | ||
graph: rdfjs.Term; | ||
} | ||
@@ -168,3 +174,2 @@ export interface Pattern extends Operation, rdfjs.Quad { | ||
type: 'reduced'; | ||
input: Operation; | ||
} | ||
@@ -177,3 +182,3 @@ export interface Seq extends Double { | ||
start: number; | ||
length: number; | ||
length?: number; | ||
} | ||
@@ -186,3 +191,3 @@ export interface Union extends Double { | ||
variables: rdfjs.Variable[]; | ||
bindings: any[]; | ||
bindings: Map<rdfjs.Variable, rdfjs.Term>[]; | ||
} | ||
@@ -189,0 +194,0 @@ export interface ZeroOrMorePath extends Operation { |
@@ -9,2 +9,3 @@ "use strict"; | ||
BGP: 'bgp', | ||
CONSTRUCT: 'construct', | ||
DESC: 'desc', | ||
@@ -49,1 +50,2 @@ DISTINCT: 'distinct', | ||
}); | ||
//# sourceMappingURL=algebra.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Factory = /** @class */ (function () { | ||
function Factory() { | ||
class Factory { | ||
static createAlt(left, right) { return { type: 'alt', left, right }; } | ||
static createAggregate(aggregator, expression, separator) { | ||
if (separator) | ||
return { type: 'aggregate', aggregator, expression, separator }; | ||
return { type: 'aggregate', aggregator, expression }; | ||
} | ||
Factory.createAlt = function (left, right) { return { type: 'alt', left: left, right: right }; }; | ||
Factory.createAggregate = function (aggregate, expression, separator) { | ||
if (separator) | ||
return { type: 'aggregate', aggregate: aggregate, expression: expression, separator: separator }; | ||
return { type: 'aggregate', aggregate: aggregate, expression: expression }; | ||
}; | ||
Factory.createBoundAggregate = function (variable, aggregate, expression, separator) { | ||
var result = Factory.createAggregate(aggregate, expression, separator); | ||
static createBoundAggregate(variable, aggregate, expression, separator) { | ||
let result = Factory.createAggregate(aggregate, expression, separator); | ||
result.variable = variable; | ||
return result; | ||
}; | ||
Factory.createBgp = function (patterns) { return { type: 'bgp', patterns: patterns }; }; | ||
Factory.createDistinct = function (input) { return { type: 'distinct', input: input }; }; | ||
Factory.createExtend = function (input, variable, expression) { return { type: 'extend', input: input, variable: variable, expression: expression }; }; | ||
Factory.createFilter = function (input, expression) { return { type: 'filter', input: input, expression: expression }; }; | ||
Factory.createGraph = function (input, graph) { return { type: 'graph', input: input, graph: graph }; }; | ||
Factory.createGroup = function (input, expressions, aggregates) { return { type: 'group', input: input, expressions: expressions, aggregates: aggregates }; }; | ||
Factory.createInv = function (path) { return { type: 'inv', path: path }; }; | ||
Factory.createJoin = function (left, right) { return { type: 'join', left: left, right: right }; }; | ||
Factory.createLeftJoin = function (left, right, expression) { return { type: 'leftjoin', left: left, right: right, expression: expression }; }; | ||
Factory.createLink = function (iri) { return { type: 'link', iri: iri }; }; | ||
Factory.createMinus = function (left, right) { return { type: 'minus', left: left, right: right }; }; | ||
Factory.createNps = function (iris) { return { type: 'nps', iris: iris }; }; | ||
Factory.createOneOrMorePath = function (path) { return { type: 'OneOrMorePath', path: path }; }; | ||
Factory.createOrderBy = function (input, expressions) { return { type: 'orderby', input: input, expressions: expressions }; }; | ||
Factory.createPath = function (subject, predicate, object, graph) { | ||
if (graph) | ||
return { type: 'path', subject: subject, predicate: predicate, object: object, graph: graph }; | ||
return { type: 'path', subject: subject, predicate: predicate, object: object }; | ||
}; | ||
Factory.createProject = function (input, variables) { return { type: 'project', input: input, variables: variables }; }; | ||
Factory.createReduced = function (input) { return { type: 'reduced', input: input }; }; | ||
return Factory; | ||
}()); | ||
} | ||
static createBgp(patterns) { return { type: 'bgp', patterns }; } | ||
static createConstruct(input, template) { return { type: 'construct', input, template }; } | ||
static createDistinct(input) { return { type: 'distinct', input }; } | ||
static createExtend(input, variable, expression) { return { type: 'extend', input, variable, expression }; } | ||
static createFilter(input, expression) { return { type: 'filter', input, expression }; } | ||
static createGraph(input, name) { return { type: 'graph', input, name }; } | ||
static createGroup(input, expressions, aggregates) { return { type: 'group', input, expressions, aggregates }; } | ||
static createInv(path) { return { type: 'inv', path }; } | ||
static createJoin(left, right) { return { type: 'join', left, right }; } | ||
static createLeftJoin(left, right, expression) { | ||
if (expression) | ||
return { type: 'leftjoin', left, right, expression }; | ||
return { type: 'leftjoin', left, right }; | ||
} | ||
static createLink(iri) { return { type: 'link', iri }; } | ||
static createMinus(left, right) { return { type: 'minus', left, right }; } | ||
static createNps(iris) { return { type: 'nps', iris }; } | ||
static createOneOrMorePath(path) { return { type: 'OneOrMorePath', path }; } | ||
static createOrderBy(input, expressions) { return { type: 'orderby', input, expressions }; } | ||
static createPath(subject, predicate, object, graph) { return { type: 'path', subject, predicate, object, graph }; } | ||
// TODO: cast needed due to missing equals method (could use https://github.com/rdf-ext/rdf-data-model ) | ||
static createPattern(subject, predicate, object, graph) { return { type: 'pattern', subject, predicate, object, graph }; } | ||
static createProject(input, variables) { return { type: 'project', input, variables }; } | ||
static createReduced(input) { return { type: 'reduced', input }; } | ||
static createSeq(left, right) { return { type: 'seq', left, right }; } | ||
static createSlice(input, start, length) { | ||
if (length !== undefined) | ||
return { type: 'slice', input, start, length }; | ||
return { type: 'slice', input, start }; | ||
} | ||
static createUnion(left, right) { return { type: 'union', left, right }; } | ||
static createValues(variables, bindings) { return { type: 'values', variables, bindings }; } | ||
static createZeroOrMorePath(path) { return { type: 'ZeroOrMorePath', path }; } | ||
static createZeroOrOnePath(path) { return { type: 'ZeroOrOnePath', path }; } | ||
static createExistenceExpression(not, input) { return { type: 'expression', expressionType: 'existence', not, input }; } | ||
static createNamedExpression(name, args) { return { type: 'expression', expressionType: 'named', name, args }; } | ||
static createOperatorExpression(operator, args) { return { type: 'expression', expressionType: 'operator', operator, args }; } | ||
static createTermExpression(term) { return { type: 'expression', expressionType: 'term', term }; } | ||
} | ||
exports.default = Factory; | ||
//# sourceMappingURL=Factory.js.map |
@@ -1,4 +0,2 @@ | ||
import { Operation } from './algebra'; | ||
export function translate (sparql: any, quads?: boolean) : Operation; | ||
import * as Algebra from './algebra'; | ||
export default function translate(sparql: any, quads?: boolean): Algebra.Operation; |
@@ -1,133 +0,21 @@ | ||
/** | ||
* Created by joachimvh on 26/02/2015. | ||
*/ | ||
const _ = require('lodash'); | ||
const algebra = require('./algebra'); | ||
const N3Util = require('n3').Util; | ||
const SparqlParser = require('sparqljs').Parser; | ||
const Algebra = algebra.types; | ||
const ETypes = algebra.expressionTypes; | ||
// ---------------------------------- END HELPER CLASSES ---------------------------------- | ||
let variables = {}; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const _ = require("lodash"); | ||
const Algebra = require("./algebra"); | ||
const Factory_1 = require("./Factory"); | ||
const n3_1 = require("n3"); | ||
const Parser = require('sparqljs').Parser; | ||
const types = Algebra.types; | ||
const eTypes = Algebra.expressionTypes; | ||
let variables = new Set(); | ||
let varCount = 0; | ||
let useQuads = false; | ||
let defaultGraph = { termType: 'DefaultGraph', value: '' }; | ||
function createAlgebraElement (symbol, args) | ||
{ | ||
return Object.assign({type: symbol}, args); | ||
} | ||
function createExpression (symbol, type, args) | ||
{ | ||
// SPARQL.js has a list as second argument for notin (and in), we don't like lists | ||
// TODO: unfortunately createExpression gets called multiple times hence the array check for now | ||
if ((symbol === 'notin' || symbol === 'in') && _.isArray(args[1])) | ||
args = [args[0]].concat(args[1]); | ||
// have their own type due to being applied to a groupGraphPattern | ||
if (symbol === 'exists' || symbol === 'notexists') | ||
{ | ||
if (args.length > 1) | ||
throw new Error('Expected a single argument for exists/notexists'); | ||
return createAlgebraElement(Algebra.EXPRESSION, { expressionType: ETypes.EXISTENCE, not: (symbol === 'notexists'), input: args[0] }); | ||
} | ||
// map all terms to TermExpressions | ||
args = args ? args.map(arg => | ||
{ | ||
if (_.isString(arg)) | ||
arg = createTerm(arg); | ||
if (arg.termType) | ||
return createAlgebraElement(Algebra.EXPRESSION, { expressionType: ETypes.TERM, term: arg }); | ||
if (!arg.expressionType) | ||
return translateFilters(arg); // TODO: should look into this nesting | ||
return arg; | ||
}) : null; | ||
// TODO: support for known operators? | ||
if (type === ETypes.OPERATOR) | ||
return createAlgebraElement(Algebra.EXPRESSION, { expressionType: ETypes.OPERATOR, operator: symbol, args }); | ||
if (type === ETypes.NAMED) | ||
return createAlgebraElement(Algebra.EXPRESSION, { expressionType: ETypes.NAMED, name: createTerm(symbol), args }); | ||
if (type === ETypes.TERM) | ||
return createAlgebraElement(Algebra.EXPRESSION, { expressionType: ETypes.TERM, term: symbol }); | ||
throw new Error('Invalid type: ' + type); | ||
} | ||
function createAggregate (aggregate, input) | ||
{ | ||
if (_.isArray(input)) | ||
input = input[0]; | ||
return createAlgebraElement(Algebra.AGGREGATE, {aggregate, expression: input}); | ||
} | ||
function createTriple (subject, predicate, object) | ||
{ | ||
// first parameter is a triple object | ||
if (subject.subject && subject.predicate && subject.object) | ||
{ | ||
predicate = subject.predicate; | ||
object = subject.object; | ||
subject = subject.subject; | ||
} | ||
if (predicate.type) | ||
return createAlgebraElement(Algebra.PATH, {subject: createTerm(subject), predicate, object: createTerm(object), graph: defaultGraph}); | ||
return createAlgebraElement(Algebra.PATTERN, {subject: createTerm(subject), predicate: createTerm(predicate), object: createTerm(object), graph: defaultGraph}); | ||
} | ||
function createTerm (str) | ||
{ | ||
if (str.termType) | ||
return str; | ||
if (str[0] === '?') | ||
return { termType: 'Variable', value: str.substring(1) }; | ||
if (str.startsWith('_:')) | ||
return { termType: 'BlankNode', value: str.substring(3) }; | ||
if (N3Util.isLiteral(str)) | ||
{ | ||
let literal = { termType: 'Literal', value: N3Util.getLiteralValue(str) }; | ||
let lang = N3Util.getLiteralLanguage(str); | ||
if (lang && lang.length > 0) | ||
literal.language = lang; | ||
else | ||
{ | ||
let type = N3Util.getLiteralType(str); | ||
if (type && type.length > 0) | ||
literal.datatype = createTerm(type); | ||
} | ||
return literal; | ||
} | ||
return { termType: 'NamedNode', value: str }; | ||
} | ||
// generate an unused variable name | ||
function generateFreshVar () | ||
{ | ||
let v = '?var' + varCount++; | ||
if (variables[v]) | ||
return generateFreshVar(); | ||
variables[v] = true; | ||
return v; | ||
} | ||
// ---------------------------------- TRANSLATE ---------------------------------- | ||
function translate (sparql, quads) | ||
{ | ||
variables = {}; | ||
function translate(sparql, quads) { | ||
variables = new Set(); | ||
varCount = 0; | ||
useQuads = quads; | ||
if (_.isString(sparql)) | ||
{ | ||
let parser = new SparqlParser(); | ||
if (_.isString(sparql)) { | ||
let parser = new Parser(); | ||
// resets the identifier counter used for blank nodes | ||
// provides nicer and more consistent output if there are multiple calls | ||
parser._resetBlanks(); | ||
@@ -138,535 +26,328 @@ sparql = parser.parse(sparql); | ||
throw new Error('Translate only works on complete query objects.'); | ||
// group and where are identical, having only 1 makes parsing easier | ||
let group = { type: 'group', patterns: sparql.where }; | ||
let vars = inScopeVariables(group); | ||
let res = translateGraphPattern(group); | ||
res = simplify(res); | ||
let vars = new Set(Object.keys(inScopeVariables(group)).map(translateTerm)); | ||
let res = translateGroupGraphPattern(group); | ||
res = translateAggregates(sparql, res, vars); | ||
res = toRdfJs(res); | ||
return res; | ||
} | ||
// ---------------------------------- TRANSLATE HELPER FUNCTIONS ---------------------------------- | ||
function mergeObjects (obj1, obj2) | ||
{ | ||
for (let key of Object.keys(obj2)) | ||
obj1[key] = obj2[key]; | ||
exports.default = translate; | ||
function isVariable(str) { | ||
// there is also a '?' operator... | ||
return _.isString(str) && str[0] === '?' && str.length > 1; | ||
} | ||
function isVariable (thingy) | ||
{ | ||
return _.isString(thingy) && thingy[0] === '?'; | ||
} | ||
function inScopeVariables (thingy) | ||
{ | ||
// 18.2.1 | ||
function inScopeVariables(thingy) { | ||
let inScope = {}; | ||
if (isVariable(thingy)) | ||
if (isVariable(thingy)) { | ||
inScope[thingy] = true; | ||
else if (thingy.type === 'bgp') | ||
{ | ||
thingy.triples.forEach(triple => | ||
{ | ||
mergeObjects(inScope, inScopeVariables(triple.subject)); | ||
mergeObjects(inScope, inScopeVariables(triple.predicate)); | ||
mergeObjects(inScope, inScopeVariables(triple.object)); | ||
}); | ||
variables.add(thingy); // keep track of all variables so we don't generate duplicates | ||
} | ||
else if (thingy.type === 'path') | ||
{ | ||
for (let item of thingy.items) | ||
mergeObjects(inScope, inScopeVariables(item)); | ||
} | ||
// group, union, optional | ||
else if (thingy.patterns) | ||
{ | ||
for (let pattern of thingy.patterns) | ||
mergeObjects(inScope, inScopeVariables(pattern)); | ||
// graph | ||
if (thingy.name) | ||
mergeObjects(inScope, inScopeVariables(thingy.name)); | ||
} | ||
// bind, group by | ||
else if (thingy.variable) | ||
{ | ||
mergeObjects(inScope, inScopeVariables(thingy.variable)); | ||
} | ||
else if (thingy.queryType === 'SELECT') | ||
{ | ||
for (let v of thingy.variables) | ||
{ | ||
if (v === '*') | ||
mergeObjects(inScope, inScopeVariables(thingy.where)); | ||
else | ||
mergeObjects(inScope, inScopeVariables(v)); | ||
else if (_.isObject(thingy)) { | ||
if (thingy.type === 'bind') { | ||
inScopeVariables(thingy.expression); // to fill `variables` | ||
Object.assign(inScope, inScopeVariables(thingy.variable)); | ||
} | ||
// TODO: I'm not 100% sure if you always add these or only when '*' was selected | ||
if (thingy.group) | ||
for (let v of thingy.group) | ||
mergeObjects(inScope, inScopeVariables(v)); | ||
else if (thingy.queryType === 'SELECT') { | ||
let all = inScopeVariables(thingy.where); // always executing this makes sure `variables` gets filled correctly | ||
for (let v of thingy.variables) { | ||
if (v === '*') | ||
Object.assign(inScope, all); | ||
else if (v.variable) | ||
Object.assign(inScope, inScopeVariables(v.variable)); | ||
else | ||
Object.assign(inScope, inScopeVariables(v)); | ||
} | ||
// TODO: I'm not 100% sure if you always add these or only when '*' was selected | ||
if (thingy.group) | ||
for (let v of thingy.group) | ||
Object.assign(inScope, inScopeVariables(v)); | ||
} | ||
else | ||
for (let key of Object.keys(thingy)) | ||
Object.assign(inScope, inScopeVariables(thingy[key])); | ||
} | ||
else if (thingy.type === 'values') | ||
{ | ||
for (let value of thingy.values) | ||
for (let v of Object.keys(value)) | ||
inScope[v] = true; | ||
} | ||
return inScope; | ||
} | ||
// ---------------------------------- TRANSLATE GRAPH PATTERN ---------------------------------- | ||
function translateGraphPattern (thingy) | ||
{ | ||
// can be UNDEF in a VALUES block | ||
if (thingy === undefined) | ||
return thingy; | ||
if (_.isString(thingy)) | ||
{ | ||
if (isVariable(thingy)) | ||
variables[thingy] = true; | ||
return thingy; | ||
} | ||
if (_.isArray(thingy)) | ||
return thingy.map(subthingy => translateGraphPattern(subthingy) ); | ||
// make sure optional and minus have group subelement (needs to be done before recursion) | ||
if (thingy.type === 'optional' || thingy.type === 'minus') | ||
thingy = { type: thingy.type, patterns: [{ type: 'group', patterns: thingy.patterns }] }; | ||
// we postpone this for subqueries so the in-scope variable calculation is correct | ||
if (thingy.type !== 'query') | ||
{ | ||
let newthingy = {}; | ||
for (let key of Object.keys(thingy)) | ||
newthingy[key] = translateGraphPattern(thingy[key]); | ||
thingy = newthingy; | ||
} | ||
// from this point on we can change contents of the input parameter since it was changed above (except for subqueries) | ||
// update expressions to algebra format (done here since they are used in both filters and binds) | ||
if (thingy.type === 'operation') | ||
thingy = createExpression(thingy.operator, ETypes.OPERATOR, thingy.args); | ||
function translateGroupGraphPattern(thingy) { | ||
// 18.2.2.1 | ||
// already done by sparql parser | ||
// 18.2.2.2 | ||
let filters = []; | ||
let nonfilters = []; | ||
if (thingy.type === 'filter') | ||
thingy = createAlgebraElement('_filter', { expression: thingy.expression }); // _filter since it doesn't really correspond to the 'filter' from the spec here | ||
else if (thingy.patterns) | ||
{ | ||
if (thingy.patterns) | ||
for (let pattern of thingy.patterns) | ||
{ | ||
if (pattern.type === '_filter') | ||
filters.push(pattern.expression); // we only need the expression, we already know they are filters now | ||
else | ||
nonfilters.push(pattern); | ||
} | ||
thingy.patterns = nonfilters; | ||
} | ||
(pattern.type === 'filter' ? filters : nonfilters).push(pattern); | ||
// 18.2.2.3 | ||
if (thingy.type === 'path') | ||
thingy = translatePathExpression(thingy, thingy.items); | ||
// 18.2.2.4 | ||
// need to do this at BGP level so seq paths can be merged into BGP | ||
// 18.2.2.5 | ||
if (thingy.type === 'bgp') | ||
{ | ||
let newTriples = []; | ||
for (let triple of thingy.triples) | ||
newTriples.push.apply(newTriples, translatePath(triple, triple.predicate)); | ||
thingy.triples = newTriples; | ||
return translateBgp(thingy); | ||
// 18.2.2.6 | ||
let result; | ||
if (thingy.type === 'union') | ||
result = nonfilters.map((p) => { | ||
// algebrajs doesn't always indicate the children are groups | ||
if (p.type !== 'group') | ||
p = { type: 'group', patterns: [p] }; | ||
return translateGroupGraphPattern(p); | ||
}).reduce((acc, item) => Factory_1.default.createUnion(acc, item)); | ||
else if (thingy.type === 'graph') | ||
// need to handle this separately since the filters need to be in the graph | ||
return translateGraph(thingy); | ||
else if (thingy.type === 'group') | ||
result = nonfilters.reduce(accumulateGroupGraphPattern, Factory_1.default.createBgp([])); | ||
else if (thingy.type === 'values') | ||
result = translateInlineData(thingy); | ||
else if (thingy.type === 'query') | ||
result = translate(thingy, useQuads); | ||
else | ||
throw new Error('Unexpected type: ' + thingy.type); | ||
if (filters.length > 0) { | ||
let expressions = filters.map(filter => translateExpression(filter.expression)); | ||
if (expressions.length > 0) | ||
result = Factory_1.default.createFilter(result, expressions.reduce((acc, exp) => Factory_1.default.createOperatorExpression('&&', [acc, exp]))); | ||
} | ||
// 18.2.2.5 | ||
if (thingy.type === 'bgp') | ||
{ | ||
let join = []; | ||
let bgp = []; | ||
for (let triple of thingy.triples) | ||
{ | ||
if (!triple.type) | ||
triple = createTriple(triple); | ||
if (triple.type === Algebra.PATH) | ||
{ | ||
if (bgp.length > 0) | ||
{ | ||
join.push(createAlgebraElement(Algebra.BGP, { patterns: bgp })); | ||
bgp = []; | ||
return result; | ||
} | ||
function translateExpression(exp) { | ||
if (_.isString(exp)) | ||
return Factory_1.default.createTermExpression(translateTerm(exp)); | ||
if (exp.function) | ||
return Factory_1.default.createNamedExpression(translateTerm(exp.function), exp.args.map(translateExpression)); | ||
if (exp.operator) { | ||
if (exp.operator === 'exists' || exp.operator === 'notexists') | ||
return Factory_1.default.createExistenceExpression(exp.operator === 'notexists', translateGroupGraphPattern(exp.args[0])); | ||
if (exp.operator === 'in' || exp.operator === 'notin') | ||
exp.args = [exp.args[0]].concat(exp.args[1]); // sparql.js uses 2 arguments with the second one bing a list | ||
return Factory_1.default.createOperatorExpression(exp.operator, exp.args.map(translateExpression)); | ||
} | ||
throw new Error('Unknown expression: ' + JSON.stringify(exp)); | ||
} | ||
function translateBgp(thingy) { | ||
let patterns = []; | ||
let joins = []; | ||
for (let t of thingy.triples) { | ||
if (t.predicate.type === 'path') { | ||
// translatePath returns a mix of Quads and Paths | ||
let path = translatePath(t); | ||
for (let p of path) { | ||
if (p.type === types.PATH) { | ||
if (patterns.length > 0) | ||
joins.push(Factory_1.default.createBgp(patterns)); | ||
patterns = []; | ||
joins.push(p); | ||
} | ||
join.push(triple); | ||
else | ||
patterns.push(p); | ||
} | ||
else | ||
bgp.push(triple); | ||
} | ||
if (bgp.length > 0) | ||
join.push(createAlgebraElement(Algebra.BGP, { patterns: bgp })); | ||
if (join.length === 1) | ||
thingy = join[0]; | ||
else | ||
thingy = join.reduce((acc, item) => createAlgebraElement(Algebra.JOIN, { left: acc, right: item })); | ||
patterns.push(translateTriple(t)); | ||
} | ||
// 18.2.2.6 | ||
if (thingy.type === 'union') | ||
thingy = translateGroupOrUnionGraphPattern(thingy); | ||
if (thingy.type === 'graph') | ||
{ | ||
// need to handle this separately here since the filters need to be in the graph | ||
thingy = translateGraphGraphPattern(thingy, filters); | ||
filters = []; | ||
} | ||
if (thingy.type === 'group') | ||
thingy = translateGroupGraphPattern(thingy); | ||
// inlineData based on Jena implementation | ||
if (thingy.type === 'values') | ||
thingy = translateInlineData(thingy); | ||
if (thingy.type === 'query') | ||
thingy = translateSubSelect(thingy); | ||
// 18.2.2.7 | ||
if (filters.length > 0) | ||
thingy = createAlgebraElement(Algebra.FILTER, { expression: translateFilters(filters), input: thingy }); | ||
return thingy; | ||
if (patterns.length > 0) | ||
joins.push(Factory_1.default.createBgp(patterns)); | ||
if (joins.length === 1) | ||
return joins[0]; | ||
return joins.reduce((acc, item) => Factory_1.default.createJoin(acc, item)); | ||
} | ||
// 18.2.2.8 | ||
function simplify (thingy) | ||
{ | ||
if (_.isString(thingy) || _.isBoolean(thingy) || _.isInteger(thingy) || thingy.termType) | ||
return thingy; | ||
if (_.isArray(thingy)) | ||
return thingy.map(subthingy => simplify(subthingy)); | ||
if (!thingy.type && !thingy.operator) | ||
throw new Error("Expected translated input."); | ||
if (thingy.type === Algebra.BGP) | ||
return thingy; | ||
if (thingy.type === Algebra.JOIN) | ||
{ | ||
if (thingy.left.type === Algebra.BGP && thingy.left.patterns.length === 0) | ||
return thingy.right; | ||
else if (thingy.right.type === Algebra.BGP && thingy.right.patterns.length === 0) | ||
return thingy.left; | ||
} | ||
for (let key of Object.keys(thingy)) | ||
thingy[key] = simplify(thingy[key]); | ||
return thingy; | ||
function translatePath(triple) { | ||
let sub = translateTerm(triple.subject); | ||
let pred = translatePathPredicate(triple.predicate); | ||
let obj = translateTerm(triple.object); | ||
return simplifyPath(sub, pred, obj); | ||
} | ||
// ---------------------------------- TRANSLATE GRAPH PATTERN HELPER FUNCTIONS ---------------------------------- | ||
function translatePathExpression (pathExp, translatedItems) | ||
{ | ||
let res = null; | ||
let items = translatedItems.map(item => _.isString(item) ? createAlgebraElement(Algebra.LINK, { iri: item }) : item); | ||
if (pathExp.pathType === '^') | ||
res = createAlgebraElement(Algebra.INV, { path: items[0] }); | ||
else if (pathExp.pathType === '!') | ||
{ | ||
function translatePathPredicate(predicate) { | ||
if (_.isString(predicate)) | ||
return Factory_1.default.createLink(translateTerm(predicate)); | ||
if (predicate.pathType === '^') | ||
return Factory_1.default.createInv(translatePathPredicate(predicate.items[0])); | ||
if (predicate.pathType === '!') { | ||
let normals = []; | ||
let inverted = []; | ||
if (items.length !== 1) | ||
throw new Error("Expected exactly 1 item for '!' path operator."); | ||
let item = items[0]; | ||
if (item.type === Algebra.LINK) | ||
normals.push(item); | ||
else if (item.type === Algebra.INV) | ||
{ | ||
if (item.items.length !== 1) | ||
throw new Error("Expected exactly 1 item for '^' path operator."); | ||
inverted.push(item.items[0]); | ||
let items = predicate.items[0].items; // the | element | ||
for (let item of items) { | ||
if (_.isString(item)) | ||
normals.push(item); | ||
else if (item.pathType === '^') | ||
inverted.push(item.items[0]); | ||
else | ||
throw new Error('Unexpected item: ' + item); | ||
} | ||
else if (item.type === Algebra.ALT) | ||
{ | ||
for (let option of [item.left, item.right]) | ||
{ | ||
if (option.type === Algebra.INV) | ||
inverted.push(option.path); | ||
else | ||
normals.push(option); | ||
} | ||
} | ||
// NPS elements do not have the LINK function | ||
let normalElement = createAlgebraElement(Algebra.NPS, { iris: normals.map(link => link.iri) }); | ||
let invertedElement = createAlgebraElement(Algebra.INV, { path: createAlgebraElement(Algebra.NPS, { iris: inverted.map(link => link.iri) }) }); | ||
let normalElement = Factory_1.default.createNps(normals.map(translateTerm)); | ||
let invertedElement = Factory_1.default.createInv(Factory_1.default.createNps(inverted.map(translateTerm))); | ||
if (inverted.length === 0) | ||
res = normalElement; | ||
else if (normals.length === 0) | ||
res = invertedElement; | ||
else | ||
res = createAlgebraElement(Algebra.ALT, { left: normalElement, right: invertedElement }); | ||
return normalElement; | ||
if (normals.length === 0) | ||
return invertedElement; | ||
return Factory_1.default.createAlt(normalElement, invertedElement); | ||
} | ||
else if (pathExp.pathType === '/') | ||
{ | ||
if (items.length < 2) | ||
throw new Error('Expected at least 2 items for '/' path operator.'); | ||
res = items.reduce((acc, v) => createAlgebraElement(Algebra.SEQ, { left: acc, right: v })); | ||
} | ||
else if (pathExp.pathType === '|') | ||
res = items.reduce((acc, v) => createAlgebraElement(Algebra.ALT, { left: acc, right: v })); | ||
else if (pathExp.pathType === '*') | ||
res = createAlgebraElement(Algebra.ZERO_OR_MORE_PATH, { path: items[0] }); | ||
else if (pathExp.pathType === '+') | ||
res = createAlgebraElement(Algebra.ONE_OR_MORE_PATH, { path: items[0] }); | ||
else if (pathExp.pathType === '?') | ||
res = createAlgebraElement(Algebra.ZERO_OR_ONE_PATH, { path: items[0] }); | ||
if (!res) | ||
throw new Error('Unable to translate path expression ' + pathExp); | ||
return res; | ||
if (predicate.pathType === '/') | ||
return predicate.items.map(translatePathPredicate).reduce((acc, p) => Factory_1.default.createSeq(acc, p)); | ||
if (predicate.pathType === '|') | ||
return predicate.items.map(translatePathPredicate).reduce((acc, p) => Factory_1.default.createAlt(acc, p)); | ||
if (predicate.pathType === '*') | ||
return Factory_1.default.createZeroOrMorePath(translatePathPredicate(predicate.items[0])); | ||
if (predicate.pathType === '+') | ||
return Factory_1.default.createOneOrMorePath(translatePathPredicate(predicate.items[0])); | ||
if (predicate.pathType === '?') | ||
return Factory_1.default.createZeroOrOnePath(translatePathPredicate(predicate.items[0])); | ||
throw new Error('Unable to translate path expression ' + predicate); | ||
} | ||
function translatePath (pathTriple, translatedPredicate) | ||
{ | ||
// assume path expressions have already been updated | ||
if (!translatedPredicate.type) | ||
return [ pathTriple ]; | ||
let pred = translatedPredicate; | ||
let res = null; | ||
if (pred.type === Algebra.LINK) | ||
res = createTriple(pathTriple.subject, pred.iri, pathTriple.object); | ||
else if (pred.type === Algebra.INV) // TODO: I think this applies to inv(path) instead of inv(iri) like the spec says, I might be wrong | ||
{ | ||
res = translatePath(createTriple(pathTriple.object, pathTriple.predicate, pathTriple.subject), pred.path); | ||
} | ||
else if (pred.type === Algebra.SEQ) | ||
{ | ||
function simplifyPath(subject, predicate, object) { | ||
if (predicate.type === types.LINK) | ||
return [Factory_1.default.createPattern(subject, predicate.iri, object, defaultGraph)]; | ||
if (predicate.type === types.INV) | ||
return simplifyPath(object, predicate.path, subject); | ||
if (predicate.type === types.SEQ) { | ||
let v = generateFreshVar(); | ||
let triple1 = createTriple(pathTriple.subject, pred.left, v); | ||
let triple2 = createTriple(v, pred.right, pathTriple.object); | ||
res = translatePath(triple1, triple1.predicate).concat(translatePath(triple2, triple2.predicate)); | ||
let left = simplifyPath(subject, predicate.left, v); | ||
let right = simplifyPath(v, predicate.right, object); | ||
return left.concat(right); | ||
} | ||
else | ||
res = createTriple(pathTriple.subject, pathTriple.predicate, pathTriple.object); | ||
if (!_.isArray(res)) | ||
res = [res]; | ||
return res; | ||
return [Factory_1.default.createPath(subject, predicate, object, defaultGraph)]; | ||
} | ||
function translateGroupOrUnionGraphPattern (group) | ||
{ | ||
if (group.patterns.length < 1) | ||
throw new Error('Expected at least one item in GroupOrUnionGraphPattern.'); | ||
return group.patterns.reduce((accumulator, pattern) => | ||
{ | ||
if (pattern.type !== 'group' && pattern.type !== 'union') | ||
pattern = { type: 'group', patterns: [ pattern ]}; | ||
pattern = translateGroupGraphPattern(pattern); | ||
if (!accumulator) | ||
return pattern; | ||
return createAlgebraElement(Algebra.UNION, { left: accumulator, right: pattern }); | ||
}, null); | ||
function generateFreshVar() { | ||
let v = '?var' + varCount++; | ||
if (variables.has(v)) | ||
return generateFreshVar(); | ||
variables.add(v); | ||
return translateTerm(v); | ||
} | ||
function translateGraphGraphPattern (group, filters) | ||
{ | ||
if (group.patterns.length !== 1) | ||
throw new Error('Expected exactly 1 item for GraphGraphPattern.'); | ||
let name = group.name; | ||
let graph = createAlgebraElement(Algebra.GRAPH, { graph: name, input: group.patterns[0] }); | ||
if (useQuads) | ||
graph = applyGraph(graph.input, graph.graph); | ||
if (filters.length > 0) | ||
{ | ||
let filterInput = translateFilters(filters); | ||
// filters can contain BGPs! | ||
if (useQuads) | ||
graph = createAlgebraElement(Algebra.FILTER, { expression: applyGraph(filterInput, name), input: graph}); | ||
else | ||
graph.input = createAlgebraElement(Algebra.FILTER, { expression: filterInput, input: graph.input }); | ||
const defaultGraph = { termType: 'DefaultGraph', value: '' }; | ||
function translateTriple(triple) { | ||
return Factory_1.default.createPattern(translateTerm(triple.subject), translateTerm(triple.predicate), translateTerm(triple.object), defaultGraph); | ||
} | ||
const stringType = translateTerm('http://www.w3.org/2001/XMLSchema#string'); | ||
const langStringType = translateTerm('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString'); | ||
function translateTerm(str) { | ||
if (str[0] === '?') | ||
return { termType: 'Variable', value: str.substring(1) }; | ||
if (_.startsWith(str, '_:')) | ||
return { termType: 'BlankNode', value: str.substring(2) }; | ||
if (n3_1.Util.isLiteral(str)) { | ||
let literal = { termType: 'Literal', value: n3_1.Util.getLiteralValue(str), language: '', datatype: stringType }; | ||
let lang = n3_1.Util.getLiteralLanguage(str); | ||
if (lang && lang.length > 0) { | ||
literal.language = lang; | ||
literal.datatype = langStringType; | ||
} | ||
else { | ||
let type = n3_1.Util.getLiteralType(str); | ||
if (type && type.length > 0) | ||
literal.datatype = translateTerm(type); | ||
} | ||
return literal; | ||
} | ||
return graph; | ||
return { termType: 'NamedNode', value: str }; | ||
} | ||
function applyGraph (thingy, graph) | ||
{ | ||
if (_.isArray(thingy)) | ||
return thingy.map(subThingy => applyGraph(subThingy, graph)); | ||
if (thingy.type) | ||
{ | ||
if (thingy.type === Algebra.PATTERN) | ||
return Object.assign(thingy, { graph }); | ||
if (thingy.type === Algebra.PATH) | ||
return Object.assign(thingy, { graph }); | ||
for (let key of Object.keys(thingy)) | ||
thingy[key] = applyGraph(thingy[key], graph); | ||
return thingy; | ||
function translateGraph(graph) { | ||
let name = translateTerm(graph.name); | ||
graph.type = 'group'; | ||
let result = translateGroupGraphPattern(graph); | ||
if (useQuads) | ||
result = recurseGraph(result, name); | ||
else | ||
result = Factory_1.default.createGraph(result, name); | ||
return result; | ||
} | ||
let typeVals = Object.keys(types).map(key => types[key]); | ||
function recurseGraph(thingy, graph) { | ||
if (thingy.type === types.BGP) | ||
thingy.patterns = thingy.patterns.map(quad => { | ||
quad.graph = graph; | ||
return quad; | ||
}); | ||
else if (thingy.type === types.PATH) | ||
thingy.graph = graph; | ||
else { | ||
for (let key of Object.keys(thingy)) { | ||
if (_.isArray(thingy[key])) | ||
thingy[key] = thingy[key].map((x) => recurseGraph(x, graph)); | ||
else if (typeVals.indexOf(thingy[key].type) >= 0) | ||
thingy[key] = recurseGraph(thingy[key], graph); | ||
} | ||
} | ||
return thingy; | ||
} | ||
function accumulateGroupGraphPattern (G, E) | ||
{ | ||
// TODO: some of the patterns aren't translated yet at this point, might be code smell | ||
// this can happen if one of the elements was a subgroup, so shouldn't throw an error | ||
// TODO: or find cleaner way to handle this | ||
// if (E.symbol === Algebra.FILTER) | ||
// throw new Error('Filters should have been removed previously.'); | ||
if (E.type === 'optional') | ||
{ | ||
if (E.patterns.length !== 1) | ||
throw new Error("Expected exactly 1 arg for 'optional'."); | ||
let A = E.patterns[0]; | ||
if (A.type === Algebra.FILTER) | ||
G = createAlgebraElement(Algebra.LEFT_JOIN, { left: G, right: A.input, expression: A.expression }); | ||
function accumulateGroupGraphPattern(G, E) { | ||
if (E.type === 'optional') { | ||
// optional input needs to be interpreted as a group | ||
let A = translateGroupGraphPattern({ type: 'group', patterns: E.patterns }); | ||
if (A.type === types.FILTER) { | ||
let filter = A; | ||
G = Factory_1.default.createLeftJoin(G, filter.input, filter.expression); | ||
} | ||
else | ||
G = createAlgebraElement(Algebra.LEFT_JOIN, { left: G, right: A }); | ||
G = Factory_1.default.createLeftJoin(G, A); | ||
} | ||
else if (E.type === Algebra.MINUS) | ||
{ | ||
if (E.patterns.length !== 1) | ||
throw new Error('MINUS element should only have 1 arg at this point.'); | ||
G = createAlgebraElement(Algebra.MINUS, { left: G, right: E.patterns[0] }); | ||
else if (E.type === 'minus') { | ||
// minus input needs to be interpreted as a group | ||
let A = translateGroupGraphPattern({ type: 'group', patterns: E.patterns }); | ||
G = Factory_1.default.createMinus(G, A); | ||
} | ||
else if (E.type === 'bind') | ||
G = createAlgebraElement(Algebra.EXTEND, { input: G, variable: E.variable, expression: translateFilters(E.expression) }); | ||
else | ||
G = createAlgebraElement(Algebra.JOIN, { left: G, right: E }); | ||
G = Factory_1.default.createExtend(G, translateTerm(E.variable), translateExpression(E.expression)); | ||
else { | ||
// 18.2.2.8 (simplification) | ||
let A = translateGroupGraphPattern(E); | ||
if (G.type === types.BGP && G.patterns.length === 0) | ||
G = A; | ||
else if (A.type === types.BGP && A.patterns.length === 0) { } // do nothing | ||
else | ||
G = Factory_1.default.createJoin(G, translateGroupGraphPattern(E)); | ||
} | ||
return G; | ||
} | ||
function translateGroupGraphPattern (group) | ||
{ | ||
return group.patterns.reduce(accumulateGroupGraphPattern, createAlgebraElement(Algebra.BGP, { patterns: [] })); | ||
} | ||
function translateInlineData (thingy) | ||
{ | ||
// let values = thingy.values.map(entry => | ||
// { | ||
// let vals = []; | ||
// for (let key of Object.keys(entry)) | ||
// if (entry[key] !== undefined) | ||
// vals.push([key, entry[key]]); | ||
// return createAlgebraElement(Algebra.ROW, vals); | ||
// }); | ||
// // parser doesn't keep track of variables on first line | ||
let variables = thingy.values.length === 0 ? [] : Object.keys(thingy.values[0]); | ||
// vars = createAlgebraElement(Algebra.VARS, vars); | ||
// return createAlgebraElement(Algebra.TABLE, [vars].concat(values)); | ||
return createAlgebraElement(Algebra.VALUES, { variables, bindings: thingy.values.map(binding => _.omitBy(binding, v => v === undefined)) }); | ||
} | ||
function translateSubSelect (query) | ||
{ | ||
return translate(query, useQuads); | ||
} | ||
function translateFilters (filterExpressions) | ||
{ | ||
if (!_.isArray(filterExpressions)) | ||
filterExpressions = [ filterExpressions ]; | ||
filterExpressions = filterExpressions.map(exp => | ||
{ | ||
// TODO: investigate when this happens | ||
if (exp.expressionType) | ||
return exp; | ||
if (_.isString(exp)) | ||
return createExpression(exp, ETypes.TERM); | ||
if (exp.function) | ||
return createExpression(exp.function, ETypes.NAMED, exp.args); | ||
if (exp.operator) | ||
return createExpression(exp.operator, ETypes.OPERATOR, exp.args); | ||
function translateInlineData(values) { | ||
let variables = (values.values.length === 0 ? [] : Object.keys(values.values[0])).map(translateTerm); | ||
let bindings = values.values.map((binding) => { | ||
let keys = Object.keys(binding); | ||
keys = keys.filter(k => binding[k] !== undefined); | ||
let map = new Map(); | ||
for (let key of keys) | ||
map.set(translateTerm(key), translateTerm(binding[key])); | ||
return map; | ||
}); | ||
return filterExpressions.reduce((acc, val) => createExpression('&&', ETypes.OPERATOR, [acc, val])); | ||
return Factory_1.default.createValues(variables, bindings); | ||
} | ||
// ---------------------------------- TRANSLATE AGGREGATES ---------------------------------- | ||
function translateAggregates (query, parsed, variables) | ||
{ | ||
let res = parsed; | ||
// --------------------------------------- AGGREGATES | ||
function translateAggregates(query, res, variables) { | ||
// 18.2.4.1 | ||
let G = null; | ||
let A = []; | ||
let E = []; | ||
if (query.group) | ||
G = createAlgebraElement(Algebra.GROUP, { expressions: query.group, input: res }); | ||
else if (containsAggregate(query.variables) || containsAggregate(query.having) || containsAggregate(query.order)) | ||
G = createAlgebraElement(Algebra.GROUP, { expressions: [], input: res }); | ||
// TODO: not doing the sample stuff atm | ||
// TODO: more based on jena results than w3 spec (spec would require us to copy G multiple times) | ||
if (G) | ||
{ | ||
// TODO: check up on scalarvals and args | ||
mapAggregates(query.variables, A); | ||
mapAggregates(query.having, A); | ||
mapAggregates(query.order, A); | ||
// TODO: we use the jena syntax of adding aggregates as second argument to group | ||
// .var will be translated later on | ||
G.aggregates = A.map(aggregate => { return Object.assign({var: aggregate.variable}, aggregate.object) }); | ||
if (query.group) | ||
{ | ||
let A = {}; | ||
query.variables = mapAggregates(query.variables, A); | ||
query.having = mapAggregates(query.having, A); | ||
query.order = mapAggregates(query.order, A); | ||
// if there are any aggregates or if we have a groupBy (both result in a GROUP) | ||
if (query.group || Object.keys(A).length > 0) { | ||
let aggregates = Object.keys(A).map(v => translateBoundAggregate(A[v], translateTerm(v))); | ||
let exps = []; | ||
if (query.group) { | ||
for (let entry of query.group) | ||
if (entry.variable) | ||
E.push(entry); | ||
exps = query.group.map((e) => e.expression).map(translateExpression); | ||
} | ||
res = G; | ||
res = Factory_1.default.createGroup(res, exps, aggregates); | ||
} | ||
// 18.2.4.2 | ||
if (query.having) | ||
{ | ||
for (let filter of query.having) | ||
res = createAlgebraElement(Algebra.FILTER, { expression: translateFilters(filter), input: res }); | ||
} | ||
res = Factory_1.default.createFilter(res, translateExpression(filter)); | ||
// 18.2.4.3 | ||
if (query.values) | ||
res = createAlgebraElement(Algebra.JOIN, { left: res, right: translateInlineData(query) }); | ||
res = Factory_1.default.createJoin(res, translateInlineData(query)); | ||
// 18.2.4.4 | ||
let PV = {}; | ||
// interpret ASK as SELECT * | ||
if (query.queryType === 'ASK' || query.variables.indexOf('*') >= 0) | ||
let PV = new Set(); | ||
// interpret other query types as SELECT * | ||
if (query.queryType !== 'SELECT' || query.variables.indexOf('*') >= 0) | ||
PV = variables; | ||
else | ||
{ | ||
for (let v of query.variables) | ||
{ | ||
else { | ||
for (let v of query.variables) { | ||
if (isVariable(v)) | ||
PV[v] = true; | ||
else if (v.variable) | ||
{ | ||
if (variables[v.variable] || PV[v.variable]) | ||
throw new Error('Aggregate variable appearing multiple times: ' + v.variable); | ||
PV[v.variable] = true; | ||
PV.add(translateTerm(v)); | ||
else if (v.variable) { | ||
PV.add(translateTerm(v.variable)); | ||
E.push(v); | ||
@@ -676,170 +357,72 @@ } | ||
} | ||
// TODO: Jena simplifies by having a list of extends | ||
for (let v of E) | ||
res = createAlgebraElement(Algebra.EXTEND, { input: res, variable: v.variable, expression: translateFilters(v.expression) }); | ||
res = Factory_1.default.createExtend(res, translateTerm(v.variable), translateExpression(v.expression)); | ||
// 18.2.5 | ||
//p = createAlgebraElement('tolist', [p]); | ||
// not using toList and toMultiset | ||
// 18.2.5.1 | ||
if (query.order) | ||
res = createAlgebraElement(Algebra.ORDER_BY, { input: res, expressions: query.order }); | ||
res = Factory_1.default.createOrderBy(res, query.order.map((exp) => { | ||
let result = translateExpression(exp.expression); | ||
if (exp.descending) | ||
result = Factory_1.default.createOperatorExpression(types.DESC, [result]); // TODO: should this really be an epxression? | ||
return result; | ||
})); | ||
// 18.2.5.2 | ||
res = createAlgebraElement(Algebra.PROJECT, { input: res, variables: Object.keys(PV) }); | ||
res = Factory_1.default.createProject(res, Array.from(PV)); | ||
// 18.2.5.3 | ||
if (query.distinct) | ||
res = createAlgebraElement(Algebra.DISTINCT, { input: res }); | ||
res = Factory_1.default.createDistinct(res); | ||
// 18.2.5.4 | ||
if (query.reduced) | ||
res = createAlgebraElement(Algebra.REDUCED, { input: res }); | ||
res = Factory_1.default.createReduced(res); | ||
// NEW: support for construct queries | ||
// limits are also applied to construct results, which is why those come last, although results should be the same | ||
if (query.queryType === 'CONSTRUCT') | ||
res = Factory_1.default.createConstruct(res, query.template.map(translateTriple)); | ||
// 18.2.5.5 | ||
// we use -1 to indiciate there is no limit | ||
if (query.offset || query.limit) | ||
{ | ||
res = createAlgebraElement(Algebra.SLICE, { input: res, start: query.offset || 0 }); | ||
if (query.offset || query.limit) { | ||
res = Factory_1.default.createSlice(res, query.offset || 0); | ||
if (query.limit) | ||
res.length = query.limit; | ||
} | ||
// clean up unchanged objects | ||
res = translateExpressionsOperations(res); | ||
return res; | ||
} | ||
// ---------------------------------- TRANSLATE AGGREGATES HELPER FUNCTIONS ---------------------------------- | ||
function containsAggregate (thingy) | ||
{ | ||
// rewrites some of the input sparql object to make use of aggregate variables | ||
function mapAggregates(thingy, aggregates) { | ||
if (!thingy) | ||
return false; | ||
if (thingy.expression && thingy.expression.type === 'aggregate') | ||
return true; | ||
if (_.isArray(thingy)) | ||
return thingy.some(subthingy => containsAggregate(subthingy)); | ||
// appears in 'having' | ||
if (thingy.args) | ||
return containsAggregate(thingy.args); | ||
return false; | ||
} | ||
function compareObjects (o1, o2) | ||
{ | ||
if (_.isString(o1) !== _.isString(o2)) | ||
return false; | ||
if (_.isString(o1)) | ||
return o1 === o2; | ||
let k1 = Object.keys(o1); | ||
let k2 = Object.keys(o2); | ||
if (k1.length !== k2.length) | ||
return false; | ||
return k1.every(key => compareObjects(o1[key], o2[key])); | ||
} | ||
// TODO: could use hash, prolly not that necessary | ||
function getMapping(thingy, map) | ||
{ | ||
for (let m of map) | ||
{ | ||
if (compareObjects(thingy, m.object)) | ||
return m.variable; | ||
} | ||
return null; | ||
} | ||
function mapAggregates (thingy, map) | ||
{ | ||
if (!thingy) | ||
return thingy; | ||
if (thingy.type === 'aggregate') | ||
{ | ||
let v = getMapping(thingy, map); | ||
if (!v) | ||
{ | ||
v = generateFreshVar(); | ||
map.push({ object: thingy, variable: v }); | ||
if (thingy.type === 'aggregate') { | ||
let found = false; | ||
let v; | ||
for (let key of Object.keys(aggregates)) { | ||
if (_.isEqual(aggregates[key], thingy)) { | ||
v = key; | ||
found = true; | ||
break; | ||
} | ||
} | ||
return v; | ||
if (!found) { | ||
v = '?' + generateFreshVar().value; // this is still in "sparql.js language" so a var string is still needed | ||
aggregates[v] = thingy; | ||
} | ||
return v; // this is still in "sparql.js language" so a var string is still needed | ||
} | ||
// non-aggregate expression | ||
if (thingy.expression) | ||
thingy.expression = mapAggregates(thingy.expression, map); | ||
thingy.expression = mapAggregates(thingy.expression, aggregates); | ||
else if (thingy.args) | ||
mapAggregates(thingy.args, map); | ||
mapAggregates(thingy.args, aggregates); | ||
else if (_.isArray(thingy)) | ||
thingy.forEach((subthingy, idx) => thingy[idx] = mapAggregates(subthingy, map)); | ||
thingy.forEach((subthingy, idx) => thingy[idx] = mapAggregates(subthingy, aggregates)); | ||
return thingy; | ||
} | ||
function translateExpressionsOperations(thingy) | ||
{ | ||
if (!thingy) | ||
return thingy; | ||
if (_.isString(thingy)) | ||
return thingy; | ||
if (thingy.type === 'aggregate' && thingy.aggregation) | ||
{ | ||
let A = createAggregate(thingy.aggregation, [ translateFilters(thingy.expression) ]); | ||
// TODO: put this in object somewhere | ||
// this is specifically for group_concat | ||
if (thingy.separator && thingy.separator !== ' ') | ||
A.separator = thingy.separator; | ||
// bound aggregates | ||
if (thingy.var) | ||
A.variable = thingy.var; | ||
if (thingy.distinct) | ||
A = createAlgebraElement(Algebra.DISTINCT, { input: A }); | ||
return A; | ||
} | ||
if (thingy.descending && !thingy.type) | ||
return createExpression(Algebra.DESC, ETypes.OPERATOR, [ translateFilters(thingy.expression) ]); | ||
if (thingy.expression && !thingy.type) | ||
return translateFilters(thingy.expression); | ||
if (_.isArray(thingy)) | ||
return thingy.map(subthingy => translateExpressionsOperations(subthingy)); | ||
for (let v of Object.keys(thingy)) | ||
thingy[v] = translateExpressionsOperations(thingy[v]); | ||
return thingy; | ||
function translateBoundAggregate(thingy, v) { | ||
if (thingy.type !== 'aggregate' || !thingy.aggregation) | ||
throw new Error('Unexpected input: ' + JSON.stringify(thingy)); | ||
let A = Factory_1.default.createBoundAggregate(v, thingy.aggregation, translateExpression(thingy.expression)); | ||
if (thingy.separator) | ||
A.separator = thingy.separator; | ||
return A; | ||
} | ||
// ---------------------------------- ADD RDF.JS ---------------------------------- | ||
function toRdfJs(thingy) | ||
{ | ||
if (_.isString(thingy)) | ||
return createTerm(thingy); | ||
if (_.isArray(thingy)) | ||
return thingy.map(toRdfJs); | ||
// caused by graphs already being present | ||
if (thingy.termType) | ||
return thingy; | ||
for (let key of Object.keys(thingy)) | ||
{ | ||
if (key === 'type' || key === 'aggregate' || key === 'operator' || key === 'expressionType' || key === 'separator') | ||
continue; | ||
thingy[key] = toRdfJs(thingy[key]); | ||
} | ||
return thingy; | ||
} | ||
// ---------------------------------- END RDF.JS ---------------------------------- | ||
module.exports = { translate }; | ||
//# sourceMappingURL=sparqlAlgebra.js.map |
{ | ||
"name": "sparqlalgebrajs", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"description": "Convert SPARQL to SPARL algebra", | ||
@@ -20,3 +20,5 @@ "author": "Joachim Van Herwegen", | ||
"@types/lodash": "^4.14.77", | ||
"@types/node": "^8.0.34", | ||
"@types/minimist": "^1.2.0", | ||
"@types/n3": "0.0.3", | ||
"@types/node": "^8.0.50", | ||
"@types/rdf-js": "^1.0.1", | ||
@@ -23,0 +25,0 @@ "mocha": "^3.5.3", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35944
8
782
1