@undefineds.co/xpod 0.3.6 → 0.3.14
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.
- package/config/cli.json +1 -1
- package/config/cloud.json +54 -22
- package/config/local.json +56 -12
- package/config/resolver.json +10 -2
- package/config/xpod.base.json +50 -0
- package/config/xpod.json +8 -8
- package/dist/agents/config/resolve.js +10 -10
- package/dist/agents/config/resolve.js.map +1 -1
- package/dist/api/chatkit/index.d.ts +1 -1
- package/dist/api/chatkit/index.js.map +1 -1
- package/dist/api/chatkit/pod-store.d.ts +14 -11
- package/dist/api/chatkit/pod-store.js +114 -78
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/runtime/AcpAgentRuntime.js +1 -1
- package/dist/api/chatkit/runtime/AcpAgentRuntime.js.map +1 -1
- package/dist/api/chatkit/service.js +1 -1
- package/dist/api/chatkit/service.js.map +1 -1
- package/dist/api/chatkit/types.d.ts +11 -11
- package/dist/api/chatkit/types.js +3 -3
- package/dist/api/chatkit/types.js.map +1 -1
- package/dist/api/container/cloud.js +0 -8
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/index.js +2 -1
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +0 -7
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +3 -17
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +0 -2
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/PodManagementHandler.d.ts +3 -0
- package/dist/api/handlers/PodManagementHandler.js +71 -1
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/RunHandler.js +5 -5
- package/dist/api/handlers/RunHandler.js.map +1 -1
- package/dist/api/runs/AgentRuntimeTypes.d.ts +7 -8
- package/dist/api/runs/AgentRuntimeTypes.js.map +1 -1
- package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
- package/dist/api/runs/ManagedRunWorker.d.ts +1 -1
- package/dist/api/runs/ManagedRunWorker.js +6 -6
- package/dist/api/runs/ManagedRunWorker.js.map +1 -1
- package/dist/api/runs/PiAgentRuntimeDriver.d.ts +16 -1
- package/dist/api/runs/PiAgentRuntimeDriver.js +182 -23
- package/dist/api/runs/PiAgentRuntimeDriver.js.map +1 -1
- package/dist/api/runs/RunStateCenter.d.ts +3 -3
- package/dist/api/runs/RunStateCenter.js +13 -13
- package/dist/api/runs/RunStateCenter.js.map +1 -1
- package/dist/api/runs/store.d.ts +4 -4
- package/dist/api/runs/store.js +2 -2
- package/dist/api/runs/store.js.map +1 -1
- package/dist/api/service/VectorStoreService.d.ts +1 -1
- package/dist/api/service/VectorStoreService.js +16 -16
- package/dist/api/service/VectorStoreService.js.map +1 -1
- package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
- package/dist/api/tasks/TaskMaterializer.d.ts +3 -3
- package/dist/api/tasks/TaskMaterializer.js +11 -11
- package/dist/api/tasks/TaskMaterializer.js.map +1 -1
- package/dist/api/tasks/TaskService.d.ts +3 -3
- package/dist/api/tasks/TaskService.js +11 -7
- package/dist/api/tasks/TaskService.js.map +1 -1
- package/dist/api/tasks/store.d.ts +10 -4
- package/dist/api/tasks/store.js +14 -4
- package/dist/api/tasks/store.js.map +1 -1
- package/dist/api/workspace/types.d.ts +3 -3
- package/dist/api/workspace/types.js +6 -6
- package/dist/api/workspace/types.js.map +1 -1
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/start.js +9 -3
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/components/components.jsonld +8 -2
- package/dist/components/context.jsonld +302 -51
- package/dist/http/search/SearchHttpHandler.js +8 -8
- package/dist/http/search/SearchHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/PodLookupRepository.d.ts +11 -1
- package/dist/identity/drizzle/PodLookupRepository.js +95 -4
- package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
- package/dist/identity/drizzle/db.js +4 -43
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +0 -5
- package/dist/identity/drizzle/schema.pg.js +2 -16
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +19 -176
- package/dist/identity/drizzle/schema.sqlite.js +2 -16
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +4 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +7 -7
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +6 -6
- package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +4 -4
- package/dist/identity/oidc/AutoDetectOidcHandler.js +6 -6
- package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
- package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +6 -6
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +37 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +211 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +158 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/main.js +8 -2
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.d.ts +3 -4
- package/dist/provision/ProvisionPodCreator.js +8 -13
- package/dist/provision/ProvisionPodCreator.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.jsonld +7 -7
- package/dist/runtime/Proxy.d.ts +0 -1
- package/dist/runtime/Proxy.js +0 -9
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts +1 -0
- package/dist/runtime/bootstrap.js +5 -2
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/runtime/css-process.d.ts +12 -4
- package/dist/runtime/css-process.js +61 -14
- package/dist/runtime/css-process.js.map +1 -1
- package/dist/runtime/oidc-issuer.d.ts +3 -2
- package/dist/runtime/oidc-issuer.js +3 -2
- package/dist/runtime/oidc-issuer.js.map +1 -1
- package/dist/runtime/runtime-types.d.ts +1 -0
- package/dist/runtime/runtime-types.js.map +1 -1
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.d.ts +21 -0
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.js +38 -0
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.js.map +1 -0
- package/dist/solidfs/LocalSolidFS.d.ts +18 -0
- package/dist/solidfs/LocalSolidFS.js +539 -0
- package/dist/solidfs/LocalSolidFS.js.map +1 -0
- package/dist/solidfs/PodSolidFsHttpClient.d.ts +16 -0
- package/dist/solidfs/PodSolidFsHttpClient.js +93 -0
- package/dist/solidfs/PodSolidFsHttpClient.js.map +1 -0
- package/dist/solidfs/PodSolidFsHydrator.d.ts +27 -0
- package/dist/solidfs/PodSolidFsHydrator.js +127 -0
- package/dist/solidfs/PodSolidFsHydrator.js.map +1 -0
- package/dist/solidfs/PodSolidFsSyncer.d.ts +21 -0
- package/dist/solidfs/PodSolidFsSyncer.js +78 -0
- package/dist/solidfs/PodSolidFsSyncer.js.map +1 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.d.ts +22 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.js +131 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.js.map +1 -0
- package/dist/solidfs/index.d.ts +7 -0
- package/dist/solidfs/index.js +24 -0
- package/dist/solidfs/index.js.map +1 -0
- package/dist/solidfs/types.d.ts +131 -0
- package/dist/solidfs/types.js +19 -0
- package/dist/solidfs/types.js.map +1 -0
- package/dist/storage/RepresentationPartialConvertingStore.js +6 -13
- package/dist/storage/RepresentationPartialConvertingStore.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.d.ts +4 -0
- package/dist/storage/SparqlUpdateResourceStore.js +13 -0
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.jsonld +26 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +85 -4
- package/dist/storage/accessors/MixDataAccessor.js +511 -16
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +176 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.d.ts +7 -0
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +72 -4
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.jsonld +24 -0
- package/dist/storage/quint/BaseQuintStore.d.ts +3 -0
- package/dist/storage/quint/BaseQuintStore.js +51 -27
- package/dist/storage/quint/BaseQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.d.ts +1 -0
- package/dist/storage/quint/PgQuintStore.js +50 -32
- package/dist/storage/quint/PgQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.jsonld +4 -3
- package/dist/storage/quint/SqliteQuintStore.d.ts +5 -0
- package/dist/storage/quint/SqliteQuintStore.js +100 -0
- package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
- package/dist/storage/quint/SqliteQuintStore.jsonld +20 -0
- package/dist/storage/quint/types.d.ts +16 -0
- package/dist/storage/quint/types.js.map +1 -1
- package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +55 -0
- package/dist/storage/rdf/Rdf3xTripleIndex.js +1235 -0
- package/dist/storage/rdf/Rdf3xTripleIndex.js.map +1 -0
- package/dist/storage/rdf/RdfContentTypes.d.ts +9 -0
- package/dist/storage/rdf/RdfContentTypes.js +79 -0
- package/dist/storage/rdf/RdfContentTypes.js.map +1 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +76 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.js +2636 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -0
- package/dist/storage/rdf/RdfQuadIndex.d.ts +98 -0
- package/dist/storage/rdf/RdfQuadIndex.js +1840 -0
- package/dist/storage/rdf/RdfQuadIndex.js.map +1 -0
- package/dist/storage/rdf/RdfQuadIndex.jsonld +416 -0
- package/dist/storage/rdf/RdfShadowComparator.d.ts +12 -0
- package/dist/storage/rdf/RdfShadowComparator.js +47 -0
- package/dist/storage/rdf/RdfShadowComparator.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.d.ts +147 -0
- package/dist/storage/rdf/RdfSparqlAdapter.js +2420 -0
- package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.jsonld +414 -0
- package/dist/storage/rdf/RdfTermDictionary.d.ts +27 -0
- package/dist/storage/rdf/RdfTermDictionary.js +352 -0
- package/dist/storage/rdf/RdfTermDictionary.js.map +1 -0
- package/dist/storage/rdf/RdfTermDictionary.jsonld +114 -0
- package/dist/storage/rdf/RdfTermSemantics.d.ts +6 -0
- package/dist/storage/rdf/RdfTermSemantics.js +40 -0
- package/dist/storage/rdf/RdfTermSemantics.js.map +1 -0
- package/dist/storage/rdf/RdfTextIndex.d.ts +23 -0
- package/dist/storage/rdf/RdfTextIndex.js +569 -0
- package/dist/storage/rdf/RdfTextIndex.js.map +1 -0
- package/dist/storage/rdf/RdfVectorIndex.d.ts +22 -0
- package/dist/storage/rdf/RdfVectorIndex.js +631 -0
- package/dist/storage/rdf/RdfVectorIndex.js.map +1 -0
- package/dist/storage/rdf/RdfXmlSerializer.d.ts +2 -0
- package/dist/storage/rdf/RdfXmlSerializer.js +123 -0
- package/dist/storage/rdf/RdfXmlSerializer.js.map +1 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.d.ts +58 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.js +202 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.js.map +1 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.jsonld +308 -0
- package/dist/storage/rdf/SolidRdfEngine.d.ts +51 -0
- package/dist/storage/rdf/SolidRdfEngine.js +264 -0
- package/dist/storage/rdf/SolidRdfEngine.js.map +1 -0
- package/dist/storage/rdf/SolidRdfEngine.jsonld +338 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +92 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.js +477 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +257 -0
- package/dist/storage/rdf/index.d.ts +15 -0
- package/dist/storage/rdf/index.js +61 -0
- package/dist/storage/rdf/index.js.map +1 -0
- package/dist/storage/rdf/models-benchmark.d.ts +260 -0
- package/dist/storage/rdf/models-benchmark.js +1405 -0
- package/dist/storage/rdf/models-benchmark.js.map +1 -0
- package/dist/storage/rdf/types.d.ts +726 -0
- package/dist/storage/rdf/types.js +3 -0
- package/dist/storage/rdf/types.js.map +1 -0
- package/dist/storage/rdf/types.jsonld +316 -0
- package/dist/storage/vector/VectorIndexingListener.d.ts +5 -5
- package/dist/storage/vector/VectorIndexingListener.js +19 -19
- package/dist/storage/vector/VectorIndexingListener.js.map +1 -1
- package/package.json +3 -2
- package/templates/pod/acp/.acr.hbs +39 -0
- package/templates/pod/acp/README.acr +18 -0
- package/templates/pod/acp/profile/card.acr +22 -0
- package/templates/pod/base/README$.md.hbs +27 -0
- package/templates/pod/base/profile/card$.ttl.hbs +13 -0
- package/templates/pod/wac/.acl.hbs +26 -0
- package/templates/pod/wac/README.acl.hbs +14 -0
- package/templates/pod/wac/profile/card.acl.hbs +19 -0
- package/dist/api/handlers/WebIdProfileHandler.d.ts +0 -16
- package/dist/api/handlers/WebIdProfileHandler.js +0 -423
- package/dist/api/handlers/WebIdProfileHandler.js.map +0 -1
- package/dist/identity/drizzle/WebIdProfileRepository.d.ts +0 -63
- package/dist/identity/drizzle/WebIdProfileRepository.js +0 -168
- package/dist/identity/drizzle/WebIdProfileRepository.js.map +0 -1
- package/dist/identity/drizzle/WebIdProfileRepository.jsonld +0 -112
- package/dist/storage/quint/BaseQuintStore.jsonld +0 -257
|
@@ -0,0 +1,1235 @@
|
|
|
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
|