Skip to content

Commit

Permalink
Fix incorrect blank node canonicalization during bindings comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensworks committed Sep 12, 2019
1 parent 39298fc commit f395a65
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 8 deletions.
27 changes: 19 additions & 8 deletions lib/testcase/sparql/QueryResultBindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,37 @@ export class QueryResultBindings implements IQueryResultBindings {
return fromRdf(term);
case 'BlankNode':
if (!(term.value in blankNodeCounters)) {
blankNodeCounters[term.value] = 0;
blankNodeCounters[term.value] = Object.keys(blankNodeCounters).length;
}
const blankNodeCounter = blankNodeCounters[term.value]++;
const blankNodeCounter = blankNodeCounters[term.value];
return '_:' + blankNodeCounter;
default:
return termToString(term);
}
}

public static hashBinding(binding: {[variable: string]: RDF.Term},
blankNodeCounters: {[label: string]: number}) {
const bHash: {[id: string]: string} = {};
for (const variable of Object.keys(binding).sort()) {
bHash[variable] = QueryResultBindings.serializeTerm(binding[variable], blankNodeCounters);
}
return stringify(bHash);
}

public static hashBindings(bindings: {[variable: string]: RDF.Term}[],
blankNodeCounters: {[label: string]: number}, checkOrder: boolean): string {
const hash = [];
if (!checkOrder) {
// Sort *before* we normalize blank nodes, otherwise isomorphic bindings may end up being sorted differently.
// We do this sorting using fresh blank node counters for each binding.
bindings = bindings.sort((binding1, binding2) => QueryResultBindings.hashBinding(binding1, {})
.localeCompare(QueryResultBindings.hashBinding(binding2, {})));
}
for (const b of bindings) {
const bHash: {[id: string]: string} = {};
for (const variable in b) {
bHash[variable] = QueryResultBindings.serializeTerm(b[variable], blankNodeCounters);
}
hash.push(stringify(bHash));
hash.push(QueryResultBindings.hashBinding(b, blankNodeCounters));
}
return (checkOrder ? hash : hash.sort()).join('');
return hash.join('');
}

public static hashBindingsCount(bindings: {[variable: string]: RDF.Term}[],
Expand Down
74 changes: 74 additions & 0 deletions test/testcase/sparql/QueryResultBindings-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,80 @@ describe('QueryResultBindings', () => {
], true);
});

describe('#hashBinding', () => {
it('should a binding with named nodes', () => {
const binding = {
'?a': namedNode('a1'),
'?b': namedNode('b1'),
};
return expect(QueryResultBindings.hashBinding(binding, {}))
.toEqual('{\"?a\":\"a1\",\"?b\":\"b1\"}');
});

it('should a binding with named nodes populated in a different order', () => {
const binding = {
'?b': namedNode('b1'),
};
binding['?a'] = namedNode('a1');
return expect(QueryResultBindings.hashBinding(binding, {}))
.toEqual('{\"?a\":\"a1\",\"?b\":\"b1\"}');
});
});

describe('#hashBindings', () => {
describe('ignoring order', () => {
it('should hash bindings with named nodes', () => {
return expect(QueryResultBindings.hashBindings(bindingsAB1.value, {}, false))
.toEqual('{\"?a\":\"a1\",\"?b\":\"b1\"}{\"?a\":\"a2\",\"?b\":\"b2\"}');
});

it('should unordered hash bindings with named nodes', () => {
return expect(QueryResultBindings.hashBindings([
{
'?a': namedNode('a2'),
'?b': namedNode('b2'),
},
{
'?a': namedNode('a1'),
'?b': namedNode('b1'),
},
], {}, false))
.toEqual('{\"?a\":\"a1\",\"?b\":\"b1\"}{\"?a\":\"a2\",\"?b\":\"b2\"}');
});

it('should hash bindings with blank nodes', () => {
return expect(QueryResultBindings.hashBindings(bindingBlankNode1Lower.value, {}, false))
.toEqual('{\"?c\":\"_:0\",\"?d\":\"_:1\"}{\"?c\":\"_:2\",\"?d\":\"_:3\"}');
});
});

describe('checking order', () => {
it('should hash bindings with named nodes', () => {
return expect(QueryResultBindings.hashBindings(bindingsAB1.value, {}, true))
.toEqual('{\"?a\":\"a1\",\"?b\":\"b1\"}{\"?a\":\"a2\",\"?b\":\"b2\"}');
});

it('should unordered hash bindings with named nodes', () => {
return expect(QueryResultBindings.hashBindings([
{
'?a': namedNode('a2'),
'?b': namedNode('b2'),
},
{
'?a': namedNode('a1'),
'?b': namedNode('b1'),
},
], {}, true))
.toEqual('{\"?a\":\"a2\",\"?b\":\"b2\"}{\"?a\":\"a1\",\"?b\":\"b1\"}');
});

it('should hash bindings with blank nodes', () => {
return expect(QueryResultBindings.hashBindings(bindingBlankNode1Lower.value, {}, true))
.toEqual('{\"?c\":\"_:0\",\"?d\":\"_:1\"}{\"?c\":\"_:2\",\"?d\":\"_:3\"}');
});
});
});

describe('when instantiated', () => {
it('should be instance of QueryResultBindings', () => {
return expect(bindingsAB1).toBeInstanceOf(QueryResultBindings);
Expand Down

0 comments on commit f395a65

Please sign in to comment.