Skip to content

Commit e1cc8dd

Browse files
authored
fix: support indexing of quoted triples (#369)
* fix: support deeply nested triples in termFromId and termToId * chore: extend dataset tests for deeply nested triples * perf: index terms of quoted triples * chore: add performance test for quoted triples * Update test/N3Store-test.js
1 parent 3b25596 commit e1cc8dd

File tree

6 files changed

+585
-121
lines changed

6 files changed

+585
-121
lines changed

perf/N3StoreStar-perf.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env node
2+
const N3 = require('..');
3+
const assert = require('assert');
4+
5+
console.log('N3Store performance test');
6+
7+
const prefix = 'http://example.org/#';
8+
9+
/* Test triples */
10+
const dim = Number.parseInt(process.argv[2], 10) || 22;
11+
const dimSquared = dim * dim;
12+
const dimCubed = dimSquared * dim;
13+
const dimToTheFour = dimCubed * dim;
14+
const dimToTheFive = dimToTheFour * dim;
15+
16+
const store = new N3.Store();
17+
let TEST = `- Adding ${dimToTheFive} triples to the default graph`;
18+
console.time(TEST);
19+
let i, j, k, l, m;
20+
for (i = 0; i < dim; i++)
21+
for (j = 0; j < dim; j++)
22+
for (k = 0; k < dim; k++)
23+
for (l = 0; l < dim; l++)
24+
for (m = 0; m < dim; m++)
25+
store.addQuad(
26+
N3.DataFactory.quad(
27+
N3.DataFactory.namedNode(prefix + i),
28+
N3.DataFactory.namedNode(prefix + j),
29+
N3.DataFactory.namedNode(prefix + k)
30+
),
31+
N3.DataFactory.namedNode(prefix + l),
32+
N3.DataFactory.namedNode(prefix + m)
33+
);
34+
console.timeEnd(TEST);
35+
36+
console.log(`* Memory usage for triples: ${Math.round(process.memoryUsage().rss / 1024 / 1024)}MB`);
37+
38+
TEST = `- Finding all ${dimToTheFive} triples in the default graph ${dimSquared * 1} times (0 variables)`;
39+
console.time(TEST);
40+
for (i = 0; i < dim; i++)
41+
for (j = 0; j < dim; j++)
42+
for (k = 0; k < dim; k++)
43+
for (l = 0; l < dim; l++)
44+
for (m = 0; m < dim; m++)
45+
assert.equal(store.getQuads(
46+
N3.DataFactory.quad(
47+
N3.DataFactory.namedNode(prefix + i),
48+
N3.DataFactory.namedNode(prefix + j),
49+
N3.DataFactory.namedNode(prefix + k)
50+
),
51+
N3.DataFactory.namedNode(prefix + l),
52+
N3.DataFactory.namedNode(prefix + m)
53+
).length, 1);
54+
console.timeEnd(TEST);
55+
56+
TEST = `- Finding all ${dimCubed} triples in the default graph ${dimSquared * 2} times (1 variable subject)`;
57+
console.time(TEST);
58+
for (i = 0; i < dim; i++)
59+
for (j = 0; j < dim; j++)
60+
assert.equal(store.getQuads(null, N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j)).length, dimCubed);
61+
console.timeEnd(TEST);
62+
63+
TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable predicate)`;
64+
console.time(TEST);
65+
for (i = 0; i < dim; i++)
66+
for (j = 0; j < dim; j++)
67+
assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), null, N3.DataFactory.namedNode(prefix + j)).length, 0);
68+
console.timeEnd(TEST);
69+
70+
TEST = `- Finding all ${dim} triples in the default graph ${dimSquared * 4} times (1 variable predicate)`;
71+
console.time(TEST);
72+
for (i = 0; i < dim; i++)
73+
for (j = 0; j < dim; j++)
74+
for (k = 0; k < dim; k++)
75+
for (l = 0; l < dim; l++)
76+
assert.equal(store.getQuads(N3.DataFactory.quad(
77+
N3.DataFactory.namedNode(prefix + i),
78+
N3.DataFactory.namedNode(prefix + j),
79+
N3.DataFactory.namedNode(prefix + k)
80+
), null, N3.DataFactory.namedNode(prefix + l)).length, dim);
81+
console.timeEnd(TEST);
82+
83+
TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable object)`;
84+
console.time(TEST);
85+
for (i = 0; i < dim; i++)
86+
for (j = 0; j < dim; j++)
87+
assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j), null).length, 0);
88+
console.timeEnd(TEST);
89+
90+
TEST = `- Finding all ${dim} triples in the default graph ${dimSquared * 4} times (1 variable objects)`;
91+
console.time(TEST);
92+
for (i = 0; i < dim; i++)
93+
for (j = 0; j < dim; j++)
94+
for (k = 0; k < dim; k++)
95+
for (l = 0; l < dim; l++)
96+
assert.equal(store.getQuads(N3.DataFactory.quad(
97+
N3.DataFactory.namedNode(prefix + i),
98+
N3.DataFactory.namedNode(prefix + j),
99+
N3.DataFactory.namedNode(prefix + k)
100+
), N3.DataFactory.namedNode(prefix + l), null).length, dim);
101+
console.timeEnd(TEST);
102+
103+
TEST = `- Finding all ${dimSquared} triples in the default graph ${dimSquared * 1} times (2 variables)`;
104+
console.time(TEST);
105+
for (i = 0; i < dim; i++)
106+
for (j = 0; j < dim; j++)
107+
for (k = 0; k < dim; k++)
108+
assert.equal(store.getQuads(
109+
N3.DataFactory.quad(
110+
N3.DataFactory.namedNode(prefix + i),
111+
N3.DataFactory.namedNode(prefix + j),
112+
N3.DataFactory.namedNode(prefix + k)
113+
),
114+
null,
115+
null
116+
).length,
117+
dimSquared);
118+
console.timeEnd(TEST);

perf/N3StoreStarViews-perf.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env node
2+
const N3 = require('../lib');
3+
const assert = require('assert');
4+
5+
console.log('N3Store performance test');
6+
7+
const prefix = 'http://example.org/#';
8+
9+
/* Test triples */
10+
const dim = Number.parseInt(process.argv[2], 10) || 64;
11+
const dimSquared = dim * dim;
12+
const dimCubed = dimSquared * dim;
13+
const dimToTheFour = dimCubed * dim;
14+
const dimToTheFive = dimToTheFour * dim;
15+
16+
const store = new N3.Store();
17+
let TEST = `- Adding ${dimToTheFive} triples to the default graph`;
18+
console.time(TEST);
19+
let i, j, k, l, m;
20+
for (i = 0; i < dim; i++)
21+
for (j = 0; j < dim; j++)
22+
for (k = 0; k < dim; k++)
23+
for (l = 0; l < 3; l++)
24+
for (m = 0; m < 3; m++)
25+
store.addQuad(
26+
N3.DataFactory.quad(
27+
N3.DataFactory.namedNode(prefix + i),
28+
N3.DataFactory.namedNode(prefix + j),
29+
N3.DataFactory.namedNode(prefix + k)
30+
),
31+
N3.DataFactory.namedNode(prefix + l),
32+
N3.DataFactory.namedNode(prefix + m)
33+
);
34+
console.timeEnd(TEST);
35+
36+
console.log(`* Memory usage for triples: ${Math.round(process.memoryUsage().rss / 1024 / 1024)}MB`);
37+
38+
TEST = `- Finding all ${dimToTheFive} triples in the default graph ${dimSquared * 1} times (0 variables)`;
39+
console.time(TEST);
40+
for (i = 0; i < dim; i++)
41+
for (j = 0; j < dim; j++)
42+
for (k = 0; k < dim; k++)
43+
for (l = 0; l < 3; l++)
44+
for (m = 0; m < 3; m++)
45+
assert.equal(store.getQuads(
46+
N3.DataFactory.quad(
47+
N3.DataFactory.namedNode(prefix + i),
48+
N3.DataFactory.namedNode(prefix + j),
49+
N3.DataFactory.namedNode(prefix + k)
50+
),
51+
N3.DataFactory.namedNode(prefix + l),
52+
N3.DataFactory.namedNode(prefix + m)
53+
).length, 1);
54+
console.timeEnd(TEST);
55+
56+
TEST = `- Finding all ${dimCubed} triples in the default graph ${dimSquared * 2} times (1 variable subject)`;
57+
console.time(TEST);
58+
for (i = 0; i < 3; i++)
59+
for (j = 0; j < 3; j++)
60+
assert.equal(store.getQuads(null, N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j)).length, dimCubed);
61+
console.timeEnd(TEST);
62+
63+
TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable predicate)`;
64+
console.time(TEST);
65+
for (i = 0; i < dim; i++)
66+
for (j = 0; j < dim; j++)
67+
assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), null, N3.DataFactory.namedNode(prefix + j)).length, 0);
68+
console.timeEnd(TEST);
69+
70+
TEST = `- Finding all ${3} triples in the default graph ${dimCubed * 3} times (1 variable predicate)`;
71+
console.time(TEST);
72+
for (i = 0; i < dim; i++)
73+
for (j = 0; j < dim; j++)
74+
for (k = 0; k < dim; k++)
75+
for (l = 0; l < 3; l++)
76+
assert.equal(store.getQuads(N3.DataFactory.quad(
77+
N3.DataFactory.namedNode(prefix + i),
78+
N3.DataFactory.namedNode(prefix + j),
79+
N3.DataFactory.namedNode(prefix + k)
80+
), null, N3.DataFactory.namedNode(prefix + l)).length, 3);
81+
console.timeEnd(TEST);
82+
83+
TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable object)`;
84+
console.time(TEST);
85+
for (i = 0; i < dim; i++)
86+
for (j = 0; j < dim; j++)
87+
assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j), null).length, 0);
88+
console.timeEnd(TEST);
89+
90+
TEST = `- Finding all ${3} triples in the default graph ${dimCubed * 3} times (1 variable objects)`;
91+
console.time(TEST);
92+
for (i = 0; i < dim; i++)
93+
for (j = 0; j < dim; j++)
94+
for (k = 0; k < dim; k++)
95+
for (l = 0; l < 3; l++)
96+
assert.equal(store.getQuads(N3.DataFactory.quad(
97+
N3.DataFactory.namedNode(prefix + i),
98+
N3.DataFactory.namedNode(prefix + j),
99+
N3.DataFactory.namedNode(prefix + k)
100+
), N3.DataFactory.namedNode(prefix + l), null).length, 3);
101+
console.timeEnd(TEST);
102+
103+
TEST = `- Finding all ${9} triples in the default graph ${dimCubed} times (2 variables)`;
104+
console.time(TEST);
105+
for (i = 0; i < dim; i++)
106+
for (j = 0; j < dim; j++)
107+
for (k = 0; k < dim; k++)
108+
assert.equal(store.getQuads(
109+
N3.DataFactory.quad(
110+
N3.DataFactory.namedNode(prefix + i),
111+
N3.DataFactory.namedNode(prefix + j),
112+
N3.DataFactory.namedNode(prefix + k)
113+
),
114+
null,
115+
null
116+
).length,
117+
9);
118+
console.timeEnd(TEST);

src/N3DataFactory.js

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ let DEFAULTGRAPH;
1010
let _blankNodeCounter = 0;
1111

1212
const escapedLiteral = /^"(.*".*)(?="[^"]*$)/;
13-
const quadId = /^<<("(?:""|[^"])*"[^ ]*|[^ ]+) ("(?:""|[^"])*"[^ ]*|[^ ]+) ("(?:""|[^"])*"[^ ]*|[^ ]+) ?("(?:""|[^"])*"[^ ]*|[^ ]+)?>>$/;
1413

1514
// ## DataFactory singleton
1615
const DataFactory = {
@@ -188,9 +187,12 @@ export class DefaultGraph extends Term {
188187
// ## DefaultGraph singleton
189188
DEFAULTGRAPH = new DefaultGraph();
190189

191-
192190
// ### Constructs a term from the given internal string ID
193-
export function termFromId(id, factory) {
191+
// The third 'nested' parameter of this function is to aid
192+
// with recursion over nested terms. It should not be used
193+
// by consumers of this library.
194+
// See https://github.com/rdfjs/N3.js/pull/311#discussion_r1061042725
195+
export function termFromId(id, factory, nested) {
194196
factory = factory || DataFactory;
195197

196198
// Falsy value or empty string indicate the default graph
@@ -215,21 +217,28 @@ export function termFromId(id, factory) {
215217
return factory.literal(id.substr(1, endPos - 1),
216218
id[endPos + 1] === '@' ? id.substr(endPos + 2)
217219
: factory.namedNode(id.substr(endPos + 3)));
218-
case '<':
219-
const components = quadId.exec(id);
220-
return factory.quad(
221-
termFromId(unescapeQuotes(components[1]), factory),
222-
termFromId(unescapeQuotes(components[2]), factory),
223-
termFromId(unescapeQuotes(components[3]), factory),
224-
components[4] && termFromId(unescapeQuotes(components[4]), factory)
225-
);
220+
case '[':
221+
id = JSON.parse(id);
222+
break;
226223
default:
227-
return factory.namedNode(id);
224+
if (!nested || !Array.isArray(id)) {
225+
return factory.namedNode(id);
226+
}
228227
}
228+
return factory.quad(
229+
termFromId(id[0], factory, true),
230+
termFromId(id[1], factory, true),
231+
termFromId(id[2], factory, true),
232+
id[3] && termFromId(id[3], factory, true)
233+
);
229234
}
230235

231236
// ### Constructs an internal string ID from the given term or ID string
232-
export function termToId(term) {
237+
// The third 'nested' parameter of this function is to aid
238+
// with recursion over nested terms. It should not be used
239+
// by consumers of this library.
240+
// See https://github.com/rdfjs/N3.js/pull/311#discussion_r1061042725
241+
export function termToId(term, nested) {
233242
if (typeof term === 'string')
234243
return term;
235244
if (term instanceof Term && term.termType !== 'Quad')
@@ -247,17 +256,15 @@ export function termToId(term) {
247256
term.language ? `@${term.language}` :
248257
(term.datatype && term.datatype.value !== xsd.string ? `^^${term.datatype.value}` : '')}`;
249258
case 'Quad':
250-
// To identify RDF* quad components, we escape quotes by doubling them.
251-
// This avoids the overhead of backslash parsing of Turtle-like syntaxes.
252-
return `<<${
253-
escapeQuotes(termToId(term.subject))
254-
} ${
255-
escapeQuotes(termToId(term.predicate))
256-
} ${
257-
escapeQuotes(termToId(term.object))
258-
}${
259-
(isDefaultGraph(term.graph)) ? '' : ` ${termToId(term.graph)}`
260-
}>>`;
259+
const res = [
260+
termToId(term.subject, true),
261+
termToId(term.predicate, true),
262+
termToId(term.object, true),
263+
];
264+
if (!isDefaultGraph(term.graph)) {
265+
res.push(termToId(term.graph, true));
266+
}
267+
return nested ? res : JSON.stringify(res);
261268
default: throw new Error(`Unexpected termType: ${term.termType}`);
262269
}
263270
}

0 commit comments

Comments
 (0)