Skip to content

Commit 2a63492

Browse files
committed
working tests
1 parent 00019b5 commit 2a63492

4 files changed

Lines changed: 115 additions & 95 deletions

File tree

packages/algebra-transformer-1-1/lib/sparqlAlgebra.ts

Lines changed: 81 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import Factory from './factory';
5959
import Util from './util';
6060

6161
const types = Algebra.Types;
62-
type mapAggregateType = Wildcard | Expression | Ordering | PatternBind;
62+
type MapAggregateType = Wildcard | Expression | Ordering | PatternBind;
6363
// Type TempDelIns = (PatternBgp | GraphQuads)[];
6464

6565
export type TransformedNamed<T extends object> = AlterNodeOutput<T, SubTyped<'term', 'namedNode'>, RDF.Term>;
@@ -188,35 +188,8 @@ class QueryTranslator {
188188

189189
// 18.2.1
190190
private inScopeVariables(thingy: SparqlQuery | TripleNesting | TripleCollection | Path | Term): Set<string> {
191-
const F = this.astFactory;
192191
const vars = new Set<string>();
193-
if (F.isQuery(thingy) || F.isUpdate(thingy)) {
194-
if (F.isQuerySelect(thingy)) {
195-
if (thingy.where && thingy.variables.some(Util.isWildcard)) {
196-
findPatternBoundedVars(thingy.where, vars);
197-
} else {
198-
for (const v of thingy.variables) {
199-
findPatternBoundedVars(v, vars);
200-
}
201-
}
202-
if (thingy.solutionModifiers.group) {
203-
const grouping = thingy.solutionModifiers.group;
204-
for (const g of grouping.groupings) {
205-
if ('variable' in g) {
206-
findPatternBoundedVars(g.variable, vars);
207-
}
208-
}
209-
}
210-
if (thingy.values?.values && thingy.values.values.length > 0) {
211-
const values = thingy.values.values;
212-
for (const v of Object.keys(values[0])) {
213-
vars.add(v);
214-
}
215-
}
216-
}
217-
} else {
218-
findPatternBoundedVars(thingy, vars);
219-
}
192+
findPatternBoundedVars(thingy, vars);
220193
return vars;
221194

222195
// Const inScope: Record<string, RDF.Variable> = {};
@@ -428,6 +401,10 @@ class QueryTranslator {
428401
if (F.isTermVariable(term)) {
429402
return <AstToRdfTerm<T>> dataFact.variable(term.value);
430403
}
404+
if (F.isTermLiteral(term)) {
405+
const langOrIri = typeof term.langOrIri === 'object' ? this.translateTerm(term.langOrIri) : term.langOrIri;
406+
return <AstToRdfTerm<T>> dataFact.literal(term.value, langOrIri);
407+
}
431408
throw new Error(`Unexpected term: ${JSON.stringify(term)}`);
432409
}
433410

@@ -467,6 +444,9 @@ class QueryTranslator {
467444
});
468445
}
469446

447+
/**
448+
* When flattening, nested subject triples first, followed by nested object triples, lastly the current tripple.
449+
*/
470450
private translateBasicGraphPattern(triples: BasicGraphPattern, result: FlattenedTriple[]): void {
471451
const F = this.astFactory;
472452
for (const triple of triples) {
@@ -535,7 +515,10 @@ class QueryTranslator {
535515
/**
536516
* 18.2.2.3 Translate Property Path Expressions
537517
*/
538-
private translatePathPredicate(predicate: RDF.NamedNode | PathPure): Algebra.PropertyPathSymbol {
518+
private translatePathPredicate(predicate: RDF.NamedNode | Path): Algebra.PropertyPathSymbol {
519+
if (this.astFactory.isTerm(predicate)) {
520+
return this.translatePathPredicate(this.translateTerm(predicate));
521+
}
539522
// Iri -> link(iri)
540523
if (this.isTerm(predicate)) {
541524
return this.factory.createLink(predicate);
@@ -548,30 +531,30 @@ class QueryTranslator {
548531

549532
if (predicate.subType === '!') {
550533
// Negation is either over a single predicate or a list of disjuncted properties - that can only have modifier '^'
551-
const normals: RDF.NamedNode[] = [];
552-
const inverted: RDF.NamedNode[] = [];
534+
const normals: TermIri[] = [];
535+
const inverted: TermIri[] = [];
553536
// Either the item of this one is an `|`, `^` or `iri`
554537
const contained = predicate.items[0];
555-
let items: (RDF.NamedNode | PathNegatedElt)[];
538+
let items: (TermIri | PathNegatedElt)[];
556539
if (this.astFactory.isPathPure(contained) && contained.subType === '|') {
557-
items = <(RDF.NamedNode | PathNegatedElt)[]> contained.items;
540+
items = <(TermIri | PathNegatedElt)[]> contained.items;
558541
} else {
559-
items = [ <RDF.NamedNode | PathNegatedElt> contained ];
542+
items = [ contained ];
560543
}
561544

562545
for (const item of items) {
563-
if (this.isTerm(item)) {
546+
if (this.astFactory.isTerm(item)) {
564547
normals.push(item);
565548
} else if (item.subType === '^') {
566-
inverted.push(<RDF.NamedNode> <unknown> item.items[0]);
549+
inverted.push(item.items[0]);
567550
} else {
568551
throw new Error(`Unexpected item: ${JSON.stringify(item)}`);
569552
}
570553
}
571554

572555
// NPS elements do not have the LINK function
573-
const normalElement = this.factory.createNps(normals);
574-
const invertedElement = this.factory.createInv(this.factory.createNps(inverted));
556+
const normalElement = this.factory.createNps(normals.map(x => this.translateTerm(x)));
557+
const invertedElement = this.factory.createInv(this.factory.createNps(inverted.map(x => this.translateTerm(x))));
575558

576559
// !(:iri1|...|:irin) -> NPS({:iri1 ... :irin})
577560
if (inverted.length === 0) {
@@ -824,21 +807,15 @@ class QueryTranslator {
824807
const bindPatterns: PatternBind[] = [];
825808

826809
const varAggrMap: Record<string, ExpressionAggregate> = {};
827-
if (F.isQuerySelect(query)) {
828-
for (const val of query.variables) {
829-
this.mapAggregate(val, varAggrMap);
830-
}
831-
}
832-
if (query.solutionModifiers.having) {
833-
for (const val of query.solutionModifiers.having.having) {
834-
this.mapAggregate(val, varAggrMap);
835-
}
836-
}
837-
if (query.solutionModifiers.order) {
838-
for (const val of query.solutionModifiers.order.orderDefs) {
839-
this.mapAggregate(val, varAggrMap);
840-
}
841-
}
810+
const variables = F.isQuerySelect(query) || F.isQueryDescribe(query) ?
811+
query.variables.map(x => this.mapAggregate(x, varAggrMap)) :
812+
undefined;
813+
const having = query.solutionModifiers.having ?
814+
query.solutionModifiers.having.having.map(x => this.mapAggregate(x, varAggrMap)) :
815+
undefined;
816+
const order = query.solutionModifiers.order ?
817+
query.solutionModifiers.order.orderDefs.map(x => this.mapAggregate(x, varAggrMap)) :
818+
undefined;
842819

843820
// Step: GROUP BY - If we found an aggregate, in group by or implicitly, do Group function.
844821
// 18.2.4.1 Grouping and Aggregation
@@ -852,12 +829,17 @@ class QueryTranslator {
852829
if (F.isTerm(expression)) {
853830
// This will always be a var, otherwise sparql would be invalid
854831
vars.push(this.translateTerm(<TermVariable> expression));
855-
} else if ('variable' in expression) {
856-
const var_ = this.translateTerm(expression.variable);
857-
res = this.factory.createExtend(res, var_, this.translateExpression(expression.value));
858832
} else {
859-
const var_ = this.generateFreshVar();
860-
res = this.factory.createExtend(res, var_, this.translateExpression(expression));
833+
let var_: RDF.Variable;
834+
let expr: Expression;
835+
if ('variable' in expression) {
836+
var_ = this.translateTerm(expression.variable);
837+
expr = expression.value;
838+
} else {
839+
var_ = this.generateFreshVar();
840+
expr = expression;
841+
}
842+
res = this.factory.createExtend(res, var_, this.translateExpression(expr));
861843
vars.push(var_);
862844
}
863845
}
@@ -866,8 +848,8 @@ class QueryTranslator {
866848
}
867849

868850
// 18.2.4.2
869-
if (query.solutionModifiers.having) {
870-
for (const filter of query.solutionModifiers.having.having) {
851+
if (having) {
852+
for (const filter of having) {
871853
res = this.factory.createFilter(res, this.translateExpression(filter));
872854
}
873855
}
@@ -880,14 +862,14 @@ class QueryTranslator {
880862
// 18.2.4.4
881863
let PatternValues: (RDF.Variable | RDF.NamedNode)[] = [];
882864

883-
if (F.isQuerySelect(query) || F.isQueryDescribe(query)) {
865+
if (variables) {
884866
// Sort variables for consistent output
885-
if (query.variables.some(wild => F.isWildcard(wild))) {
886-
PatternValues = Object.values(this.inScopeVariables(query))
867+
if (variables.some(wild => F.isWildcard(wild))) {
868+
PatternValues = [ ...this.inScopeVariables(query).values() ].map(x => this.dataFactory.variable(x))
887869
.sort((left, right) => left.value.localeCompare(right.value));
888870
} else {
889871
// Wildcard has been filtered out above
890-
for (const var_ of <Exclude<typeof query.variables, [Wildcard]>> query.variables) {
872+
for (const var_ of <(TermVariable | TermIri | PatternBind)[]> variables) {
891873
// Can have non-variables with DESCRIBE
892874
if (F.isTerm(var_)) {
893875
PatternValues.push(this.translateTerm(var_));
@@ -913,8 +895,8 @@ class QueryTranslator {
913895
// not using toList and toMultiset
914896

915897
// 18.2.5.1
916-
if (query.solutionModifiers.order) {
917-
res = this.factory.createOrderBy(res, query.solutionModifiers.order.orderDefs.map((expr) => {
898+
if (order) {
899+
res = this.factory.createOrderBy(res, order.map((expr) => {
918900
let result = this.translateExpression(expr.expression);
919901
if (expr.descending) {
920902
result = this.factory.createOperatorExpression('desc', [ result ]);
@@ -965,37 +947,42 @@ class QueryTranslator {
965947
return res;
966948
}
967949

968-
// Rewrites some of the input sparql object to make use of aggregate variables
969-
private mapAggregate(thingy: mapAggregateType, aggregates: Record<string, ExpressionAggregate>): void {
950+
/**
951+
* Rewrites some of the input sparql object to make use of aggregate variables
952+
* It thus replaces aggregates by their representative variable and registers the mapping.
953+
*
954+
* DEBUG NOTE: The type is tricky since it does replace aggregates by variables, but it works for the most part
955+
*/
956+
private mapAggregate<T extends MapAggregateType>(thingy: T, aggregates: Record<string, ExpressionAggregate>): T {
970957
const F = this.astFactory;
971958

972959
if (F.isExpressionAggregate(thingy)) {
973-
let val: RDF.Variable | undefined;
960+
// Needed to take away the difference in the various `loc` descriptions
961+
const canonicalAggregate = this.astFactory.forcedAutoGenTree<ExpressionAggregate>(thingy);
962+
let val: TermVariable | undefined;
963+
// Look for the matching aggregate
974964
for (const [ key, aggregate ] of Object.entries(aggregates)) {
975-
if (equal(aggregate, thingy)) {
976-
val = this.dataFactory.variable(key);
965+
if (equal(aggregate, canonicalAggregate)) {
966+
val = F.variable(key, F.sourceLocation());
977967
break;
978968
}
979969
}
980970
if (val !== undefined) {
981-
return;
971+
return <T> val;
982972
}
983973
const freshVar = this.generateFreshVar();
984-
aggregates[freshVar.value] = thingy;
985-
return;
974+
aggregates[freshVar.value] = canonicalAggregate;
975+
return <T> F.variable(freshVar.value, F.sourceLocation());
986976
}
987977

988978
if (F.isExpressionPure(thingy) && !F.isExpressionPatternOperation(thingy)) {
989-
for (const expr of thingy.args) {
990-
this.mapAggregate(expr, aggregates);
991-
}
992-
return;
979+
return { ...thingy, args: thingy.args.map(x => this.mapAggregate(x, aggregates)) };
993980
}
994-
995981
// Non-aggregate expression
996982
if ('expression' in thingy && thingy.expression) {
997-
this.mapAggregate(thingy.expression, aggregates);
983+
return { ...thingy, expression: this.mapAggregate(thingy.expression, aggregates) };
998984
}
985+
return thingy;
999986
}
1000987

1001988
private translateBoundAggregate(thingy: ExpressionAggregate, variable: RDF.Variable): Algebra.BoundAggregate {
@@ -1008,6 +995,9 @@ class QueryTranslator {
1008995
this.registerContextDefinitions(update.context);
1009996
return update.operation ? [ this.translateSingleUpdate(update.operation) ] : [];
1010997
});
998+
if (updates.length === 0) {
999+
return this.factory.createNop();
1000+
}
10111001
if (updates.length === 1) {
10121002
return updates[0];
10131003
}
@@ -1076,8 +1066,10 @@ class QueryTranslator {
10761066
} else if (F.isUpdateOperationInsertData(op)) {
10771067
insertTriples.push(...op.data.flatMap(quad => this.translateUpdateTriplesBlock(quad)));
10781068
} else {
1079-
deleteTriples.push(...op.delete.flatMap(quad => this.translateUpdateTriplesBlock(quad)));
1080-
insertTriples.push(...op.insert.flatMap(quad => this.translateUpdateTriplesBlock(quad)));
1069+
deleteTriples.push(...op.delete.flatMap(quad =>
1070+
this.translateUpdateTriplesBlock(quad, op.graph ? this.translateTerm(op.graph) : op.graph)));
1071+
insertTriples.push(...op.insert.flatMap(quad =>
1072+
this.translateUpdateTriplesBlock(quad, op.graph ? this.translateTerm(op.graph) : op.graph)));
10811073
if (op.where.length > 0) {
10821074
where = this.translateGraphPattern(F.patternGroup(op.where, F.sourceLocation(...op.where)));
10831075
const use: { default: RDF.NamedNode[]; named: RDF.NamedNode[] } = this.translateDatasetClause(op.from);
@@ -1105,20 +1097,21 @@ class QueryTranslator {
11051097
}
11061098

11071099
// UPDATE parsing will always return quads and have no GRAPH elements
1108-
private translateUpdateTriplesBlock(thingy: PatternBgp | GraphQuads): Algebra.Pattern[] {
1100+
private translateUpdateTriplesBlock(thingy: PatternBgp | GraphQuads, graph: RDF.NamedNode | undefined = undefined):
1101+
Algebra.Pattern[] {
11091102
const F = this.astFactory;
1110-
let graph: TermIri | TermVariable | undefined;
1103+
let currentGraph: RDF.NamedNode | RDF.Variable | undefined = graph;
11111104
let patternBgp: PatternBgp;
11121105
if (F.isGraphQuads(thingy)) {
1113-
graph = thingy.graph;
1106+
currentGraph = this.translateTerm(thingy.graph);
11141107
patternBgp = thingy.triples;
11151108
} else {
11161109
patternBgp = thingy;
11171110
}
11181111
let triples: FlattenedTriple[] = [];
11191112
this.translateBasicGraphPattern(patternBgp.triples, triples);
1120-
if (graph) {
1121-
triples = triples.map(triple => Object.assign(triple, { graph }));
1113+
if (currentGraph) {
1114+
triples = triples.map(triple => Object.assign(triple, { graph: currentGraph }));
11221115
}
11231116
return triples.map(triple => this.translateQuad(triple));
11241117
}

packages/algebra-transformer-1-1/test/algebra.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ describe('algebra output', () => {
1515
const rootJson = path.join(__dirname, 'algebra');
1616
const rootJsonBlankToVariable = path.join(__dirname, 'algebra-blank-to-var');
1717

18-
console.error(rootSparql);
19-
2018
const canon = Util.getCanonicalizerInstance();
2119
const parser = new Parser();
2220

@@ -33,7 +31,7 @@ describe('algebra output', () => {
3331
if (!fs.existsSync(jsonPath) && blankToVariable) {
3432
return;
3533
}
36-
it (`${name.replace(rootJson, '')}${blankToVariable ? ' (no blanks)' : ''}`, ({ expect }) => {
34+
it(`${name.replace(rootJson, '')}${blankToVariable ? ' (no blanks)' : ''}`, ({ expect }) => {
3735
const query = fs.readFileSync(sparqlName, 'utf8');
3836
const ast = parser.parse(query);
3937
const algebra = LibUtil.objectify(

packages/rules-sparql-1-1/lib/grammar/builtIn.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ SparqlGrammarRule<'builtInGroup_concat', ExpressionAggregateDefault | Expression
255255
operatorToken.image,
256256
Boolean(distinctToken),
257257
expr,
258-
undefined,
258+
' ',
259259
F.sourceLocation(operatorToken, closeToken),
260260
);
261261
});

0 commit comments

Comments
 (0)