@undefineds.co/xpod 0.3.18 → 0.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/config/bun.json +57 -11
  2. package/config/cloud.json +14 -12
  3. package/config/local.json +16 -14
  4. package/config/xpod.json +47 -9
  5. package/dist/api/matrix/PodMatrixStore.d.ts +4 -7
  6. package/dist/api/matrix/PodMatrixStore.js +116 -148
  7. package/dist/api/matrix/PodMatrixStore.js.map +1 -1
  8. package/dist/api/matrix/types.d.ts +2 -0
  9. package/dist/api/matrix/types.js.map +1 -1
  10. package/dist/api/runs/PiAgentRuntimeDriver.d.ts +1 -0
  11. package/dist/api/runs/PiAgentRuntimeDriver.js +4 -1
  12. package/dist/api/runs/PiAgentRuntimeDriver.js.map +1 -1
  13. package/dist/components/components.jsonld +3 -0
  14. package/dist/components/context.jsonld +71 -32
  15. package/dist/http/SubgraphSparqlHttpHandler.d.ts +1 -0
  16. package/dist/http/SubgraphSparqlHttpHandler.js +27 -4
  17. package/dist/http/SubgraphSparqlHttpHandler.js.map +1 -1
  18. package/dist/http/SubgraphSparqlHttpHandler.jsonld +4 -0
  19. package/dist/http/vector/VectorHttpHandler.d.ts +5 -1
  20. package/dist/http/vector/VectorHttpHandler.js +5 -5
  21. package/dist/http/vector/VectorHttpHandler.js.map +1 -1
  22. package/dist/http/vector/VectorHttpHandler.jsonld +40 -28
  23. package/dist/index.d.ts +5 -2
  24. package/dist/index.js +9 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/runtime/Proxy.d.ts +3 -0
  27. package/dist/runtime/Proxy.js +31 -7
  28. package/dist/runtime/Proxy.js.map +1 -1
  29. package/dist/solidfs/LocalSolidFS.js +31 -124
  30. package/dist/solidfs/LocalSolidFS.js.map +1 -1
  31. package/dist/solidfs/SolidFsPathUtils.d.ts +13 -0
  32. package/dist/solidfs/SolidFsPathUtils.js +114 -0
  33. package/dist/solidfs/SolidFsPathUtils.js.map +1 -0
  34. package/dist/solidfs/SolidFsSyncJournal.d.ts +117 -0
  35. package/dist/solidfs/SolidFsSyncJournal.js +553 -0
  36. package/dist/solidfs/SolidFsSyncJournal.js.map +1 -0
  37. package/dist/solidfs/index.d.ts +1 -0
  38. package/dist/solidfs/index.js +1 -0
  39. package/dist/solidfs/index.js.map +1 -1
  40. package/dist/solidfs/types.d.ts +1 -0
  41. package/dist/solidfs/types.js.map +1 -1
  42. package/dist/storage/SparqlUpdateResourceStore.js +94 -33
  43. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  44. package/dist/storage/accessors/MixDataAccessor.d.ts +22 -5
  45. package/dist/storage/accessors/MixDataAccessor.js +376 -61
  46. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  47. package/dist/storage/accessors/MixDataAccessor.jsonld +73 -5
  48. package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js +32 -10
  49. package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js.map +1 -1
  50. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +28 -6
  51. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
  52. package/dist/storage/accessors/SolidRdfDataAccessor.d.ts +45 -0
  53. package/dist/storage/accessors/SolidRdfDataAccessor.js +277 -0
  54. package/dist/storage/accessors/SolidRdfDataAccessor.js.map +1 -0
  55. package/dist/storage/accessors/SolidRdfDataAccessor.jsonld +161 -0
  56. package/dist/storage/rdf/Rdf3xIndex.d.ts +122 -0
  57. package/dist/storage/rdf/Rdf3xIndex.js +2695 -0
  58. package/dist/storage/rdf/Rdf3xIndex.js.map +1 -0
  59. package/dist/storage/rdf/Rdf3xIndex.jsonld +528 -0
  60. package/dist/storage/rdf/Rdf3xSchema.d.ts +20 -0
  61. package/dist/storage/rdf/Rdf3xSchema.js +65 -0
  62. package/dist/storage/rdf/Rdf3xSchema.js.map +1 -0
  63. package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +10 -4
  64. package/dist/storage/rdf/RdfLocalQueryEngine.js +607 -127
  65. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -1
  66. package/dist/storage/rdf/RdfQuadIndex.d.ts +12 -1
  67. package/dist/storage/rdf/RdfQuadIndex.js +152 -22
  68. package/dist/storage/rdf/RdfQuadIndex.js.map +1 -1
  69. package/dist/storage/rdf/RdfQuadIndex.jsonld +36 -4
  70. package/dist/storage/rdf/RdfSparqlAdapter.d.ts +20 -2
  71. package/dist/storage/rdf/RdfSparqlAdapter.js +364 -40
  72. package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -1
  73. package/dist/storage/rdf/RdfSparqlAdapter.jsonld +60 -0
  74. package/dist/storage/rdf/RdfTermDictionary.d.ts +8 -0
  75. package/dist/storage/rdf/RdfTermDictionary.js +141 -70
  76. package/dist/storage/rdf/RdfTermDictionary.js.map +1 -1
  77. package/dist/storage/rdf/RdfTermDictionary.jsonld +24 -0
  78. package/dist/storage/rdf/RdfTextIndex.js +10 -3
  79. package/dist/storage/rdf/RdfTextIndex.js.map +1 -1
  80. package/dist/storage/rdf/SolidRdfEngine.d.ts +15 -6
  81. package/dist/storage/rdf/SolidRdfEngine.js +218 -25
  82. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  83. package/dist/storage/rdf/SolidRdfEngine.jsonld +70 -7
  84. package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +11 -7
  85. package/dist/storage/rdf/SolidRdfSparqlEngine.js +60 -47
  86. package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
  87. package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +9 -5
  88. package/dist/storage/rdf/index.d.ts +2 -2
  89. package/dist/storage/rdf/index.js +3 -3
  90. package/dist/storage/rdf/index.js.map +1 -1
  91. package/dist/storage/rdf/models-benchmark.d.ts +12 -1
  92. package/dist/storage/rdf/models-benchmark.js +549 -32
  93. package/dist/storage/rdf/models-benchmark.js.map +1 -1
  94. package/dist/storage/rdf/types.d.ts +81 -7
  95. package/dist/storage/rdf/types.js.map +1 -1
  96. package/dist/storage/sparql/CompatibilitySparqlEngine.d.ts +36 -0
  97. package/dist/storage/sparql/CompatibilitySparqlEngine.js +96 -0
  98. package/dist/storage/sparql/CompatibilitySparqlEngine.js.map +1 -0
  99. package/dist/storage/sparql/CompatibilitySparqlEngine.jsonld +123 -0
  100. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.d.ts +35 -0
  101. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js +112 -0
  102. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js.map +1 -0
  103. package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -36
  104. package/dist/storage/sparql/SubgraphQueryEngine.js +2 -115
  105. package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
  106. package/dist/storage/sparql/SubgraphQueryEngine.jsonld +1 -124
  107. package/dist/terminal/AclPermissionService.d.ts +2 -1
  108. package/dist/terminal/AclPermissionService.js +26 -3
  109. package/dist/terminal/AclPermissionService.js.map +1 -1
  110. package/dist/terminal/TerminalSessionManager.js +25 -3
  111. package/dist/terminal/TerminalSessionManager.js.map +1 -1
  112. package/package.json +1 -1
  113. package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +0 -55
  114. package/dist/storage/rdf/Rdf3xTripleIndex.js +0 -1235
  115. package/dist/storage/rdf/Rdf3xTripleIndex.js.map +0 -1
@@ -1,1235 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Rdf3xTripleIndex = void 0;
4
- const node_fs_1 = require("node:fs");
5
- const node_path_1 = require("node:path");
6
- const n3_1 = require("n3");
7
- const SqliteRuntime_1 = require("../SqliteRuntime");
8
- const RdfTermDictionary_1 = require("./RdfTermDictionary");
9
- const RdfTermSemantics_1 = require("./RdfTermSemantics");
10
- const TERM_COLUMN = {
11
- subject: 'subject_id',
12
- predicate: 'predicate_id',
13
- object: 'object_id',
14
- };
15
- const ORDER_COLUMN = {
16
- graph: 'graph_id',
17
- ...TERM_COLUMN,
18
- };
19
- const PATTERN_COLUMNS = {
20
- graph: 'graph_id',
21
- ...TERM_COLUMN,
22
- };
23
- const TERM_KEYS = ['subject', 'predicate', 'object'];
24
- const PERMUTATIONS = [
25
- { name: 'SPO', table: 'rdf3x_spo', columns: ['subject_id', 'predicate_id', 'object_id'] },
26
- { name: 'SOP', table: 'rdf3x_sop', columns: ['subject_id', 'object_id', 'predicate_id'] },
27
- { name: 'PSO', table: 'rdf3x_pso', columns: ['predicate_id', 'subject_id', 'object_id'] },
28
- { name: 'POS', table: 'rdf3x_pos', columns: ['predicate_id', 'object_id', 'subject_id'] },
29
- { name: 'OSP', table: 'rdf3x_osp', columns: ['object_id', 'subject_id', 'predicate_id'] },
30
- { name: 'OPS', table: 'rdf3x_ops', columns: ['object_id', 'predicate_id', 'subject_id'] },
31
- ];
32
- const PAIR_PROJECTIONS = [
33
- { name: 'SP', table: 'rdf3x_stat_sp', columns: ['subject_id', 'predicate_id'], remainder: 'object_id' },
34
- { name: 'SO', table: 'rdf3x_stat_so', columns: ['subject_id', 'object_id'], remainder: 'predicate_id' },
35
- { name: 'PS', table: 'rdf3x_stat_ps', columns: ['predicate_id', 'subject_id'], remainder: 'object_id' },
36
- { name: 'PO', table: 'rdf3x_stat_po', columns: ['predicate_id', 'object_id'], remainder: 'subject_id' },
37
- { name: 'OS', table: 'rdf3x_stat_os', columns: ['object_id', 'subject_id'], remainder: 'predicate_id' },
38
- { name: 'OP', table: 'rdf3x_stat_op', columns: ['object_id', 'predicate_id'], remainder: 'subject_id' },
39
- ];
40
- const TERM_PROJECTIONS = [
41
- { name: 'S', table: 'rdf3x_stat_s', column: 'subject_id' },
42
- { name: 'P', table: 'rdf3x_stat_p', column: 'predicate_id' },
43
- { name: 'O', table: 'rdf3x_stat_o', column: 'object_id' },
44
- ];
45
- class Rdf3xTripleIndex {
46
- constructor(options) {
47
- this.options = options;
48
- this.sqliteRuntime = (0, SqliteRuntime_1.createSqliteRuntime)();
49
- this.db = null;
50
- this.dictionary = null;
51
- }
52
- open() {
53
- if (this.db) {
54
- return;
55
- }
56
- if (this.options.path !== ':memory:') {
57
- const dir = (0, node_path_1.dirname)(this.options.path);
58
- if (!(0, node_fs_1.existsSync)(dir)) {
59
- (0, node_fs_1.mkdirSync)(dir, { recursive: true });
60
- }
61
- }
62
- this.db = this.sqliteRuntime.openDatabase(this.options.path);
63
- this.dictionary = new RdfTermDictionary_1.RdfTermDictionary(this.db);
64
- this.dictionary.initialize();
65
- this.initializeSchema();
66
- }
67
- close() {
68
- this.db?.close();
69
- this.db = null;
70
- this.dictionary = null;
71
- }
72
- clear() {
73
- this.clearRdf3xTables();
74
- }
75
- rebuildFromCurrentQuads() {
76
- const start = Date.now();
77
- const db = this.requireDb();
78
- const scannedQuads = db.prepare('SELECT COUNT(*) AS count FROM rdf_quads').get()?.count ?? 0;
79
- db.transaction(() => {
80
- this.clearRdf3xTables();
81
- db.prepare(`
82
- INSERT INTO rdf3x_triple_membership (
83
- graph_id,
84
- subject_id,
85
- predicate_id,
86
- object_id,
87
- source_file_id,
88
- source_line_no
89
- )
90
- SELECT
91
- graph_id,
92
- subject_id,
93
- predicate_id,
94
- object_id,
95
- source_file_id,
96
- source_line_no
97
- FROM rdf_quads
98
- `).run();
99
- for (const permutation of PERMUTATIONS) {
100
- db.prepare(`
101
- INSERT OR IGNORE INTO ${permutation.table} (${permutation.columns.join(', ')})
102
- SELECT DISTINCT ${permutation.columns.join(', ')}
103
- FROM rdf_quads
104
- `).run();
105
- }
106
- for (const projection of PAIR_PROJECTIONS) {
107
- this.rebuildPairProjection(projection);
108
- }
109
- for (const projection of TERM_PROJECTIONS) {
110
- this.rebuildTermProjection(projection);
111
- }
112
- })();
113
- const stats = this.stats();
114
- return {
115
- scannedQuads,
116
- uniqueTriples: stats.uniqueTriples,
117
- memberships: stats.membershipCount,
118
- projectionRows: pairProjectionRowTotal(stats.pairProjectionRows) + termProjectionRowTotal(stats.termProjectionRows),
119
- durationMs: Date.now() - start,
120
- };
121
- }
122
- scan(pattern, options) {
123
- const start = Date.now();
124
- const resolved = this.resolvePattern(pattern);
125
- if (resolved.unresolved) {
126
- return {
127
- quads: [],
128
- metrics: this.metrics('none', 0, 0, start, [`unresolved ${resolved.unresolved}`]),
129
- };
130
- }
131
- const permutation = this.choosePermutation(resolved.ids, { objectRange: Boolean(resolved.objectNumericRange) });
132
- const compiled = this.compileScanSql(permutation, resolved, options);
133
- const matchedRows = this.requireDb()
134
- .prepare(compiled.countSql)
135
- .get(...compiled.countParams)?.count ?? 0;
136
- const rows = this.requireDb().prepare(compiled.sql).all(...compiled.params);
137
- return {
138
- quads: this.rowsToQuads(rows),
139
- metrics: this.metrics(permutation.name, matchedRows, rows.length, start, [
140
- `Rdf3xPermutationScan(${permutation.name})`,
141
- ...compiled.queryPlan,
142
- compiled.sql,
143
- ]),
144
- };
145
- }
146
- joinPatterns(patterns, options) {
147
- const start = Date.now();
148
- if (patterns.length === 0) {
149
- return {
150
- bindings: [],
151
- metrics: this.joinMetrics('none', 0, 0, start, ['Rdf3xJoinBGP(empty)']),
152
- };
153
- }
154
- const compiled = this.compileJoinPatterns(patterns, options);
155
- if (compiled.unresolved) {
156
- return {
157
- bindings: [],
158
- metrics: this.joinMetrics('none', 0, 0, start, [
159
- ...compiled.queryPlan,
160
- `unresolved ${compiled.unresolved}`,
161
- ]),
162
- };
163
- }
164
- const rows = this.requireDb().prepare(compiled.sql).all(...compiled.params);
165
- const matchedRows = compiled.countSql
166
- ? this.requireDb().prepare(compiled.countSql).get(...compiled.countParams)?.count ?? 0
167
- : rows.length;
168
- return {
169
- bindings: this.joinRowsToBindings(rows, compiled.variableAliases),
170
- metrics: this.joinMetrics(compiled.indexChoice, matchedRows, rows.length, start, [...compiled.queryPlan, compiled.sql]),
171
- };
172
- }
173
- estimateCardinality(pattern) {
174
- const resolved = this.resolvePattern(pattern);
175
- if (resolved.unresolved) {
176
- return {
177
- uniqueTriples: 0,
178
- matchingQuads: 0,
179
- source: 'exact-membership',
180
- indexChoice: 'none',
181
- };
182
- }
183
- if (resolved.objectNumericRange) {
184
- return this.estimateNumericObjectRangeCardinality(resolved);
185
- }
186
- if (resolved.ids.graph !== undefined || resolved.graphPrefix !== undefined) {
187
- return this.estimateResolvedMembershipCardinality(resolved);
188
- }
189
- const termIds = TERM_KEYS.filter((key) => resolved.ids[key] !== undefined);
190
- const permutation = this.choosePermutation(resolved.ids);
191
- if (termIds.length === 3) {
192
- return this.estimateExactTriple(resolved.ids, permutation.name);
193
- }
194
- if (termIds.length === 2) {
195
- return this.estimatePairProjection(resolved.ids, permutation.name);
196
- }
197
- if (termIds.length === 1) {
198
- return this.estimateTermProjection(resolved.ids, permutation.name);
199
- }
200
- return {
201
- uniqueTriples: this.rowCount('rdf3x_spo'),
202
- matchingQuads: this.rowCount('rdf3x_triple_membership'),
203
- source: 'full-count',
204
- indexChoice: permutation.name,
205
- };
206
- }
207
- stats() {
208
- const spaceObjects = this.collectSpaceObjects();
209
- const accountedBytes = spaceObjects.reduce((sum, object) => sum + object.bytes, 0);
210
- const databaseBytes = accountedBytes || this.estimateDatabaseBytes();
211
- return {
212
- uniqueTriples: this.rowCount('rdf3x_spo'),
213
- membershipCount: this.rowCount('rdf3x_triple_membership'),
214
- graphCount: this.requireDb()
215
- .prepare('SELECT COUNT(DISTINCT graph_id) AS count FROM rdf3x_triple_membership')
216
- .get()?.count ?? 0,
217
- permutationRows: Object.fromEntries(PERMUTATIONS.map((permutation) => [
218
- permutation.name,
219
- this.rowCount(permutation.table),
220
- ])),
221
- pairProjectionRows: Object.fromEntries(PAIR_PROJECTIONS.map((projection) => [
222
- projection.name,
223
- this.rowCount(projection.table),
224
- ])),
225
- termProjectionRows: Object.fromEntries(TERM_PROJECTIONS.map((projection) => [
226
- projection.name,
227
- this.rowCount(projection.table),
228
- ])),
229
- databaseBytes,
230
- tableBytes: sumSpaceObjects(spaceObjects, 'table'),
231
- indexBytes: sumSpaceObjects(spaceObjects, 'index'),
232
- spaceObjects,
233
- };
234
- }
235
- collectSpaceObjects() {
236
- const db = this.requireDb();
237
- try {
238
- const schemaRows = db.prepare(`
239
- SELECT name, type, tbl_name
240
- FROM sqlite_schema
241
- WHERE type IN ('table', 'index')
242
- AND (name LIKE 'rdf3x_%' OR tbl_name LIKE 'rdf3x_%')
243
- `).all();
244
- const schema = new Map(schemaRows.map((row) => [row.name, row]));
245
- try {
246
- const rows = db.prepare(`
247
- SELECT name, COUNT(*) AS pages, SUM(pgsize) AS bytes
248
- FROM dbstat
249
- WHERE name LIKE 'rdf3x_%'
250
- OR name LIKE 'sqlite_autoindex_rdf3x_%'
251
- GROUP BY name
252
- ORDER BY name
253
- `).all();
254
- if (rows.length > 0) {
255
- return rows.map((row) => {
256
- const object = schema.get(row.name);
257
- const kind = rdf3xSpaceObjectKind(row.name, object?.type, object?.tbl_name);
258
- return {
259
- name: row.name,
260
- kind,
261
- ...(object?.tbl_name && object.tbl_name !== row.name ? { tableName: object.tbl_name } : {}),
262
- pages: row.pages,
263
- bytes: row.bytes ?? 0,
264
- };
265
- });
266
- }
267
- }
268
- catch {
269
- // dbstat is optional in SQLite builds and often unavailable for in-memory databases.
270
- }
271
- return this.estimateSpaceObjectsFromSchema(schemaRows);
272
- }
273
- catch {
274
- return [];
275
- }
276
- }
277
- initializeSchema() {
278
- const permutationTables = PERMUTATIONS.map((permutation) => `
279
- CREATE TABLE IF NOT EXISTS ${permutation.table} (
280
- ${permutation.columns[0]} INTEGER NOT NULL,
281
- ${permutation.columns[1]} INTEGER NOT NULL,
282
- ${permutation.columns[2]} INTEGER NOT NULL,
283
- PRIMARY KEY (${permutation.columns.join(', ')})
284
- );
285
- `).join('\n');
286
- const pairProjectionTables = PAIR_PROJECTIONS.map((projection) => `
287
- CREATE TABLE IF NOT EXISTS ${projection.table} (
288
- ${projection.columns[0]} INTEGER NOT NULL,
289
- ${projection.columns[1]} INTEGER NOT NULL,
290
- triple_count INTEGER NOT NULL,
291
- membership_count INTEGER NOT NULL,
292
- min_${projection.remainder} INTEGER,
293
- max_${projection.remainder} INTEGER,
294
- PRIMARY KEY (${projection.columns.join(', ')})
295
- );
296
- `).join('\n');
297
- const termProjectionTables = TERM_PROJECTIONS.map((projection) => `
298
- CREATE TABLE IF NOT EXISTS ${projection.table} (
299
- ${projection.column} INTEGER NOT NULL PRIMARY KEY,
300
- triple_count INTEGER NOT NULL,
301
- membership_count INTEGER NOT NULL
302
- );
303
- `).join('\n');
304
- this.requireDb().exec(`
305
- CREATE TABLE IF NOT EXISTS rdf3x_triple_membership (
306
- graph_id INTEGER NOT NULL,
307
- subject_id INTEGER NOT NULL,
308
- predicate_id INTEGER NOT NULL,
309
- object_id INTEGER NOT NULL,
310
- source_file_id INTEGER,
311
- source_line_no INTEGER,
312
- PRIMARY KEY (graph_id, subject_id, predicate_id, object_id)
313
- );
314
-
315
- CREATE INDEX IF NOT EXISTS rdf3x_membership_gspo
316
- ON rdf3x_triple_membership(graph_id, subject_id, predicate_id, object_id);
317
- CREATE INDEX IF NOT EXISTS rdf3x_membership_spo
318
- ON rdf3x_triple_membership(subject_id, predicate_id, object_id);
319
- CREATE INDEX IF NOT EXISTS rdf3x_membership_source
320
- ON rdf3x_triple_membership(source_file_id);
321
-
322
- ${permutationTables}
323
- ${pairProjectionTables}
324
- ${termProjectionTables}
325
- `);
326
- }
327
- clearRdf3xTables() {
328
- const db = this.requireDb();
329
- db.exec([
330
- ...PAIR_PROJECTIONS.map((projection) => `DELETE FROM ${projection.table};`),
331
- ...TERM_PROJECTIONS.map((projection) => `DELETE FROM ${projection.table};`),
332
- 'DELETE FROM rdf3x_triple_membership;',
333
- ...PERMUTATIONS.map((permutation) => `DELETE FROM ${permutation.table};`),
334
- ].join('\n'));
335
- }
336
- rebuildPairProjection(projection) {
337
- const [left, right] = projection.columns;
338
- this.requireDb().prepare(`
339
- INSERT INTO ${projection.table} (
340
- ${left},
341
- ${right},
342
- triple_count,
343
- membership_count,
344
- min_${projection.remainder},
345
- max_${projection.remainder}
346
- )
347
- SELECT
348
- triple.${left},
349
- triple.${right},
350
- triple.triple_count,
351
- COALESCE(member.membership_count, 0) AS membership_count,
352
- triple.min_remainder,
353
- triple.max_remainder
354
- FROM (
355
- SELECT
356
- ${left},
357
- ${right},
358
- COUNT(*) AS triple_count,
359
- MIN(${projection.remainder}) AS min_remainder,
360
- MAX(${projection.remainder}) AS max_remainder
361
- FROM rdf3x_spo
362
- GROUP BY ${left}, ${right}
363
- ) triple
364
- LEFT JOIN (
365
- SELECT
366
- ${left},
367
- ${right},
368
- COUNT(*) AS membership_count
369
- FROM rdf3x_triple_membership
370
- GROUP BY ${left}, ${right}
371
- ) member
372
- ON member.${left} = triple.${left}
373
- AND member.${right} = triple.${right}
374
- `).run();
375
- }
376
- rebuildTermProjection(projection) {
377
- this.requireDb().prepare(`
378
- INSERT INTO ${projection.table} (
379
- ${projection.column},
380
- triple_count,
381
- membership_count
382
- )
383
- SELECT
384
- triple.${projection.column},
385
- triple.triple_count,
386
- COALESCE(member.membership_count, 0) AS membership_count
387
- FROM (
388
- SELECT
389
- ${projection.column},
390
- COUNT(*) AS triple_count
391
- FROM rdf3x_spo
392
- GROUP BY ${projection.column}
393
- ) triple
394
- LEFT JOIN (
395
- SELECT
396
- ${projection.column},
397
- COUNT(*) AS membership_count
398
- FROM rdf3x_triple_membership
399
- GROUP BY ${projection.column}
400
- ) member
401
- ON member.${projection.column} = triple.${projection.column}
402
- `).run();
403
- }
404
- compileScanSql(permutation, resolved, options) {
405
- const conditions = [];
406
- const params = [];
407
- const queryPlan = [`Permutation(${permutation.name})`];
408
- const ids = resolved.ids;
409
- for (const key of TERM_KEYS) {
410
- const id = ids[key];
411
- if (id === undefined) {
412
- continue;
413
- }
414
- conditions.push(`idx.${TERM_COLUMN[key]} = ?`);
415
- params.push(id);
416
- }
417
- if (ids.graph !== undefined) {
418
- conditions.push('membership.graph_id = ?');
419
- params.push(ids.graph);
420
- queryPlan.push('GraphMembershipFilter');
421
- }
422
- const graphPrefixJoin = resolved.graphPrefix
423
- ? ` JOIN rdf_terms graph_prefix
424
- ON graph_prefix.id = membership.graph_id`
425
- : '';
426
- if (resolved.graphPrefix) {
427
- conditions.push(`graph_prefix.kind = ?
428
- AND graph_prefix.value >= ?
429
- AND graph_prefix.value < ?`);
430
- params.push('iri', resolved.graphPrefix, `${resolved.graphPrefix}\uffff`);
431
- queryPlan.push('GraphPrefixMembershipFilter');
432
- }
433
- if (resolved.objectNumericRange) {
434
- const range = resolved.objectNumericRange;
435
- conditions.push('object_numeric.kind = ?');
436
- params.push('literal');
437
- if (range.min !== undefined) {
438
- conditions.push(`object_numeric.numeric_value ${range.minInclusive ? '>=' : '>'} ?`);
439
- params.push(range.min);
440
- }
441
- if (range.max !== undefined) {
442
- conditions.push(`object_numeric.numeric_value ${range.maxInclusive ? '<=' : '<'} ?`);
443
- params.push(range.max);
444
- }
445
- queryPlan.push(`NumericRange(object${range.min !== undefined ? (range.minInclusive ? '$gte' : '$gt') : ''}${range.max !== undefined ? (range.maxInclusive ? '$lte' : '$lt') : ''})`);
446
- }
447
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
448
- const orderClause = this.buildOrderClause(options);
449
- const from = `
450
- FROM ${permutation.table} idx
451
- JOIN rdf3x_triple_membership membership
452
- ON membership.subject_id = idx.subject_id
453
- AND membership.predicate_id = idx.predicate_id
454
- AND membership.object_id = idx.object_id
455
- ${graphPrefixJoin}
456
- ${resolved.objectNumericRange ? 'JOIN rdf_terms object_numeric ON object_numeric.id = idx.object_id' : ''}
457
- `;
458
- const pagination = this.buildPagination(options);
459
- return {
460
- sql: `
461
- SELECT
462
- membership.graph_id,
463
- idx.subject_id,
464
- idx.predicate_id,
465
- idx.object_id
466
- ${from}
467
- ${orderClause.joins}
468
- ${whereClause}
469
- ${orderClause.orderBy || ` ORDER BY ${permutation.columns.map((column) => `idx.${column}`).join(', ')}, membership.graph_id`}
470
- ${pagination.sql}
471
- `,
472
- params: [...params, ...pagination.params],
473
- countSql: `SELECT COUNT(*) AS count ${from} ${whereClause}`,
474
- countParams: params,
475
- queryPlan: [
476
- ...queryPlan,
477
- ...(orderClause.orderBy ? [`Rdf3xJoinOrder(${(options?.order ?? []).map((entry, index) => `${options?.reverse ? 'desc' : 'asc'}:${entry}:${index}`).join(',')})`] : []),
478
- ...(pagination.sql ? ['Pagination'] : []),
479
- ],
480
- };
481
- }
482
- compileJoinPatterns(patterns, options) {
483
- const sources = patterns.map((entry, inputIndex) => {
484
- const resolved = this.resolveJoinPattern(entry.pattern);
485
- const permutation = this.choosePermutation(resolved.ids, { objectRange: Boolean(resolved.objectNumericRange) });
486
- const estimate = resolved.unresolved
487
- ? {
488
- uniqueTriples: 0,
489
- matchingQuads: 0,
490
- source: 'full-count',
491
- indexChoice: 'none',
492
- }
493
- : this.estimateResolvedCardinality(resolved);
494
- return {
495
- inputIndex,
496
- alias: `q${inputIndex}`,
497
- membershipAlias: `m${inputIndex}`,
498
- entry,
499
- resolved,
500
- permutation,
501
- estimate,
502
- };
503
- });
504
- const startPattern = this.chooseJoinStart(sources);
505
- const orderedSources = [startPattern, ...sources.filter((source) => source.inputIndex !== startPattern.inputIndex)];
506
- const queryPlan = [
507
- `Rdf3xJoinBGP(${patterns.length})`,
508
- `Rdf3xJoinOrder(${orderedSources.map((source) => `?${source.inputIndex}:${source.estimate.indexChoice}`).join('>')})`,
509
- ];
510
- const variableColumns = new Map();
511
- const variableAliases = new Map();
512
- const conditions = [];
513
- const params = [];
514
- const countParams = [];
515
- const indexChoices = [];
516
- const fromFragments = [];
517
- for (const [position, source] of orderedSources.entries()) {
518
- const scanSql = this.joinSourceSql(source, position === 0);
519
- if (source.resolved.unresolved) {
520
- return {
521
- sql: '',
522
- params: [],
523
- countParams: [],
524
- indexChoice: 'none',
525
- queryPlan,
526
- variableColumns,
527
- variableAliases,
528
- unresolved: source.resolved.unresolved,
529
- };
530
- }
531
- fromFragments.push(scanSql.from);
532
- conditions.push(...scanSql.conditions);
533
- params.push(...scanSql.params);
534
- countParams.push(...scanSql.params);
535
- queryPlan.push(...scanSql.queryPlan);
536
- indexChoices.push(source.estimate.indexChoice);
537
- for (const key of ['graph', ...TERM_KEYS]) {
538
- const variableName = source.entry.variables[key];
539
- if (!variableName) {
540
- continue;
541
- }
542
- const column = key === 'graph'
543
- ? `${source.membershipAlias}.graph_id`
544
- : `${source.alias}.${TERM_COLUMN[key]}`;
545
- const existing = variableColumns.get(variableName);
546
- if (existing) {
547
- conditions.push(`${existing} = ${column}`);
548
- }
549
- else {
550
- variableColumns.set(variableName, column);
551
- }
552
- }
553
- }
554
- const projectVariables = options?.project ?? [...variableColumns.keys()];
555
- const projectionColumns = projectVariables.map((variableName) => {
556
- const column = variableColumns.get(variableName);
557
- if (!column) {
558
- throw new Error(`Rdf3x BGP join cannot project unbound variable: ${variableName}`);
559
- }
560
- const alias = `v${variableAliases.size}`;
561
- variableAliases.set(variableName, alias);
562
- return `${column} AS ${alias}`;
563
- });
564
- const projection = projectionColumns.length > 0
565
- ? `${options?.distinct ? 'DISTINCT ' : ''}${projectionColumns.join(', ')}`
566
- : `${options?.distinct ? 'DISTINCT ' : ''}1 AS __empty`;
567
- const orderClause = this.buildJoinOrderClause(options, variableColumns);
568
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
569
- const from = fromFragments.join('');
570
- let sql = `SELECT ${projection} FROM ${from}${orderClause.joins}${whereClause}${orderClause.orderBy}`;
571
- const sqlParams = [...params];
572
- const paginated = options?.limit !== undefined || options?.offset !== undefined;
573
- const countMatchedRows = options?.countMatchedRows ?? true;
574
- if (options?.limit !== undefined) {
575
- sql += ' LIMIT ?';
576
- sqlParams.push(options.limit);
577
- }
578
- if (options?.offset !== undefined) {
579
- if (options.limit === undefined) {
580
- sql += ' LIMIT -1';
581
- }
582
- sql += ' OFFSET ?';
583
- sqlParams.push(options.offset);
584
- }
585
- if (orderClause.orderBy) {
586
- queryPlan.push(`Rdf3xJoinOrderBy(${(options?.orderBy ?? []).map((entry) => `${entry.direction ?? 'asc'}:${entry.variable}`).join(',')})`);
587
- }
588
- if (options?.distinct) {
589
- queryPlan.push(`Rdf3xJoinDistinct(${projectVariables.map((variableName) => `?${variableName}`).join(',')})`);
590
- }
591
- if (paginated) {
592
- queryPlan.push('Rdf3xJoinLimit');
593
- }
594
- return {
595
- sql,
596
- params: sqlParams,
597
- countSql: paginated && countMatchedRows ? `SELECT COUNT(*) AS count FROM ${from}${orderClause.joins}${whereClause}` : undefined,
598
- countParams,
599
- indexChoice: `Rdf3xJoinBGP(${indexChoices.join('>')})`,
600
- queryPlan,
601
- variableColumns,
602
- variableAliases,
603
- };
604
- }
605
- joinSourceSql(source, first) {
606
- const conditions = [];
607
- const params = [];
608
- const queryPlan = [`Rdf3xPermutationScan(${source.permutation.name})`];
609
- const alias = source.alias;
610
- const graphPrefixAlias = `${source.membershipAlias}_graph_prefix`;
611
- for (const key of TERM_KEYS) {
612
- const id = source.resolved.ids[key];
613
- if (id === undefined) {
614
- continue;
615
- }
616
- conditions.push(`${alias}.${TERM_COLUMN[key]} = ?`);
617
- params.push(id);
618
- }
619
- let from = first
620
- ? `${source.permutation.table} ${alias}
621
- JOIN rdf3x_triple_membership ${source.membershipAlias}
622
- ON ${source.membershipAlias}.subject_id = ${alias}.subject_id
623
- AND ${source.membershipAlias}.predicate_id = ${alias}.predicate_id
624
- AND ${source.membershipAlias}.object_id = ${alias}.object_id`
625
- : ` JOIN ${source.permutation.table} ${alias}
626
- ON 1 = 1
627
- JOIN rdf3x_triple_membership ${source.membershipAlias}
628
- ON ${source.membershipAlias}.subject_id = ${alias}.subject_id
629
- AND ${source.membershipAlias}.predicate_id = ${alias}.predicate_id
630
- AND ${source.membershipAlias}.object_id = ${alias}.object_id`;
631
- if (source.resolved.ids.graph !== undefined) {
632
- conditions.push(`${source.membershipAlias}.graph_id = ?`);
633
- params.push(source.resolved.ids.graph);
634
- queryPlan.push('GraphMembershipFilter');
635
- }
636
- if (source.resolved.graphPrefix !== undefined) {
637
- from += ` JOIN rdf_terms ${graphPrefixAlias}
638
- ON ${graphPrefixAlias}.id = ${source.membershipAlias}.graph_id`;
639
- conditions.push(`${graphPrefixAlias}.kind = ?
640
- AND ${graphPrefixAlias}.value >= ?
641
- AND ${graphPrefixAlias}.value < ?`);
642
- params.push('iri', source.resolved.graphPrefix, `${source.resolved.graphPrefix}\uffff`);
643
- queryPlan.push('GraphPrefixMembershipFilter');
644
- }
645
- if (source.resolved.objectNumericRange) {
646
- from += ` JOIN rdf_terms ${source.alias}_object_numeric
647
- ON ${source.alias}_object_numeric.id = ${source.alias}.object_id`;
648
- const range = source.resolved.objectNumericRange;
649
- conditions.push(`${source.alias}_object_numeric.kind = ?`);
650
- params.push('literal');
651
- if (range.min !== undefined) {
652
- conditions.push(`${source.alias}_object_numeric.numeric_value ${range.minInclusive ? '>=' : '>'} ?`);
653
- params.push(range.min);
654
- }
655
- if (range.max !== undefined) {
656
- conditions.push(`${source.alias}_object_numeric.numeric_value ${range.maxInclusive ? '<=' : '<'} ?`);
657
- params.push(range.max);
658
- }
659
- queryPlan.push(`NumericRange(object${range.min !== undefined ? (range.minInclusive ? '$gte' : '$gt') : ''}${range.max !== undefined ? (range.maxInclusive ? '$lte' : '$lt') : ''})`);
660
- }
661
- return {
662
- from,
663
- conditions,
664
- params,
665
- queryPlan,
666
- };
667
- }
668
- buildJoinOrderClause(options, variableColumns) {
669
- if (!options?.orderBy || options.orderBy.length === 0) {
670
- return { joins: '', orderBy: '' };
671
- }
672
- const joins = options.orderBy.map((entry, index) => {
673
- const column = variableColumns.get(entry.variable);
674
- if (!column) {
675
- throw new Error(`Rdf3x join cannot order by unbound variable: ${entry.variable}`);
676
- }
677
- const alias = `join_order_t${index}`;
678
- return {
679
- join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${column}`,
680
- order: `${alias}.value${entry.direction === 'desc' ? ' DESC' : ''}`,
681
- };
682
- });
683
- return {
684
- joins: joins.map((entry) => entry.join).join(''),
685
- orderBy: ` ORDER BY ${joins.map((entry) => entry.order).join(', ')}`,
686
- };
687
- }
688
- buildOrderClause(options) {
689
- if (!options?.order || options.order.length === 0) {
690
- return { joins: '', orderBy: '' };
691
- }
692
- const joins = options.order.map((termName, index) => {
693
- const column = ORDER_COLUMN[termName];
694
- const alias = `order_t${index}`;
695
- const direction = options.reverse ? ' DESC' : '';
696
- return {
697
- join: ` JOIN rdf_terms ${alias} ON ${alias}.id = idx.${column}`,
698
- order: `${alias}.value${direction}`,
699
- };
700
- });
701
- return {
702
- joins: joins.map((entry) => entry.join).join(''),
703
- orderBy: ` ORDER BY ${joins.map((entry) => entry.order).join(', ')}`,
704
- };
705
- }
706
- chooseJoinStart(sources) {
707
- if (sources.length === 0) {
708
- throw new Error('Rdf3x join requires at least one source');
709
- }
710
- return [...sources].sort((left, right) => this.compareJoinSources(left, right))[0];
711
- }
712
- compareJoinSources(left, right) {
713
- const leftResolved = left.resolved.unresolved ? Number.POSITIVE_INFINITY : left.estimate.matchingQuads;
714
- const rightResolved = right.resolved.unresolved ? Number.POSITIVE_INFINITY : right.estimate.matchingQuads;
715
- if (leftResolved !== rightResolved) {
716
- return leftResolved - rightResolved;
717
- }
718
- if (left.estimate.uniqueTriples !== right.estimate.uniqueTriples) {
719
- return left.estimate.uniqueTriples - right.estimate.uniqueTriples;
720
- }
721
- return left.inputIndex - right.inputIndex;
722
- }
723
- estimateResolvedCardinality(resolved) {
724
- const ids = resolved.ids;
725
- if (resolved.objectNumericRange) {
726
- return this.estimateNumericObjectRangeCardinality(resolved);
727
- }
728
- const termIds = TERM_KEYS.filter((key) => ids[key] !== undefined);
729
- if (ids.graph !== undefined || resolved.graphPrefix !== undefined) {
730
- return this.estimateResolvedMembershipCardinality(resolved);
731
- }
732
- if (termIds.length === 3) {
733
- return this.estimateExactTriple(ids, this.choosePermutation(ids).name);
734
- }
735
- if (termIds.length === 2) {
736
- return this.estimatePairProjection(ids, this.choosePermutation(ids).name);
737
- }
738
- if (termIds.length === 1) {
739
- return this.estimateTermProjection(ids, this.choosePermutation(ids).name);
740
- }
741
- return {
742
- uniqueTriples: this.rowCount('rdf3x_spo'),
743
- matchingQuads: this.rowCount('rdf3x_triple_membership'),
744
- source: 'full-count',
745
- indexChoice: this.choosePermutation(ids, { objectRange: Boolean(resolved.objectNumericRange) }).name,
746
- };
747
- }
748
- resolveJoinPattern(pattern) {
749
- const ids = {};
750
- let graphPrefix;
751
- let objectNumericRange;
752
- for (const key of ['graph', ...TERM_KEYS]) {
753
- const match = pattern[key];
754
- if (!match) {
755
- continue;
756
- }
757
- if (key === 'graph' && isGraphPrefixPattern(match)) {
758
- graphPrefix = match.$startsWith;
759
- continue;
760
- }
761
- if (key === 'object' && isNumericObjectRangePattern(match)) {
762
- const resolvedRange = this.resolveNumericObjectRange(match);
763
- if (!resolvedRange) {
764
- return { ids, graphPrefix, objectNumericRange: resolvedRange, unresolved: key };
765
- }
766
- objectNumericRange = resolvedRange;
767
- continue;
768
- }
769
- if (!isRdfTerm(match)) {
770
- return { ids, graphPrefix, unresolved: key };
771
- }
772
- const id = this.requireDictionary().find(match);
773
- if (id === undefined) {
774
- return { ids, graphPrefix, unresolved: key };
775
- }
776
- ids[key] = id;
777
- }
778
- return {
779
- ids,
780
- ...(graphPrefix !== undefined ? { graphPrefix } : {}),
781
- ...(objectNumericRange !== undefined ? { objectNumericRange } : {}),
782
- };
783
- }
784
- joinMetrics(indexChoice, matchedRows, returnedRows, start, queryPlan) {
785
- return {
786
- engine: 'solid-rdf3x',
787
- indexChoice,
788
- matchedRows,
789
- returnedRows,
790
- durationMs: Date.now() - start,
791
- queryPlan,
792
- };
793
- }
794
- joinRowsToBindings(rows, variableAliases) {
795
- const aliases = [...variableAliases.entries()];
796
- const termMap = this.requireDictionary().rowsForIds(rows.flatMap((row) => (aliases
797
- .map(([, alias]) => row[alias])
798
- .filter((id) => typeof id === 'number'))));
799
- return rows.map((row) => {
800
- const binding = {};
801
- for (const [variableName, alias] of aliases) {
802
- const id = row[alias];
803
- if (typeof id !== 'number') {
804
- continue;
805
- }
806
- binding[variableName] = requiredTerm(termMap, id);
807
- }
808
- return binding;
809
- });
810
- }
811
- buildPagination(options) {
812
- if (!options) {
813
- return { sql: '', params: [] };
814
- }
815
- const clauses = [];
816
- const params = [];
817
- if (options.limit !== undefined) {
818
- clauses.push('LIMIT ?');
819
- params.push(Math.max(0, options.limit));
820
- }
821
- if (options.offset !== undefined) {
822
- if (options.limit === undefined) {
823
- clauses.push('LIMIT -1');
824
- }
825
- clauses.push('OFFSET ?');
826
- params.push(Math.max(0, options.offset));
827
- }
828
- return {
829
- sql: clauses.length > 0 ? ` ${clauses.join(' ')}` : '',
830
- params,
831
- };
832
- }
833
- estimateExactTriple(ids, indexChoice) {
834
- const row = this.requireDb().prepare(`
835
- SELECT COUNT(*) AS count
836
- FROM rdf3x_spo
837
- WHERE subject_id = ?
838
- AND predicate_id = ?
839
- AND object_id = ?
840
- `).get(ids.subject, ids.predicate, ids.object);
841
- const membership = this.requireDb().prepare(`
842
- SELECT COUNT(*) AS count
843
- FROM rdf3x_triple_membership
844
- WHERE subject_id = ?
845
- AND predicate_id = ?
846
- AND object_id = ?
847
- `).get(ids.subject, ids.predicate, ids.object);
848
- return {
849
- uniqueTriples: row?.count ?? 0,
850
- matchingQuads: membership?.count ?? 0,
851
- source: 'exact-triple',
852
- indexChoice,
853
- };
854
- }
855
- estimatePairProjection(ids, indexChoice) {
856
- const projection = this.pairProjectionFor(ids);
857
- if (!projection) {
858
- return this.estimateMembershipCardinality(ids);
859
- }
860
- const [left, right] = projection.columns;
861
- const row = this.requireDb().prepare(`
862
- SELECT triple_count, membership_count
863
- FROM ${projection.table}
864
- WHERE ${left} = ?
865
- AND ${right} = ?
866
- `).get(ids[keyForColumn(left)], ids[keyForColumn(right)]);
867
- return {
868
- uniqueTriples: row?.triple_count ?? 0,
869
- matchingQuads: row?.membership_count ?? 0,
870
- source: 'projection-stat',
871
- indexChoice,
872
- };
873
- }
874
- estimateTermProjection(ids, indexChoice) {
875
- const key = TERM_KEYS.find((candidate) => ids[candidate] !== undefined);
876
- if (!key) {
877
- return this.estimateMembershipCardinality(ids);
878
- }
879
- const projection = TERM_PROJECTIONS.find((candidate) => candidate.column === TERM_COLUMN[key]);
880
- if (!projection) {
881
- return this.estimateMembershipCardinality(ids);
882
- }
883
- const row = this.requireDb().prepare(`
884
- SELECT triple_count, membership_count
885
- FROM ${projection.table}
886
- WHERE ${projection.column} = ?
887
- `).get(ids[key]);
888
- return {
889
- uniqueTriples: row?.triple_count ?? 0,
890
- matchingQuads: row?.membership_count ?? 0,
891
- source: 'term-stat',
892
- indexChoice,
893
- };
894
- }
895
- estimateMembershipCardinality(ids) {
896
- return this.estimateResolvedMembershipCardinality({ ids });
897
- }
898
- estimateResolvedMembershipCardinality(resolved) {
899
- const { joins, whereClause, params } = this.buildMembershipWhere(resolved);
900
- const matchingQuads = this.requireDb().prepare(`
901
- SELECT COUNT(*) AS count
902
- FROM rdf3x_triple_membership
903
- ${joins}
904
- ${whereClause}
905
- `).get(...params)?.count ?? 0;
906
- const uniqueTriples = this.requireDb().prepare(`
907
- SELECT COUNT(*) AS count
908
- FROM (
909
- SELECT DISTINCT subject_id, predicate_id, object_id
910
- FROM rdf3x_triple_membership
911
- ${joins}
912
- ${whereClause}
913
- ) distinct_triples
914
- `).get(...params)?.count ?? 0;
915
- return {
916
- uniqueTriples,
917
- matchingQuads,
918
- source: 'exact-membership',
919
- indexChoice: 'source-membership',
920
- };
921
- }
922
- buildMembershipWhere(resolved) {
923
- const ids = resolved.ids;
924
- const conditions = [];
925
- const params = [];
926
- for (const key of ['graph', ...TERM_KEYS]) {
927
- const id = ids[key];
928
- if (id === undefined) {
929
- continue;
930
- }
931
- conditions.push(`${PATTERN_COLUMNS[key]} = ?`);
932
- params.push(id);
933
- }
934
- const joins = resolved.graphPrefix !== undefined
935
- ? ` JOIN rdf_terms membership_graph_prefix
936
- ON membership_graph_prefix.id = rdf3x_triple_membership.graph_id`
937
- : '';
938
- if (resolved.graphPrefix !== undefined) {
939
- conditions.push(`membership_graph_prefix.kind = ?
940
- AND membership_graph_prefix.value >= ?
941
- AND membership_graph_prefix.value < ?`);
942
- params.push('iri', resolved.graphPrefix, `${resolved.graphPrefix}\uffff`);
943
- }
944
- return {
945
- whereClause: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '',
946
- joins,
947
- params,
948
- };
949
- }
950
- pairProjectionFor(ids) {
951
- const columns = TERM_KEYS
952
- .filter((key) => ids[key] !== undefined)
953
- .map((key) => TERM_COLUMN[key]);
954
- return PAIR_PROJECTIONS.find((projection) => (projection.columns.every((column) => columns.includes(column))));
955
- }
956
- resolvePattern(pattern) {
957
- const ids = {};
958
- let graphPrefix;
959
- let objectNumericRange;
960
- for (const key of ['graph', ...TERM_KEYS]) {
961
- const term = pattern[key];
962
- if (!term) {
963
- continue;
964
- }
965
- if (key === 'graph' && isGraphPrefixPattern(term)) {
966
- graphPrefix = term.$startsWith;
967
- continue;
968
- }
969
- if (key === 'object' && isNumericObjectRangePattern(term)) {
970
- const resolvedRange = this.resolveNumericObjectRange(term);
971
- if (!resolvedRange) {
972
- return { ids, graphPrefix, objectNumericRange: resolvedRange, unresolved: key };
973
- }
974
- objectNumericRange = resolvedRange;
975
- continue;
976
- }
977
- if (key === 'graph' && !isRdfTerm(term)) {
978
- return { ids, graphPrefix, unresolved: key };
979
- }
980
- if (key !== 'graph' && !isRdfTerm(term)) {
981
- return { ids, graphPrefix, unresolved: key };
982
- }
983
- const rdfTerm = term;
984
- const id = this.requireDictionary().find(rdfTerm);
985
- if (id === undefined) {
986
- return { ids, graphPrefix, unresolved: key };
987
- }
988
- ids[key] = id;
989
- }
990
- return {
991
- ids,
992
- ...(graphPrefix !== undefined ? { graphPrefix } : {}),
993
- ...(objectNumericRange !== undefined ? { objectNumericRange } : {}),
994
- };
995
- }
996
- estimateNumericObjectRangeCardinality(resolved) {
997
- const range = resolved.objectNumericRange;
998
- if (!range) {
999
- return this.estimateMembershipCardinality(resolved.ids);
1000
- }
1001
- const { joins, whereClause, params } = this.buildMembershipWhere({
1002
- ids: resolved.ids,
1003
- ...(resolved.graphPrefix !== undefined ? { graphPrefix: resolved.graphPrefix } : {}),
1004
- });
1005
- const numericConditions = ['object_numeric.kind = ?'];
1006
- const numericParams = ['literal'];
1007
- if (range.min !== undefined) {
1008
- numericConditions.push(`object_numeric.numeric_value ${range.minInclusive ? '>=' : '>'} ?`);
1009
- numericParams.push(range.min);
1010
- }
1011
- if (range.max !== undefined) {
1012
- numericConditions.push(`object_numeric.numeric_value ${range.maxInclusive ? '<=' : '<'} ?`);
1013
- numericParams.push(range.max);
1014
- }
1015
- const membershipWhere = whereClause
1016
- ? `${whereClause} AND ${numericConditions.join(' AND ')}`
1017
- : ` WHERE ${numericConditions.join(' AND ')}`;
1018
- const matchingQuads = this.requireDb().prepare(`
1019
- SELECT COUNT(*) AS count
1020
- FROM rdf3x_triple_membership
1021
- ${joins}
1022
- JOIN rdf_terms object_numeric ON object_numeric.id = rdf3x_triple_membership.object_id
1023
- ${membershipWhere}
1024
- `).get(...params, ...numericParams)?.count ?? 0;
1025
- const uniqueTriples = this.requireDb().prepare(`
1026
- SELECT COUNT(*) AS count
1027
- FROM (
1028
- SELECT DISTINCT subject_id, predicate_id, object_id
1029
- FROM rdf3x_triple_membership
1030
- ${joins}
1031
- JOIN rdf_terms object_numeric ON object_numeric.id = rdf3x_triple_membership.object_id
1032
- ${membershipWhere}
1033
- ) distinct_triples
1034
- `).get(...params, ...numericParams)?.count ?? 0;
1035
- return {
1036
- uniqueTriples,
1037
- matchingQuads,
1038
- source: 'exact-membership',
1039
- indexChoice: 'source-membership',
1040
- };
1041
- }
1042
- resolveNumericObjectRange(match) {
1043
- const range = {};
1044
- let hasRange = false;
1045
- for (const [operator, inclusive] of [
1046
- ['$gt', false],
1047
- ['$gte', true],
1048
- ['$lt', false],
1049
- ['$lte', true],
1050
- ]) {
1051
- const value = match[operator];
1052
- if (value === undefined) {
1053
- continue;
1054
- }
1055
- hasRange = true;
1056
- const numericValue = this.numericValueForPattern(value);
1057
- if (numericValue === undefined) {
1058
- return undefined;
1059
- }
1060
- if (operator === '$gt' || operator === '$gte') {
1061
- range.min = numericValue;
1062
- range.minInclusive = inclusive;
1063
- }
1064
- else {
1065
- range.max = numericValue;
1066
- range.maxInclusive = inclusive;
1067
- }
1068
- }
1069
- return hasRange ? range : undefined;
1070
- }
1071
- numericValueForPattern(value) {
1072
- if (typeof value === 'number') {
1073
- return Number.isFinite(value) ? value : undefined;
1074
- }
1075
- if (typeof value === 'string') {
1076
- const parsed = Number(value);
1077
- return Number.isFinite(parsed) ? parsed : undefined;
1078
- }
1079
- if (value.termType !== 'Literal' || !(0, RdfTermSemantics_1.isRdfNumericDatatype)(value.datatype.value)) {
1080
- return undefined;
1081
- }
1082
- const parsed = (0, RdfTermSemantics_1.rdfNumericValue)(value.value);
1083
- return Number.isFinite(parsed) ? parsed : undefined;
1084
- }
1085
- choosePermutation(ids, constraints) {
1086
- const has = (key) => ids[key] !== undefined;
1087
- const hasObjectConstraint = has('object') || Boolean(constraints?.objectRange);
1088
- if (has('subject') && has('predicate'))
1089
- return this.permutation('SPO');
1090
- if (has('subject') && hasObjectConstraint)
1091
- return this.permutation('SOP');
1092
- if (has('predicate') && has('subject'))
1093
- return this.permutation('PSO');
1094
- if (has('predicate') && hasObjectConstraint)
1095
- return this.permutation('POS');
1096
- if (hasObjectConstraint && has('subject'))
1097
- return this.permutation('OSP');
1098
- if (hasObjectConstraint && has('predicate'))
1099
- return this.permutation('OPS');
1100
- if (has('subject'))
1101
- return this.permutation('SPO');
1102
- if (has('predicate'))
1103
- return this.permutation('PSO');
1104
- if (hasObjectConstraint)
1105
- return this.permutation('OSP');
1106
- return this.permutation('SPO');
1107
- }
1108
- permutation(name) {
1109
- const permutation = PERMUTATIONS.find((candidate) => candidate.name === name);
1110
- if (!permutation) {
1111
- throw new Error(`Unknown RDF-3X permutation: ${name}`);
1112
- }
1113
- return permutation;
1114
- }
1115
- rowsToQuads(rows) {
1116
- const termMap = this.requireDictionary().rowsForIds(rows.flatMap((row) => [
1117
- row.graph_id,
1118
- row.subject_id,
1119
- row.predicate_id,
1120
- row.object_id,
1121
- ]));
1122
- return rows.map((row) => n3_1.DataFactory.quad(requiredTerm(termMap, row.subject_id), requiredTerm(termMap, row.predicate_id), requiredTerm(termMap, row.object_id), requiredTerm(termMap, row.graph_id)));
1123
- }
1124
- rowCount(table) {
1125
- return this.requireDb().prepare(`SELECT COUNT(*) AS count FROM ${table}`).get()?.count ?? 0;
1126
- }
1127
- collectPageCount() {
1128
- try {
1129
- return this.requireDb().prepare('PRAGMA page_count').get()?.page_count ?? 0;
1130
- }
1131
- catch {
1132
- return 0;
1133
- }
1134
- }
1135
- estimateDatabaseBytes() {
1136
- const pageSize = this.estimatePageSize();
1137
- const pageCount = this.collectPageCount();
1138
- return pageSize * pageCount;
1139
- }
1140
- estimateSpaceObjectsFromSchema(schemaRows) {
1141
- const pageSize = this.estimatePageSize();
1142
- return schemaRows.map((object) => ({
1143
- name: object.name,
1144
- kind: rdf3xSpaceObjectKind(object.name, object.type, object.tbl_name),
1145
- ...(object.tbl_name && object.tbl_name !== object.name ? { tableName: object.tbl_name } : {}),
1146
- pages: 1,
1147
- bytes: pageSize,
1148
- estimated: true,
1149
- }));
1150
- }
1151
- estimatePageSize() {
1152
- try {
1153
- return this.requireDb().prepare('PRAGMA page_size').get()?.page_size ?? 4096;
1154
- }
1155
- catch {
1156
- return 4096;
1157
- }
1158
- }
1159
- metrics(indexChoice, matchedRows, returnedRows, start, queryPlan) {
1160
- return {
1161
- engine: 'solid-rdf3x',
1162
- indexChoice,
1163
- matchedRows,
1164
- returnedRows,
1165
- durationMs: Date.now() - start,
1166
- queryPlan,
1167
- };
1168
- }
1169
- requireDb() {
1170
- if (!this.db) {
1171
- throw new Error('Rdf3xTripleIndex is not open');
1172
- }
1173
- return this.db;
1174
- }
1175
- requireDictionary() {
1176
- if (!this.dictionary) {
1177
- throw new Error('Rdf3xTripleIndex is not open');
1178
- }
1179
- return this.dictionary;
1180
- }
1181
- }
1182
- exports.Rdf3xTripleIndex = Rdf3xTripleIndex;
1183
- function keyForColumn(column) {
1184
- if (column === 'subject_id')
1185
- return 'subject';
1186
- if (column === 'predicate_id')
1187
- return 'predicate';
1188
- return 'object';
1189
- }
1190
- function requiredTerm(termMap, id) {
1191
- const term = termMap.get(id);
1192
- if (!term) {
1193
- throw new Error(`RDF term not found while reading RDF-3X index: ${id}`);
1194
- }
1195
- return term;
1196
- }
1197
- function isRdfTerm(value) {
1198
- return value !== null && typeof value === 'object' && 'termType' in value;
1199
- }
1200
- function pairProjectionRowTotal(rows) {
1201
- return Object.values(rows).reduce((sum, count) => sum + count, 0);
1202
- }
1203
- function termProjectionRowTotal(rows) {
1204
- return Object.values(rows).reduce((sum, count) => sum + count, 0);
1205
- }
1206
- function sumSpaceObjects(objects, kind) {
1207
- return objects
1208
- .filter((object) => object.kind === kind)
1209
- .reduce((sum, object) => sum + object.bytes, 0);
1210
- }
1211
- function rdf3xSpaceObjectKind(name, schemaType, tableName) {
1212
- if (schemaType === 'table' && name.startsWith('rdf3x_')) {
1213
- return 'table';
1214
- }
1215
- if (schemaType === 'index' && (name.startsWith('rdf3x_') || tableName?.startsWith('rdf3x_'))) {
1216
- return 'index';
1217
- }
1218
- if (name.startsWith('sqlite_')) {
1219
- return 'internal';
1220
- }
1221
- return 'unknown';
1222
- }
1223
- function isGraphPrefixPattern(value) {
1224
- return value !== null
1225
- && typeof value === 'object'
1226
- && '$startsWith' in value
1227
- && typeof value.$startsWith === 'string';
1228
- }
1229
- function isNumericObjectRangePattern(value) {
1230
- return value !== null
1231
- && typeof value === 'object'
1232
- && !('termType' in value)
1233
- && ['$gt', '$gte', '$lt', '$lte'].some((operator) => operator in value);
1234
- }
1235
- //# sourceMappingURL=Rdf3xTripleIndex.js.map