@undefineds.co/xpod 0.3.6 → 0.3.15

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 (249) hide show
  1. package/config/cli.json +1 -1
  2. package/config/cloud.json +54 -22
  3. package/config/local.json +56 -12
  4. package/config/resolver.json +10 -2
  5. package/config/xpod.base.json +50 -0
  6. package/config/xpod.json +8 -8
  7. package/dist/agents/config/resolve.js +10 -10
  8. package/dist/agents/config/resolve.js.map +1 -1
  9. package/dist/api/chatkit/index.d.ts +1 -1
  10. package/dist/api/chatkit/index.js.map +1 -1
  11. package/dist/api/chatkit/pod-store.d.ts +14 -11
  12. package/dist/api/chatkit/pod-store.js +114 -78
  13. package/dist/api/chatkit/pod-store.js.map +1 -1
  14. package/dist/api/chatkit/runtime/AcpAgentRuntime.js +1 -1
  15. package/dist/api/chatkit/runtime/AcpAgentRuntime.js.map +1 -1
  16. package/dist/api/chatkit/service.js +1 -1
  17. package/dist/api/chatkit/service.js.map +1 -1
  18. package/dist/api/chatkit/types.d.ts +11 -11
  19. package/dist/api/chatkit/types.js +3 -3
  20. package/dist/api/chatkit/types.js.map +1 -1
  21. package/dist/api/container/cloud.js +0 -8
  22. package/dist/api/container/cloud.js.map +1 -1
  23. package/dist/api/container/index.js +2 -1
  24. package/dist/api/container/index.js.map +1 -1
  25. package/dist/api/container/local.js +0 -7
  26. package/dist/api/container/local.js.map +1 -1
  27. package/dist/api/container/routes.js +3 -17
  28. package/dist/api/container/routes.js.map +1 -1
  29. package/dist/api/container/types.d.ts +0 -2
  30. package/dist/api/container/types.js.map +1 -1
  31. package/dist/api/handlers/PodManagementHandler.d.ts +3 -0
  32. package/dist/api/handlers/PodManagementHandler.js +71 -1
  33. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  34. package/dist/api/handlers/RunHandler.js +5 -5
  35. package/dist/api/handlers/RunHandler.js.map +1 -1
  36. package/dist/api/runs/AgentRuntimeTypes.d.ts +7 -8
  37. package/dist/api/runs/AgentRuntimeTypes.js.map +1 -1
  38. package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
  39. package/dist/api/runs/ManagedRunWorker.d.ts +1 -1
  40. package/dist/api/runs/ManagedRunWorker.js +6 -6
  41. package/dist/api/runs/ManagedRunWorker.js.map +1 -1
  42. package/dist/api/runs/PiAgentRuntimeDriver.d.ts +16 -1
  43. package/dist/api/runs/PiAgentRuntimeDriver.js +182 -23
  44. package/dist/api/runs/PiAgentRuntimeDriver.js.map +1 -1
  45. package/dist/api/runs/RunStateCenter.d.ts +3 -3
  46. package/dist/api/runs/RunStateCenter.js +13 -13
  47. package/dist/api/runs/RunStateCenter.js.map +1 -1
  48. package/dist/api/runs/store.d.ts +4 -4
  49. package/dist/api/runs/store.js +2 -2
  50. package/dist/api/runs/store.js.map +1 -1
  51. package/dist/api/service/VectorStoreService.d.ts +1 -1
  52. package/dist/api/service/VectorStoreService.js +16 -16
  53. package/dist/api/service/VectorStoreService.js.map +1 -1
  54. package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
  55. package/dist/api/tasks/TaskMaterializer.d.ts +3 -3
  56. package/dist/api/tasks/TaskMaterializer.js +11 -11
  57. package/dist/api/tasks/TaskMaterializer.js.map +1 -1
  58. package/dist/api/tasks/TaskService.d.ts +3 -3
  59. package/dist/api/tasks/TaskService.js +11 -7
  60. package/dist/api/tasks/TaskService.js.map +1 -1
  61. package/dist/api/tasks/store.d.ts +10 -4
  62. package/dist/api/tasks/store.js +14 -4
  63. package/dist/api/tasks/store.js.map +1 -1
  64. package/dist/api/workspace/types.d.ts +3 -3
  65. package/dist/api/workspace/types.js +6 -6
  66. package/dist/api/workspace/types.js.map +1 -1
  67. package/dist/cli/commands/config.js +2 -2
  68. package/dist/cli/commands/config.js.map +1 -1
  69. package/dist/cli/commands/start.js +9 -3
  70. package/dist/cli/commands/start.js.map +1 -1
  71. package/dist/components/components.jsonld +8 -2
  72. package/dist/components/context.jsonld +308 -51
  73. package/dist/http/search/SearchHttpHandler.js +8 -8
  74. package/dist/http/search/SearchHttpHandler.js.map +1 -1
  75. package/dist/identity/drizzle/PodLookupRepository.d.ts +11 -1
  76. package/dist/identity/drizzle/PodLookupRepository.js +95 -4
  77. package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
  78. package/dist/identity/drizzle/db.js +4 -43
  79. package/dist/identity/drizzle/db.js.map +1 -1
  80. package/dist/identity/drizzle/schema.pg.d.ts +0 -5
  81. package/dist/identity/drizzle/schema.pg.js +2 -16
  82. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  83. package/dist/identity/drizzle/schema.sqlite.d.ts +19 -176
  84. package/dist/identity/drizzle/schema.sqlite.js +2 -16
  85. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  86. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +4 -4
  87. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +7 -7
  88. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  89. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +6 -6
  90. package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +4 -4
  91. package/dist/identity/oidc/AutoDetectOidcHandler.js +6 -6
  92. package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
  93. package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +6 -6
  94. package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +37 -0
  95. package/dist/identity/oidc/ScopedPickWebIdHandler.js +211 -0
  96. package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -0
  97. package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +158 -0
  98. package/dist/index.d.ts +12 -2
  99. package/dist/index.js +16 -4
  100. package/dist/index.js.map +1 -1
  101. package/dist/main.js +8 -2
  102. package/dist/main.js.map +1 -1
  103. package/dist/provision/ProvisionPodCreator.d.ts +3 -4
  104. package/dist/provision/ProvisionPodCreator.js +8 -13
  105. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  106. package/dist/provision/ProvisionPodCreator.jsonld +7 -7
  107. package/dist/runtime/Proxy.d.ts +0 -1
  108. package/dist/runtime/Proxy.js +0 -9
  109. package/dist/runtime/Proxy.js.map +1 -1
  110. package/dist/runtime/bootstrap.d.ts +1 -0
  111. package/dist/runtime/bootstrap.js +5 -2
  112. package/dist/runtime/bootstrap.js.map +1 -1
  113. package/dist/runtime/css-process.d.ts +12 -4
  114. package/dist/runtime/css-process.js +61 -14
  115. package/dist/runtime/css-process.js.map +1 -1
  116. package/dist/runtime/oidc-issuer.d.ts +3 -2
  117. package/dist/runtime/oidc-issuer.js +3 -2
  118. package/dist/runtime/oidc-issuer.js.map +1 -1
  119. package/dist/runtime/runtime-types.d.ts +1 -0
  120. package/dist/runtime/runtime-types.js.map +1 -1
  121. package/dist/solidfs/LocalFirstRdfRepresentationResolver.d.ts +21 -0
  122. package/dist/solidfs/LocalFirstRdfRepresentationResolver.js +38 -0
  123. package/dist/solidfs/LocalFirstRdfRepresentationResolver.js.map +1 -0
  124. package/dist/solidfs/LocalSolidFS.d.ts +18 -0
  125. package/dist/solidfs/LocalSolidFS.js +539 -0
  126. package/dist/solidfs/LocalSolidFS.js.map +1 -0
  127. package/dist/solidfs/PodSolidFsHttpClient.d.ts +16 -0
  128. package/dist/solidfs/PodSolidFsHttpClient.js +93 -0
  129. package/dist/solidfs/PodSolidFsHttpClient.js.map +1 -0
  130. package/dist/solidfs/PodSolidFsHydrator.d.ts +27 -0
  131. package/dist/solidfs/PodSolidFsHydrator.js +127 -0
  132. package/dist/solidfs/PodSolidFsHydrator.js.map +1 -0
  133. package/dist/solidfs/PodSolidFsSyncer.d.ts +21 -0
  134. package/dist/solidfs/PodSolidFsSyncer.js +78 -0
  135. package/dist/solidfs/PodSolidFsSyncer.js.map +1 -0
  136. package/dist/solidfs/RdfIndexSolidFsSyncer.d.ts +22 -0
  137. package/dist/solidfs/RdfIndexSolidFsSyncer.js +131 -0
  138. package/dist/solidfs/RdfIndexSolidFsSyncer.js.map +1 -0
  139. package/dist/solidfs/index.d.ts +7 -0
  140. package/dist/solidfs/index.js +24 -0
  141. package/dist/solidfs/index.js.map +1 -0
  142. package/dist/solidfs/types.d.ts +131 -0
  143. package/dist/solidfs/types.js +19 -0
  144. package/dist/solidfs/types.js.map +1 -0
  145. package/dist/storage/RepresentationPartialConvertingStore.js +6 -13
  146. package/dist/storage/RepresentationPartialConvertingStore.js.map +1 -1
  147. package/dist/storage/SparqlUpdateResourceStore.d.ts +4 -0
  148. package/dist/storage/SparqlUpdateResourceStore.js +13 -0
  149. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  150. package/dist/storage/SparqlUpdateResourceStore.jsonld +26 -0
  151. package/dist/storage/accessors/MixDataAccessor.d.ts +85 -4
  152. package/dist/storage/accessors/MixDataAccessor.js +511 -16
  153. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  154. package/dist/storage/accessors/MixDataAccessor.jsonld +176 -1
  155. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.d.ts +7 -0
  156. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +72 -4
  157. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
  158. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.jsonld +24 -0
  159. package/dist/storage/quint/BaseQuintStore.d.ts +3 -0
  160. package/dist/storage/quint/BaseQuintStore.js +51 -27
  161. package/dist/storage/quint/BaseQuintStore.js.map +1 -1
  162. package/dist/storage/quint/PgQuintStore.d.ts +1 -0
  163. package/dist/storage/quint/PgQuintStore.js +50 -32
  164. package/dist/storage/quint/PgQuintStore.js.map +1 -1
  165. package/dist/storage/quint/PgQuintStore.jsonld +4 -3
  166. package/dist/storage/quint/SqliteQuintStore.d.ts +5 -0
  167. package/dist/storage/quint/SqliteQuintStore.js +100 -0
  168. package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
  169. package/dist/storage/quint/SqliteQuintStore.jsonld +20 -0
  170. package/dist/storage/quint/types.d.ts +16 -0
  171. package/dist/storage/quint/types.js.map +1 -1
  172. package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +55 -0
  173. package/dist/storage/rdf/Rdf3xTripleIndex.js +1235 -0
  174. package/dist/storage/rdf/Rdf3xTripleIndex.js.map +1 -0
  175. package/dist/storage/rdf/RdfContentTypes.d.ts +9 -0
  176. package/dist/storage/rdf/RdfContentTypes.js +79 -0
  177. package/dist/storage/rdf/RdfContentTypes.js.map +1 -0
  178. package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +79 -0
  179. package/dist/storage/rdf/RdfLocalQueryEngine.js +2705 -0
  180. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -0
  181. package/dist/storage/rdf/RdfQuadIndex.d.ts +98 -0
  182. package/dist/storage/rdf/RdfQuadIndex.js +1840 -0
  183. package/dist/storage/rdf/RdfQuadIndex.js.map +1 -0
  184. package/dist/storage/rdf/RdfQuadIndex.jsonld +416 -0
  185. package/dist/storage/rdf/RdfShadowComparator.d.ts +12 -0
  186. package/dist/storage/rdf/RdfShadowComparator.js +47 -0
  187. package/dist/storage/rdf/RdfShadowComparator.js.map +1 -0
  188. package/dist/storage/rdf/RdfSparqlAdapter.d.ts +147 -0
  189. package/dist/storage/rdf/RdfSparqlAdapter.js +2420 -0
  190. package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -0
  191. package/dist/storage/rdf/RdfSparqlAdapter.jsonld +414 -0
  192. package/dist/storage/rdf/RdfTermDictionary.d.ts +27 -0
  193. package/dist/storage/rdf/RdfTermDictionary.js +352 -0
  194. package/dist/storage/rdf/RdfTermDictionary.js.map +1 -0
  195. package/dist/storage/rdf/RdfTermDictionary.jsonld +114 -0
  196. package/dist/storage/rdf/RdfTermSemantics.d.ts +6 -0
  197. package/dist/storage/rdf/RdfTermSemantics.js +40 -0
  198. package/dist/storage/rdf/RdfTermSemantics.js.map +1 -0
  199. package/dist/storage/rdf/RdfTextIndex.d.ts +23 -0
  200. package/dist/storage/rdf/RdfTextIndex.js +569 -0
  201. package/dist/storage/rdf/RdfTextIndex.js.map +1 -0
  202. package/dist/storage/rdf/RdfVectorIndex.d.ts +22 -0
  203. package/dist/storage/rdf/RdfVectorIndex.js +631 -0
  204. package/dist/storage/rdf/RdfVectorIndex.js.map +1 -0
  205. package/dist/storage/rdf/RdfXmlSerializer.d.ts +2 -0
  206. package/dist/storage/rdf/RdfXmlSerializer.js +123 -0
  207. package/dist/storage/rdf/RdfXmlSerializer.js.map +1 -0
  208. package/dist/storage/rdf/ShadowRdfQuintStore.d.ts +58 -0
  209. package/dist/storage/rdf/ShadowRdfQuintStore.js +202 -0
  210. package/dist/storage/rdf/ShadowRdfQuintStore.js.map +1 -0
  211. package/dist/storage/rdf/ShadowRdfQuintStore.jsonld +308 -0
  212. package/dist/storage/rdf/SolidRdfEngine.d.ts +56 -0
  213. package/dist/storage/rdf/SolidRdfEngine.js +292 -0
  214. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -0
  215. package/dist/storage/rdf/SolidRdfEngine.jsonld +372 -0
  216. package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +92 -0
  217. package/dist/storage/rdf/SolidRdfSparqlEngine.js +477 -0
  218. package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -0
  219. package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +257 -0
  220. package/dist/storage/rdf/index.d.ts +15 -0
  221. package/dist/storage/rdf/index.js +61 -0
  222. package/dist/storage/rdf/index.js.map +1 -0
  223. package/dist/storage/rdf/models-benchmark.d.ts +260 -0
  224. package/dist/storage/rdf/models-benchmark.js +1405 -0
  225. package/dist/storage/rdf/models-benchmark.js.map +1 -0
  226. package/dist/storage/rdf/types.d.ts +726 -0
  227. package/dist/storage/rdf/types.js +3 -0
  228. package/dist/storage/rdf/types.js.map +1 -0
  229. package/dist/storage/rdf/types.jsonld +316 -0
  230. package/dist/storage/vector/VectorIndexingListener.d.ts +5 -5
  231. package/dist/storage/vector/VectorIndexingListener.js +19 -19
  232. package/dist/storage/vector/VectorIndexingListener.js.map +1 -1
  233. package/package.json +3 -2
  234. package/templates/pod/acp/.acr.hbs +39 -0
  235. package/templates/pod/acp/README.acr +18 -0
  236. package/templates/pod/acp/profile/card.acr +22 -0
  237. package/templates/pod/base/README$.md.hbs +27 -0
  238. package/templates/pod/base/profile/card$.ttl.hbs +13 -0
  239. package/templates/pod/wac/.acl.hbs +26 -0
  240. package/templates/pod/wac/README.acl.hbs +14 -0
  241. package/templates/pod/wac/profile/card.acl.hbs +19 -0
  242. package/dist/api/handlers/WebIdProfileHandler.d.ts +0 -16
  243. package/dist/api/handlers/WebIdProfileHandler.js +0 -423
  244. package/dist/api/handlers/WebIdProfileHandler.js.map +0 -1
  245. package/dist/identity/drizzle/WebIdProfileRepository.d.ts +0 -63
  246. package/dist/identity/drizzle/WebIdProfileRepository.js +0 -168
  247. package/dist/identity/drizzle/WebIdProfileRepository.js.map +0 -1
  248. package/dist/identity/drizzle/WebIdProfileRepository.jsonld +0 -112
  249. package/dist/storage/quint/BaseQuintStore.jsonld +0 -257
@@ -0,0 +1,1840 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RdfQuadIndex = 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 types_1 = require("../quint/types");
9
+ const RdfTermDictionary_1 = require("./RdfTermDictionary");
10
+ const RdfTermSemantics_1 = require("./RdfTermSemantics");
11
+ const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
12
+ const XSD_DECIMAL = 'http://www.w3.org/2001/XMLSchema#decimal';
13
+ const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
14
+ const TERM_COLUMN = {
15
+ graph: 'graph_id',
16
+ subject: 'subject_id',
17
+ predicate: 'predicate_id',
18
+ object: 'object_id',
19
+ };
20
+ const TERM_KEYS = ['graph', 'subject', 'predicate', 'object'];
21
+ const TERM_IN_JOIN_THRESHOLD = 64;
22
+ class RdfQuadIndex {
23
+ constructor(options) {
24
+ this.options = options;
25
+ this.sqliteRuntime = (0, SqliteRuntime_1.createSqliteRuntime)();
26
+ this.db = null;
27
+ this.dictionary = null;
28
+ this.cardinalityCache = new Map();
29
+ }
30
+ open() {
31
+ if (this.db) {
32
+ return;
33
+ }
34
+ if (this.options.path !== ':memory:') {
35
+ const dir = (0, node_path_1.dirname)(this.options.path);
36
+ if (!(0, node_fs_1.existsSync)(dir)) {
37
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
38
+ }
39
+ }
40
+ this.db = this.sqliteRuntime.openDatabase(this.options.path);
41
+ this.dictionary = new RdfTermDictionary_1.RdfTermDictionary(this.db);
42
+ this.dictionary.initialize();
43
+ this.initializeSchema();
44
+ }
45
+ close() {
46
+ this.db?.close();
47
+ this.db = null;
48
+ this.dictionary = null;
49
+ }
50
+ clear() {
51
+ const db = this.requireDb();
52
+ db.exec('DELETE FROM rdf_quads; DELETE FROM rdf_sources; DELETE FROM rdf_terms;');
53
+ this.cardinalityCache.clear();
54
+ }
55
+ put(quad, options) {
56
+ this.multiPut([quad], options);
57
+ }
58
+ replaceSource(quads, source) {
59
+ const db = this.requireDb();
60
+ db.transaction(() => {
61
+ this.deleteSource(source.source);
62
+ if (quads.length > 0) {
63
+ this.insertQuads(quads, { source });
64
+ }
65
+ else {
66
+ this.upsertSource(source);
67
+ }
68
+ })();
69
+ this.cardinalityCache.clear();
70
+ }
71
+ deleteSource(source) {
72
+ const db = this.requireDb();
73
+ const row = db
74
+ .prepare('SELECT id FROM rdf_sources WHERE source = ?')
75
+ .get(source);
76
+ if (!row) {
77
+ return 0;
78
+ }
79
+ const result = db.prepare('DELETE FROM rdf_quads WHERE source_file_id = ?').run(row.id);
80
+ db.prepare('DELETE FROM rdf_sources WHERE id = ?').run(row.id);
81
+ if (result.changes > 0) {
82
+ this.cardinalityCache.clear();
83
+ }
84
+ return result.changes;
85
+ }
86
+ multiPut(quads, options) {
87
+ if (quads.length === 0) {
88
+ return;
89
+ }
90
+ const db = this.requireDb();
91
+ db.transaction(() => {
92
+ this.insertQuads(quads, options);
93
+ })();
94
+ this.cardinalityCache.clear();
95
+ }
96
+ insertQuads(quads, options) {
97
+ const db = this.requireDb();
98
+ const dictionary = this.requireDictionary();
99
+ const sourceId = options?.source ? this.upsertSource(options.source) : null;
100
+ const insert = db.prepare(`
101
+ INSERT INTO rdf_quads (
102
+ graph_id,
103
+ subject_id,
104
+ predicate_id,
105
+ object_id,
106
+ source_file_id,
107
+ source_line_no
108
+ )
109
+ VALUES (?, ?, ?, ?, ?, ?)
110
+ ON CONFLICT (graph_id, subject_id, predicate_id, object_id)
111
+ DO UPDATE SET
112
+ source_file_id = excluded.source_file_id,
113
+ source_line_no = excluded.source_line_no
114
+ `);
115
+ for (const quad of quads) {
116
+ insert.run(dictionary.getOrCreate(quad.graph), dictionary.getOrCreate(quad.subject), dictionary.getOrCreate(quad.predicate), dictionary.getOrCreate(quad.object), sourceId, options?.sourceLineNo ?? null);
117
+ }
118
+ }
119
+ delete(pattern) {
120
+ const db = this.requireDb();
121
+ const { joins, whereClause, params } = this.buildWhereClause(pattern, false);
122
+ if (!whereClause) {
123
+ const result = db.prepare('DELETE FROM rdf_quads').run();
124
+ this.cardinalityCache.clear();
125
+ return result.changes;
126
+ }
127
+ const sql = joins
128
+ ? `DELETE FROM rdf_quads WHERE rowid IN (SELECT rdf_quads.rowid FROM rdf_quads${joins}${whereClause})`
129
+ : `DELETE FROM rdf_quads${whereClause}`;
130
+ const changes = db.prepare(sql).run(...params).changes;
131
+ if (changes > 0) {
132
+ this.cardinalityCache.clear();
133
+ }
134
+ return changes;
135
+ }
136
+ scan(pattern, options) {
137
+ return this.scanInternal(pattern, options);
138
+ }
139
+ scanWithTupleConstraints(pattern, tupleSource, options) {
140
+ return this.scanInternal(pattern, options, tupleSource);
141
+ }
142
+ joinPatterns(patterns, options) {
143
+ const start = Date.now();
144
+ if (patterns.length === 0) {
145
+ return {
146
+ bindings: [],
147
+ metrics: this.metrics('none', 0, 0, start, ['JoinBGP(empty)']),
148
+ };
149
+ }
150
+ const compiled = this.compileJoinPatterns(patterns, options);
151
+ if (compiled.unresolved) {
152
+ return {
153
+ bindings: [],
154
+ metrics: this.metrics('none', 0, 0, start, [...compiled.queryPlan, `unresolved ${compiled.unresolved}`]),
155
+ };
156
+ }
157
+ const rows = this.requireDb().prepare(compiled.sql).all(...compiled.params);
158
+ const matchedRows = compiled.countSql
159
+ ? this.requireDb().prepare(compiled.countSql).get(...compiled.countParams)?.count ?? 0
160
+ : rows.length;
161
+ return {
162
+ bindings: this.joinRowsToBindings(rows, compiled.variableAliases),
163
+ metrics: this.metrics(compiled.indexChoice, matchedRows, rows.length, start, [...compiled.queryPlan, compiled.sql]),
164
+ };
165
+ }
166
+ countJoinPatterns(patterns, options) {
167
+ return this.aggregateJoinPatternsInternal(patterns, options, 'JoinCount');
168
+ }
169
+ aggregateJoinPatterns(patterns, options) {
170
+ return this.aggregateJoinPatternsInternal(patterns, options, 'JoinAggregate');
171
+ }
172
+ aggregateJoinPatternsInternal(patterns, options, label) {
173
+ const start = Date.now();
174
+ if (patterns.length === 0) {
175
+ return {
176
+ bindings: [],
177
+ metrics: this.metrics('none', 0, 0, start, [`${label}(empty)`]),
178
+ };
179
+ }
180
+ const compiled = this.compileJoinPatterns(patterns);
181
+ if (compiled.unresolved) {
182
+ return {
183
+ bindings: [],
184
+ metrics: this.metrics('none', 0, 0, start, [...compiled.queryPlan, `unresolved ${compiled.unresolved}`]),
185
+ };
186
+ }
187
+ const aggregateAliases = new Map();
188
+ const aggregateTypes = new Map();
189
+ const numericJoins = new Map();
190
+ const numericJoinSql = [];
191
+ const projection = options.aggregates.map((aggregate, index) => {
192
+ const alias = `a${index}`;
193
+ aggregateAliases.set(aggregate.as, alias);
194
+ return this.buildJoinAggregateColumn(aggregate, alias, compiled.variableColumns, aggregateTypes, numericJoins, numericJoinSql, 'RDF BGP', this.joinRowKeyExpression(patterns));
195
+ }).join(', ');
196
+ const aggregateJoins = numericJoinSql.join('');
197
+ const sql = `SELECT ${projection} FROM ${compiled.from}${compiled.joins}${aggregateJoins}${compiled.whereClause}`;
198
+ const rows = this.requireDb().prepare(sql).all(...compiled.params);
199
+ const matchedRows = this.requireDb()
200
+ .prepare(`SELECT COUNT(*) AS count FROM ${compiled.from}${compiled.joins}${aggregateJoins}${compiled.whereClause}`)
201
+ .get(...compiled.params)?.count ?? 0;
202
+ return {
203
+ bindings: this.joinRowsToBindings(rows, compiled.variableAliases, aggregateAliases, aggregateTypes),
204
+ metrics: this.metrics(compiled.indexChoice, matchedRows, rows.length, start, [
205
+ ...compiled.queryPlan,
206
+ ...(numericJoinSql.length > 0 ? [`JoinAggregateNumeric(${[...numericJoins.keys()].map((variableName) => `?${variableName}`).join(',')})`] : []),
207
+ `${label}(${options.aggregates.map((aggregate) => (`${aggregate.type}${aggregate.distinct ? ':DISTINCT' : ''}(${aggregate.variable ? `?${aggregate.variable}` : '*'})`)).join(',')})`,
208
+ sql,
209
+ ]),
210
+ };
211
+ }
212
+ groupCountJoinPatterns(patterns, options) {
213
+ return this.groupAggregateJoinPatternsInternal(patterns, options, 'JoinGroupCount');
214
+ }
215
+ groupAggregateJoinPatterns(patterns, options) {
216
+ return this.groupAggregateJoinPatternsInternal(patterns, options, 'JoinGroupAggregate');
217
+ }
218
+ groupAggregateJoinPatternsInternal(patterns, options, label) {
219
+ const start = Date.now();
220
+ if (patterns.length === 0) {
221
+ return {
222
+ bindings: [],
223
+ metrics: this.metrics('none', 0, 0, start, [`${label}(empty)`]),
224
+ };
225
+ }
226
+ const compiled = this.compileJoinPatterns(patterns);
227
+ if (compiled.unresolved) {
228
+ return {
229
+ bindings: [],
230
+ metrics: this.metrics('none', 0, 0, start, [...compiled.queryPlan, `unresolved ${compiled.unresolved}`]),
231
+ };
232
+ }
233
+ const aggregateAliases = new Map();
234
+ const aggregateSqlAliases = new Map();
235
+ const aggregateTypes = new Map();
236
+ const numericJoins = new Map();
237
+ const numericJoinSql = [];
238
+ const groupColumns = options.groupBy.map((variableName) => {
239
+ const column = compiled.variableColumns.get(variableName);
240
+ if (!column) {
241
+ throw new Error(`RDF BGP group aggregate cannot group by unbound variable: ${variableName}`);
242
+ }
243
+ return column;
244
+ });
245
+ const aggregateColumns = options.aggregates.map((aggregate, index) => {
246
+ const alias = `a${index}`;
247
+ aggregateAliases.set(aggregate.as, alias);
248
+ aggregateSqlAliases.set(aggregate.as, alias);
249
+ return this.buildJoinAggregateColumn(aggregate, alias, compiled.variableColumns, aggregateTypes, numericJoins, numericJoinSql, 'RDF BGP group aggregate', '__row_key');
250
+ });
251
+ const projection = [
252
+ ...options.groupBy.map((variableName) => {
253
+ const alias = compiled.variableAliases.get(variableName);
254
+ const column = compiled.variableColumns.get(variableName);
255
+ if (!alias || !column) {
256
+ throw new Error(`RDF BGP group aggregate cannot project unbound group variable: ${variableName}`);
257
+ }
258
+ return `${column} AS ${alias}`;
259
+ }),
260
+ ...aggregateColumns,
261
+ ].join(', ');
262
+ const groupBy = groupColumns.join(', ');
263
+ const rowKeyExpression = this.joinRowKeyExpression(patterns);
264
+ const aggregateJoins = numericJoinSql.join('');
265
+ const havingClause = this.buildGroupAggregateHavingClause(options.having, aggregateSqlAliases);
266
+ const orderScope = this.buildGroupAggregateOrderScope(options, compiled.variableColumns, aggregateSqlAliases);
267
+ const fromSql = `${compiled.from}${compiled.joins}${aggregateJoins}${compiled.whereClause}`;
268
+ const sourceFromSql = `${compiled.from}${compiled.joins}${aggregateJoins}${orderScope.joins}${compiled.whereClause}`;
269
+ const sourceSql = aggregateColumns.some((entry) => entry.includes('__row_key'))
270
+ ? `SELECT ${projection.replace(/__row_key/g, rowKeyExpression)} FROM ${sourceFromSql} GROUP BY ${groupBy}${havingClause.sql}`
271
+ : `SELECT ${projection} FROM ${sourceFromSql} GROUP BY ${groupBy}${havingClause.sql}`;
272
+ const orderClause = orderScope.orderBy;
273
+ let sql = `${sourceSql}${orderClause}`;
274
+ const params = [...compiled.params, ...havingClause.params];
275
+ const paginated = options.limit !== undefined || options.offset !== undefined;
276
+ if (options.limit !== undefined) {
277
+ sql += ' LIMIT ?';
278
+ params.push(options.limit);
279
+ }
280
+ if (options.offset !== undefined) {
281
+ if (options.limit === undefined) {
282
+ sql += ' LIMIT -1';
283
+ }
284
+ sql += ' OFFSET ?';
285
+ params.push(options.offset);
286
+ }
287
+ const rows = this.requireDb().prepare(sql).all(...params);
288
+ const matchedRows = this.requireDb()
289
+ .prepare(`SELECT COUNT(*) AS count FROM ${fromSql}`)
290
+ .get(...compiled.params)?.count ?? 0;
291
+ return {
292
+ bindings: this.joinRowsToBindings(rows, compiled.variableAliases, aggregateAliases, aggregateTypes),
293
+ metrics: this.metrics(compiled.indexChoice, matchedRows, rows.length, start, [
294
+ ...compiled.queryPlan,
295
+ ...(numericJoinSql.length > 0 ? [`JoinGroupAggregateNumeric(${[...numericJoins.keys()].map((variableName) => `?${variableName}`).join(',')})`] : []),
296
+ `${label}(${options.groupBy.map((variableName) => `?${variableName}`).join(',')})`,
297
+ ...(havingClause.sql ? [`${label}Having(${(options.having ?? []).map((entry) => `${entry.aggregate}${entry.operator}`).join(',')})`] : []),
298
+ ...(orderClause ? [`${label}Order(${(options.orderBy ?? []).map((entry) => `${entry.direction ?? 'asc'}:${entry.variable}`).join(',')})`] : []),
299
+ ...(paginated ? [`${label}Limit`] : []),
300
+ sql,
301
+ ]),
302
+ };
303
+ }
304
+ scanInternal(pattern, options, tupleSource) {
305
+ const start = Date.now();
306
+ const { joins, whereClause, params, indexHint, queryPlan, unresolved } = this.buildWhereClause(pattern, true);
307
+ if (unresolved) {
308
+ return {
309
+ quads: [],
310
+ metrics: this.metrics(indexHint, 0, 0, start, [...queryPlan, `unresolved ${unresolved}`]),
311
+ };
312
+ }
313
+ const orderClause = this.buildOrderClause(options);
314
+ const tupleJoin = tupleSource ? this.buildTupleConstraintJoin(tupleSource) : undefined;
315
+ let sql = `SELECT rdf_quads.graph_id, rdf_quads.subject_id, rdf_quads.predicate_id, rdf_quads.object_id, rdf_quads.source_file_id, rdf_quads.source_line_no FROM rdf_quads${joins}${tupleJoin?.join ?? ''}${orderClause.joins}`;
316
+ if (whereClause) {
317
+ sql += whereClause;
318
+ }
319
+ if (orderClause.orderBy) {
320
+ sql += orderClause.orderBy;
321
+ }
322
+ if (options?.limit !== undefined) {
323
+ sql += ' LIMIT ?';
324
+ params.push(options.limit);
325
+ }
326
+ if (options?.offset !== undefined) {
327
+ if (options.limit === undefined) {
328
+ sql += ' LIMIT -1';
329
+ }
330
+ sql += ' OFFSET ?';
331
+ params.push(options.offset);
332
+ }
333
+ const countSql = `SELECT COUNT(*) AS count FROM rdf_quads${joins}${tupleJoin?.join ?? ''}${orderClause.joins}${whereClause}`;
334
+ const countParams = [...params.slice(0, params.length - this.paginationParamCount(options))];
335
+ const countRow = this.requireDb().prepare(countSql).get(...countParams);
336
+ const matchedRows = countRow?.count ?? 0;
337
+ const rows = this.requireDb().prepare(sql).all(...params);
338
+ return {
339
+ quads: this.rowsToQuads(rows),
340
+ metrics: this.metrics(indexHint, matchedRows, rows.length, start, [
341
+ ...queryPlan,
342
+ ...(tupleJoin ? [`TupleValuesJoin(${tupleSource?.columns.join(',')})`] : []),
343
+ sql,
344
+ ]),
345
+ };
346
+ }
347
+ compileJoinPatterns(patterns, options) {
348
+ const from = 'rdf_quads q0';
349
+ const joins = [];
350
+ const conditions = [];
351
+ const params = [];
352
+ const queryPlan = [`JoinBGP(${patterns.length})`];
353
+ const variableColumns = new Map();
354
+ const variableAliases = new Map();
355
+ const indexChoices = [];
356
+ for (const [patternIndex, entry] of patterns.entries()) {
357
+ const alias = `q${patternIndex}`;
358
+ if (patternIndex > 0) {
359
+ joins.push(` JOIN rdf_quads ${alias} ON 1 = 1`);
360
+ }
361
+ const equalityColumns = new Set();
362
+ for (const key of TERM_KEYS) {
363
+ const match = entry.pattern[key];
364
+ const column = `${alias}.${TERM_COLUMN[key]}`;
365
+ const variableName = entry.variables[key];
366
+ if (variableName) {
367
+ const existingColumn = variableColumns.get(variableName);
368
+ if (existingColumn) {
369
+ conditions.push(`${existingColumn} = ${column}`);
370
+ }
371
+ else {
372
+ variableColumns.set(variableName, column);
373
+ }
374
+ }
375
+ if (!match) {
376
+ continue;
377
+ }
378
+ const condition = this.matchToJoinCondition(key, alias, TERM_COLUMN[key], match, true);
379
+ if (condition.unresolved) {
380
+ return {
381
+ from,
382
+ joins: joins.join(''),
383
+ whereClause: '',
384
+ sql: '',
385
+ params: [],
386
+ countParams: [],
387
+ indexChoice: 'none',
388
+ queryPlan,
389
+ variableColumns,
390
+ variableAliases,
391
+ unresolved: key,
392
+ };
393
+ }
394
+ joins.push(...condition.joins);
395
+ if (condition.sql) {
396
+ conditions.push(condition.sql);
397
+ params.push(...condition.params);
398
+ }
399
+ queryPlan.push(...condition.queryPlan);
400
+ if (condition.equality) {
401
+ equalityColumns.add(TERM_COLUMN[key]);
402
+ }
403
+ }
404
+ indexChoices.push(this.chooseIndex(equalityColumns));
405
+ }
406
+ const projectVariables = options?.project ?? [...variableColumns.keys()];
407
+ const projectionColumns = projectVariables.map((variableName) => {
408
+ const column = variableColumns.get(variableName);
409
+ if (!column) {
410
+ throw new Error(`RDF BGP join cannot project unbound variable: ${variableName}`);
411
+ }
412
+ const columnAlias = `v${variableAliases.size}`;
413
+ variableAliases.set(variableName, columnAlias);
414
+ return `${column} AS ${columnAlias}`;
415
+ });
416
+ const projection = projectionColumns.length > 0
417
+ ? `${options?.distinct ? 'DISTINCT ' : ''}${projectionColumns.join(', ')}`
418
+ : `${options?.distinct ? 'DISTINCT ' : ''}1 AS __empty`;
419
+ const orderClause = this.buildJoinOrderClause(options, variableColumns);
420
+ const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
421
+ let sql = `SELECT ${projection} FROM ${from}${joins.join('')}${orderClause.joins}${whereClause}${orderClause.orderBy}`;
422
+ const sqlParams = [...params];
423
+ const paginated = options?.limit !== undefined || options?.offset !== undefined;
424
+ const countMatchedRows = options?.countMatchedRows ?? true;
425
+ if (options?.limit !== undefined) {
426
+ sql += ' LIMIT ?';
427
+ sqlParams.push(options.limit);
428
+ }
429
+ if (options?.offset !== undefined) {
430
+ if (options.limit === undefined) {
431
+ sql += ' LIMIT -1';
432
+ }
433
+ sql += ' OFFSET ?';
434
+ sqlParams.push(options.offset);
435
+ }
436
+ if (orderClause.orderBy) {
437
+ queryPlan.push(`JoinOrder(${(options?.orderBy ?? []).map((entry) => `${entry.direction ?? 'asc'}:${entry.variable}`).join(',')})`);
438
+ }
439
+ if (options?.distinct) {
440
+ queryPlan.push(`JoinDistinct(${projectVariables.map((variableName) => `?${variableName}`).join(',')})`);
441
+ }
442
+ if (paginated) {
443
+ queryPlan.push('JoinLimit');
444
+ }
445
+ return {
446
+ from,
447
+ joins: joins.join(''),
448
+ whereClause,
449
+ sql,
450
+ params: sqlParams,
451
+ countSql: paginated && countMatchedRows ? `SELECT COUNT(*) AS count FROM ${from}${joins.join('')}${whereClause}` : undefined,
452
+ countParams: params,
453
+ indexChoice: `JoinBGP(${indexChoices.join('>')})`,
454
+ queryPlan,
455
+ variableColumns,
456
+ variableAliases,
457
+ };
458
+ }
459
+ buildJoinOrderClause(options, variableColumns) {
460
+ if (!options?.orderBy || options.orderBy.length === 0) {
461
+ return { joins: '', orderBy: '' };
462
+ }
463
+ const joins = options.orderBy.map((entry, index) => {
464
+ const column = variableColumns.get(entry.variable);
465
+ if (!column) {
466
+ throw new Error(`RDF BGP join cannot order by unbound variable: ${entry.variable}`);
467
+ }
468
+ const alias = `join_order_t${index}`;
469
+ return {
470
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${column}`,
471
+ order: `${alias}.value${entry.direction === 'desc' ? ' DESC' : ''}`,
472
+ };
473
+ });
474
+ return {
475
+ joins: joins.map((entry) => entry.join).join(''),
476
+ orderBy: ` ORDER BY ${joins.map((entry) => entry.order).join(', ')}`,
477
+ };
478
+ }
479
+ joinRowKeyExpression(patterns) {
480
+ return patterns.map((_, index) => `q${index}.rowid`).join(` || ':' || `);
481
+ }
482
+ buildJoinAggregateColumn(aggregate, alias, variableColumns, aggregateTypes, numericJoins, numericJoinSql, errorPrefix, rowKeyExpression) {
483
+ if (aggregate.type === 'count' && !aggregate.variable) {
484
+ aggregateTypes.set(aggregate.as, 'integer');
485
+ return `${aggregate.distinct ? `COUNT(DISTINCT ${rowKeyExpression})` : 'COUNT(*)'} AS ${alias}`;
486
+ }
487
+ if (!aggregate.variable) {
488
+ throw new Error(`${errorPrefix} ${aggregate.type} aggregate requires a bound variable`);
489
+ }
490
+ const column = variableColumns.get(aggregate.variable);
491
+ if (!column) {
492
+ throw new Error(`${errorPrefix} aggregate cannot read unbound variable: ${aggregate.variable}`);
493
+ }
494
+ if (aggregate.type === 'count') {
495
+ aggregateTypes.set(aggregate.as, 'integer');
496
+ return `COUNT(${aggregate.distinct ? 'DISTINCT ' : ''}${column}) AS ${alias}`;
497
+ }
498
+ if (aggregate.distinct) {
499
+ throw new Error(`${errorPrefix} ${aggregate.type} DISTINCT aggregate is not supported in SQL aggregate path`);
500
+ }
501
+ aggregateTypes.set(aggregate.as, 'decimal');
502
+ const termAlias = numericJoins.get(aggregate.variable) ?? `agg_numeric_t${numericJoins.size}`;
503
+ if (!numericJoins.has(aggregate.variable)) {
504
+ numericJoins.set(aggregate.variable, termAlias);
505
+ numericJoinSql.push(` JOIN rdf_terms ${termAlias} ON ${termAlias}.id = ${column} AND ${termAlias}.kind = 'literal' AND ${termAlias}.numeric_value IS NOT NULL`);
506
+ }
507
+ switch (aggregate.type) {
508
+ case 'sum':
509
+ return `COALESCE(SUM(${termAlias}.numeric_value), 0) AS ${alias}`;
510
+ case 'avg':
511
+ return `AVG(${termAlias}.numeric_value) AS ${alias}`;
512
+ case 'min':
513
+ return `MIN(${termAlias}.numeric_value) AS ${alias}`;
514
+ case 'max':
515
+ return `MAX(${termAlias}.numeric_value) AS ${alias}`;
516
+ default: {
517
+ const exhaustive = aggregate.type;
518
+ throw new Error(`Unsupported RDF BGP aggregate type: ${exhaustive}`);
519
+ }
520
+ }
521
+ }
522
+ buildGroupCountHavingClause(having, aggregateAliases) {
523
+ return this.buildGroupAggregateHavingClause(having, aggregateAliases);
524
+ }
525
+ buildGroupAggregateHavingClause(having, aggregateAliases) {
526
+ if (!having || having.length === 0) {
527
+ return { sql: '', params: [] };
528
+ }
529
+ const conditions = [];
530
+ const params = [];
531
+ for (const entry of having) {
532
+ const alias = aggregateAliases.get(entry.aggregate);
533
+ if (!alias) {
534
+ throw new Error(`RDF BGP group count cannot HAVING on unknown aggregate: ${entry.aggregate}`);
535
+ }
536
+ conditions.push(`${alias} ${this.havingSqlOperator(entry.operator)} ?`);
537
+ params.push(entry.value);
538
+ }
539
+ return {
540
+ sql: ` HAVING ${conditions.join(' AND ')}`,
541
+ params,
542
+ };
543
+ }
544
+ havingSqlOperator(operator) {
545
+ switch (operator) {
546
+ case '$eq':
547
+ return '=';
548
+ case '$ne':
549
+ return '!=';
550
+ case '$gt':
551
+ return '>';
552
+ case '$gte':
553
+ return '>=';
554
+ case '$lt':
555
+ return '<';
556
+ case '$lte':
557
+ return '<=';
558
+ default: {
559
+ const exhaustive = operator;
560
+ throw new Error(`Unsupported RDF BGP group count HAVING operator: ${exhaustive}`);
561
+ }
562
+ }
563
+ }
564
+ buildGroupCountOrderScope(options, variableColumns, aggregateAliases) {
565
+ return this.buildGroupAggregateOrderScope(options, variableColumns, aggregateAliases);
566
+ }
567
+ buildGroupAggregateOrderScope(options, variableColumns, aggregateAliases) {
568
+ if (!options.orderBy || options.orderBy.length === 0) {
569
+ return { joins: '', orderBy: '' };
570
+ }
571
+ const joins = [];
572
+ const orders = options.orderBy.map((entry, index) => {
573
+ const aggregateAlias = aggregateAliases.get(entry.variable);
574
+ if (aggregateAlias) {
575
+ return `${aggregateAlias}${entry.direction === 'desc' ? ' DESC' : ''}`;
576
+ }
577
+ const column = variableColumns.get(entry.variable);
578
+ if (!column) {
579
+ throw new Error(`RDF BGP group count cannot order by unbound variable: ${entry.variable}`);
580
+ }
581
+ const alias = `group_order_t${index}`;
582
+ joins.push(` JOIN rdf_terms ${alias} ON ${alias}.id = ${column}`);
583
+ return `${alias}.value${entry.direction === 'desc' ? ' DESC' : ''}`;
584
+ });
585
+ return {
586
+ joins: joins.join(''),
587
+ orderBy: ` ORDER BY ${orders.join(', ')}`,
588
+ };
589
+ }
590
+ matchToJoinCondition(key, alias, columnName, match, allowUnresolved) {
591
+ return this.matchToCondition(key, columnName, match, allowUnresolved, {
592
+ quadAlias: alias,
593
+ aliasPrefix: alias,
594
+ });
595
+ }
596
+ joinRowsToBindings(rows, variableAliases, aggregateAliases, aggregateTypes) {
597
+ const aliases = [...variableAliases.entries()];
598
+ const termMap = this.requireDictionary().rowsForIds(rows.flatMap((row) => (aliases
599
+ .map(([, alias]) => row[alias])
600
+ .filter((id) => typeof id === 'number'))));
601
+ return rows.map((row) => {
602
+ const binding = {};
603
+ for (const [variableName, alias] of aliases) {
604
+ const id = row[alias];
605
+ if (typeof id !== 'number') {
606
+ continue;
607
+ }
608
+ binding[variableName] = this.requiredTerm(termMap, id);
609
+ }
610
+ for (const [variableName, alias] of aggregateAliases ?? []) {
611
+ const value = row[alias];
612
+ if (typeof value === 'number') {
613
+ const datatype = aggregateTypes?.get(variableName) === 'decimal' ? XSD_DECIMAL : XSD_INTEGER;
614
+ binding[variableName] = n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode(datatype));
615
+ }
616
+ }
617
+ return binding;
618
+ });
619
+ }
620
+ count(pattern) {
621
+ const { joins, whereClause, params, unresolved } = this.buildWhereClause(pattern, true);
622
+ if (unresolved) {
623
+ return 0;
624
+ }
625
+ const row = this.requireDb()
626
+ .prepare(`SELECT COUNT(*) AS count FROM rdf_quads${joins}${whereClause}`)
627
+ .get(...params);
628
+ return row?.count ?? 0;
629
+ }
630
+ estimateCardinality(pattern) {
631
+ const exact = this.exactTermPattern(pattern);
632
+ if (!exact) {
633
+ return {
634
+ rows: this.count(pattern),
635
+ source: 'exact-count',
636
+ indexChoice: this.buildWhereClause(pattern, true).indexHint,
637
+ };
638
+ }
639
+ const cacheKey = exact.cacheKey;
640
+ const cached = this.cardinalityCache.get(cacheKey);
641
+ if (cached) {
642
+ return {
643
+ ...cached,
644
+ source: 'cached-exact-count',
645
+ };
646
+ }
647
+ const estimate = this.countExactTermPattern(exact.ids, exact.indexChoice);
648
+ this.cardinalityCache.set(cacheKey, estimate);
649
+ return estimate;
650
+ }
651
+ countDistinct(pattern, distinctKey) {
652
+ const exact = this.exactTermPattern(pattern);
653
+ if (!exact) {
654
+ const count = this.countDistinctPattern(pattern, distinctKey);
655
+ return {
656
+ rows: count,
657
+ source: 'exact-distinct-count',
658
+ indexChoice: this.buildWhereClause(pattern, true).indexHint,
659
+ };
660
+ }
661
+ const cacheKey = `distinct:${distinctKey}|${exact.cacheKey}`;
662
+ const cached = this.cardinalityCache.get(cacheKey);
663
+ if (cached) {
664
+ return {
665
+ ...cached,
666
+ source: 'cached-exact-distinct-count',
667
+ };
668
+ }
669
+ const estimate = this.countExactDistinctTermPattern(exact.ids, exact.indexChoice, distinctKey);
670
+ this.cardinalityCache.set(cacheKey, estimate);
671
+ return estimate;
672
+ }
673
+ countDistinctTuple(pattern, distinctKeys) {
674
+ const keys = uniquePatternKeys(distinctKeys);
675
+ if (keys.length === 0) {
676
+ return this.estimateCardinality(pattern);
677
+ }
678
+ if (keys.length === 1) {
679
+ return this.countDistinct(pattern, keys[0]);
680
+ }
681
+ const exact = this.exactTermPattern(pattern);
682
+ if (!exact) {
683
+ const count = this.countDistinctTuplePattern(pattern, keys);
684
+ return {
685
+ rows: count,
686
+ source: 'exact-distinct-tuple-count',
687
+ indexChoice: this.buildWhereClause(pattern, true).indexHint,
688
+ };
689
+ }
690
+ const cacheKey = `distinct-tuple:${keys.join(',')}|${exact.cacheKey}`;
691
+ const cached = this.cardinalityCache.get(cacheKey);
692
+ if (cached) {
693
+ return {
694
+ ...cached,
695
+ source: 'cached-exact-distinct-tuple-count',
696
+ };
697
+ }
698
+ const estimate = this.countExactDistinctTupleTermPattern(exact.ids, exact.indexChoice, keys);
699
+ this.cardinalityCache.set(cacheKey, estimate);
700
+ return estimate;
701
+ }
702
+ stats() {
703
+ const db = this.requireDb();
704
+ const spaceObjects = this.collectSpaceObjects();
705
+ const databaseBytes = this.estimateDatabaseBytes();
706
+ const accountedBytes = spaceObjects.reduce((sum, object) => sum + object.bytes, 0);
707
+ return {
708
+ termCount: this.requireDictionary().count(),
709
+ quadCount: db.prepare('SELECT COUNT(*) AS count FROM rdf_quads').get()?.count ?? 0,
710
+ sourceCount: db.prepare('SELECT COUNT(*) AS count FROM rdf_sources').get()?.count ?? 0,
711
+ graphCount: db.prepare('SELECT COUNT(DISTINCT graph_id) AS count FROM rdf_quads').get()?.count ?? 0,
712
+ databaseBytes: accountedBytes || databaseBytes,
713
+ tableBytes: sumSpaceObjects(spaceObjects, 'table'),
714
+ indexBytes: sumSpaceObjects(spaceObjects, 'index'),
715
+ spaceObjects,
716
+ serializedTermTextBytes: this.estimateSerializedTextBytes(),
717
+ literalDatatypeDistribution: this.literalDatatypeDistribution(),
718
+ cardinalityDistributions: this.cardinalityDistributions(),
719
+ };
720
+ }
721
+ cardinalityDistributions(limit = 100) {
722
+ return {
723
+ graphs: this.graphCardinalityDistribution(limit),
724
+ predicates: this.predicateCardinalityDistribution(limit),
725
+ predicateObjects: this.predicateObjectCardinalityDistribution(limit),
726
+ subjectPredicates: this.subjectPredicateCardinalityDistribution(limit),
727
+ };
728
+ }
729
+ literalDatatypeDistribution() {
730
+ const rows = this.requireDb().prepare(`
731
+ SELECT
732
+ COALESCE(datatype.value, 'http://www.w3.org/2001/XMLSchema#string') AS datatype,
733
+ COUNT(DISTINCT literal.id) AS term_count,
734
+ COUNT(quad.object_id) AS object_quad_count
735
+ FROM rdf_terms literal
736
+ LEFT JOIN rdf_terms datatype ON datatype.id = literal.datatype_id
737
+ LEFT JOIN rdf_quads quad ON quad.object_id = literal.id
738
+ WHERE literal.kind = 'literal'
739
+ GROUP BY datatype
740
+ ORDER BY object_quad_count DESC, term_count DESC, datatype ASC
741
+ `).all();
742
+ return rows.map((row) => ({
743
+ datatype: row.datatype ?? 'http://www.w3.org/2001/XMLSchema#string',
744
+ termCount: row.term_count,
745
+ objectQuadCount: row.object_quad_count ?? 0,
746
+ }));
747
+ }
748
+ graphCardinalityDistribution(limit) {
749
+ const rows = this.requireDb().prepare(`
750
+ SELECT
751
+ graph_id,
752
+ COUNT(*) AS quad_count,
753
+ COUNT(DISTINCT subject_id) AS distinct_subjects,
754
+ COUNT(DISTINCT predicate_id) AS distinct_predicates,
755
+ COUNT(DISTINCT object_id) AS distinct_objects
756
+ FROM rdf_quads
757
+ GROUP BY graph_id
758
+ ORDER BY quad_count DESC, graph_id ASC
759
+ LIMIT ?
760
+ `).all(limit);
761
+ const termMap = this.requireDictionary().rowsForIds(rows.map((row) => row.graph_id));
762
+ return rows.map((row) => ({
763
+ graph: this.cardinalityTerm(termMap, row.graph_id),
764
+ quadCount: row.quad_count,
765
+ distinctSubjects: row.distinct_subjects,
766
+ distinctPredicates: row.distinct_predicates,
767
+ distinctObjects: row.distinct_objects,
768
+ }));
769
+ }
770
+ predicateCardinalityDistribution(limit) {
771
+ const rows = this.requireDb().prepare(`
772
+ SELECT
773
+ predicate_id,
774
+ COUNT(*) AS quad_count,
775
+ COUNT(DISTINCT graph_id) AS graph_count,
776
+ COUNT(DISTINCT subject_id) AS distinct_subjects,
777
+ COUNT(DISTINCT object_id) AS distinct_objects
778
+ FROM rdf_quads
779
+ GROUP BY predicate_id
780
+ ORDER BY quad_count DESC, predicate_id ASC
781
+ LIMIT ?
782
+ `).all(limit);
783
+ const termMap = this.requireDictionary().rowsForIds(rows.map((row) => row.predicate_id));
784
+ return rows.map((row) => ({
785
+ predicate: this.cardinalityTerm(termMap, row.predicate_id),
786
+ quadCount: row.quad_count,
787
+ graphCount: row.graph_count,
788
+ distinctSubjects: row.distinct_subjects,
789
+ distinctObjects: row.distinct_objects,
790
+ }));
791
+ }
792
+ predicateObjectCardinalityDistribution(limit) {
793
+ const rows = this.requireDb().prepare(`
794
+ SELECT
795
+ predicate_id,
796
+ object_id,
797
+ COUNT(*) AS quad_count,
798
+ COUNT(DISTINCT graph_id) AS graph_count,
799
+ COUNT(DISTINCT subject_id) AS distinct_subjects
800
+ FROM rdf_quads
801
+ GROUP BY predicate_id, object_id
802
+ ORDER BY quad_count DESC, predicate_id ASC, object_id ASC
803
+ LIMIT ?
804
+ `).all(limit);
805
+ const termMap = this.requireDictionary().rowsForIds(rows.flatMap((row) => [row.predicate_id, row.object_id]));
806
+ return rows.map((row) => ({
807
+ predicate: this.cardinalityTerm(termMap, row.predicate_id),
808
+ object: this.cardinalityTerm(termMap, row.object_id),
809
+ quadCount: row.quad_count,
810
+ graphCount: row.graph_count,
811
+ distinctSubjects: row.distinct_subjects,
812
+ }));
813
+ }
814
+ subjectPredicateCardinalityDistribution(limit) {
815
+ const rows = this.requireDb().prepare(`
816
+ SELECT
817
+ subject_id,
818
+ predicate_id,
819
+ COUNT(*) AS quad_count,
820
+ COUNT(DISTINCT graph_id) AS graph_count,
821
+ COUNT(DISTINCT object_id) AS distinct_objects
822
+ FROM rdf_quads
823
+ GROUP BY subject_id, predicate_id
824
+ ORDER BY quad_count DESC, subject_id ASC, predicate_id ASC
825
+ LIMIT ?
826
+ `).all(limit);
827
+ const termMap = this.requireDictionary().rowsForIds(rows.flatMap((row) => [row.subject_id, row.predicate_id]));
828
+ return rows.map((row) => ({
829
+ subject: this.cardinalityTerm(termMap, row.subject_id),
830
+ predicate: this.cardinalityTerm(termMap, row.predicate_id),
831
+ quadCount: row.quad_count,
832
+ graphCount: row.graph_count,
833
+ distinctObjects: row.distinct_objects,
834
+ }));
835
+ }
836
+ cardinalityTerm(termMap, id) {
837
+ const term = this.requiredTerm(termMap, id);
838
+ const result = {
839
+ value: term.value,
840
+ kind: rdfTermKind(term),
841
+ };
842
+ if (term.termType === 'Literal') {
843
+ if (term.datatype.value) {
844
+ result.datatype = term.datatype.value;
845
+ }
846
+ if (term.language) {
847
+ result.language = term.language;
848
+ }
849
+ }
850
+ return result;
851
+ }
852
+ estimateSerializedTextBytes() {
853
+ const db = this.requireDb();
854
+ const row = db.prepare(`
855
+ SELECT
856
+ COALESCE(SUM(length(value)), 0) +
857
+ COALESCE(SUM(CASE WHEN lang IS NULL THEN 0 ELSE length(lang) END), 0) AS bytes
858
+ FROM rdf_terms
859
+ `).get();
860
+ return row?.bytes ?? 0;
861
+ }
862
+ estimateDatabaseBytes() {
863
+ const db = this.requireDb();
864
+ try {
865
+ const pageCount = db.prepare('PRAGMA page_count').get()?.page_count ?? 0;
866
+ const pageSize = db.prepare('PRAGMA page_size').get()?.page_size ?? 0;
867
+ return pageCount * pageSize;
868
+ }
869
+ catch {
870
+ return 0;
871
+ }
872
+ }
873
+ collectSpaceObjects() {
874
+ const db = this.requireDb();
875
+ try {
876
+ const schemaRows = db.prepare(`
877
+ SELECT name, type, tbl_name
878
+ FROM sqlite_schema
879
+ WHERE type IN ('table', 'index')
880
+ AND (
881
+ name IN ('rdf_terms', 'rdf_sources', 'rdf_quads')
882
+ OR tbl_name IN ('rdf_terms', 'rdf_sources', 'rdf_quads')
883
+ )
884
+ `).all();
885
+ const schema = new Map(schemaRows.map((row) => [row.name, row]));
886
+ try {
887
+ const rows = db.prepare(`
888
+ SELECT name, COUNT(*) AS pages, SUM(pgsize) AS bytes
889
+ FROM dbstat
890
+ WHERE name IN (
891
+ SELECT name
892
+ FROM sqlite_schema
893
+ WHERE type IN ('table', 'index')
894
+ AND (
895
+ name IN ('rdf_terms', 'rdf_sources', 'rdf_quads')
896
+ OR tbl_name IN ('rdf_terms', 'rdf_sources', 'rdf_quads')
897
+ )
898
+ )
899
+ GROUP BY name
900
+ ORDER BY name
901
+ `).all();
902
+ if (rows.length > 0) {
903
+ return rows.map((row) => {
904
+ const object = schema.get(row.name);
905
+ const kind = rdfSpaceObjectKind(row.name, object?.type, object?.tbl_name);
906
+ return {
907
+ name: row.name,
908
+ kind,
909
+ ...(object?.tbl_name && object.tbl_name !== row.name ? { tableName: object.tbl_name } : {}),
910
+ pages: row.pages,
911
+ bytes: row.bytes ?? 0,
912
+ };
913
+ });
914
+ }
915
+ }
916
+ catch {
917
+ // Some SQLite builds do not expose dbstat for in-memory databases.
918
+ }
919
+ return this.estimateSpaceObjectsFromSchema(schemaRows);
920
+ }
921
+ catch {
922
+ return [];
923
+ }
924
+ }
925
+ estimateSpaceObjectsFromSchema(schemaRows) {
926
+ const pageSize = this.estimatePageSize();
927
+ return schemaRows.map((object) => ({
928
+ name: object.name,
929
+ kind: rdfSpaceObjectKind(object.name, object.type, object.tbl_name),
930
+ ...(object.tbl_name && object.tbl_name !== object.name ? { tableName: object.tbl_name } : {}),
931
+ pages: 1,
932
+ bytes: pageSize,
933
+ estimated: true,
934
+ }));
935
+ }
936
+ estimatePageSize() {
937
+ try {
938
+ return this.requireDb().prepare('PRAGMA page_size').get()?.page_size ?? 4096;
939
+ }
940
+ catch {
941
+ return 4096;
942
+ }
943
+ }
944
+ initializeSchema() {
945
+ this.requireDb().exec(`
946
+ CREATE TABLE IF NOT EXISTS rdf_sources (
947
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
948
+ source TEXT NOT NULL UNIQUE,
949
+ workspace TEXT NOT NULL,
950
+ local_path TEXT,
951
+ content_type TEXT,
952
+ last_indexed_at TEXT,
953
+ source_version TEXT
954
+ );
955
+
956
+ CREATE TABLE IF NOT EXISTS rdf_quads (
957
+ graph_id INTEGER NOT NULL,
958
+ subject_id INTEGER NOT NULL,
959
+ predicate_id INTEGER NOT NULL,
960
+ object_id INTEGER NOT NULL,
961
+ source_file_id INTEGER,
962
+ source_line_no INTEGER,
963
+ PRIMARY KEY (graph_id, subject_id, predicate_id, object_id),
964
+ FOREIGN KEY (graph_id) REFERENCES rdf_terms(id),
965
+ FOREIGN KEY (subject_id) REFERENCES rdf_terms(id),
966
+ FOREIGN KEY (predicate_id) REFERENCES rdf_terms(id),
967
+ FOREIGN KEY (object_id) REFERENCES rdf_terms(id),
968
+ FOREIGN KEY (source_file_id) REFERENCES rdf_sources(id)
969
+ );
970
+
971
+ CREATE INDEX IF NOT EXISTS rdf_quads_spog ON rdf_quads(subject_id, predicate_id, object_id, graph_id);
972
+ CREATE INDEX IF NOT EXISTS rdf_quads_sopg ON rdf_quads(subject_id, object_id, predicate_id, graph_id);
973
+ CREATE INDEX IF NOT EXISTS rdf_quads_psog ON rdf_quads(predicate_id, subject_id, object_id, graph_id);
974
+ CREATE INDEX IF NOT EXISTS rdf_quads_posg ON rdf_quads(predicate_id, object_id, subject_id, graph_id);
975
+ CREATE INDEX IF NOT EXISTS rdf_quads_ospg ON rdf_quads(object_id, subject_id, predicate_id, graph_id);
976
+ CREATE INDEX IF NOT EXISTS rdf_quads_opsg ON rdf_quads(object_id, predicate_id, subject_id, graph_id);
977
+ CREATE INDEX IF NOT EXISTS rdf_quads_gspo ON rdf_quads(graph_id, subject_id, predicate_id, object_id);
978
+ CREATE INDEX IF NOT EXISTS rdf_quads_gpos ON rdf_quads(graph_id, predicate_id, object_id, subject_id);
979
+ CREATE INDEX IF NOT EXISTS rdf_quads_source ON rdf_quads(source_file_id);
980
+ `);
981
+ }
982
+ upsertSource(source) {
983
+ const db = this.requireDb();
984
+ db.prepare(`
985
+ INSERT INTO rdf_sources (
986
+ source,
987
+ workspace,
988
+ local_path,
989
+ content_type,
990
+ last_indexed_at,
991
+ source_version
992
+ )
993
+ VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), ?)
994
+ ON CONFLICT (source)
995
+ DO UPDATE SET
996
+ workspace = excluded.workspace,
997
+ local_path = excluded.local_path,
998
+ content_type = excluded.content_type,
999
+ last_indexed_at = excluded.last_indexed_at,
1000
+ source_version = excluded.source_version
1001
+ `).run(source.source, source.workspace, source.localPath ?? null, source.contentType ?? null, source.sourceVersion ?? null);
1002
+ const row = db.prepare('SELECT * FROM rdf_sources WHERE source = ?').get(source.source);
1003
+ if (!row) {
1004
+ throw new Error(`Failed to upsert RDF source: ${source.source}`);
1005
+ }
1006
+ return row.id;
1007
+ }
1008
+ buildWhereClause(pattern, allowUnresolved) {
1009
+ const conditions = [];
1010
+ const joins = [];
1011
+ const params = [];
1012
+ const queryPlan = [];
1013
+ const equalityColumns = new Set();
1014
+ for (const key of TERM_KEYS) {
1015
+ const match = pattern[key];
1016
+ if (!match) {
1017
+ continue;
1018
+ }
1019
+ const column = TERM_COLUMN[key];
1020
+ const condition = this.matchToCondition(key, column, match, allowUnresolved);
1021
+ if (condition.unresolved) {
1022
+ return { joins: '', whereClause: '', params: [], indexHint: 'none', queryPlan, unresolved: key };
1023
+ }
1024
+ joins.push(...condition.joins);
1025
+ if (condition.sql) {
1026
+ conditions.push(condition.sql);
1027
+ params.push(...condition.params);
1028
+ }
1029
+ queryPlan.push(...condition.queryPlan);
1030
+ if (condition.equality) {
1031
+ equalityColumns.add(column);
1032
+ }
1033
+ }
1034
+ return {
1035
+ joins: joins.join(''),
1036
+ whereClause: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '',
1037
+ params,
1038
+ indexHint: this.chooseIndex(equalityColumns),
1039
+ queryPlan,
1040
+ };
1041
+ }
1042
+ matchToCondition(key, column, match, allowUnresolved, scope) {
1043
+ const columnRef = this.scopedQuadColumn(column, scope);
1044
+ if ((0, types_1.isTerm)(match)) {
1045
+ const id = this.requireDictionary().find(match);
1046
+ if (id === undefined) {
1047
+ if (allowUnresolved) {
1048
+ return { joins: [], params: [], queryPlan: [], unresolved: true };
1049
+ }
1050
+ return { joins: [], sql: `${columnRef} = ?`, params: [-1], equality: true, queryPlan: [] };
1051
+ }
1052
+ return { joins: [], sql: `${columnRef} = ?`, params: [id], equality: true, queryPlan: [] };
1053
+ }
1054
+ const ops = match;
1055
+ const fragments = [];
1056
+ const joins = [];
1057
+ const params = [];
1058
+ const queryPlan = [];
1059
+ let equality = false;
1060
+ if (ops.$eq !== undefined) {
1061
+ const id = this.termOperatorValueId(ops.$eq, allowUnresolved);
1062
+ if (id === undefined)
1063
+ return { joins, params: [], queryPlan, unresolved: true };
1064
+ fragments.push(`${columnRef} = ?`);
1065
+ params.push(id);
1066
+ equality = true;
1067
+ }
1068
+ if (ops.$in !== undefined) {
1069
+ const ids = uniqueNumbers(ops.$in
1070
+ .map((value) => this.termOperatorValueId(value, allowUnresolved))
1071
+ .filter((value) => value !== undefined));
1072
+ if (ids.length === 0) {
1073
+ return { joins, params: [], queryPlan, unresolved: true };
1074
+ }
1075
+ if (ids.length > TERM_IN_JOIN_THRESHOLD) {
1076
+ const candidateTable = this.populateTermInCandidateTable(column, 'in', ids, scope);
1077
+ const candidateAlias = this.scopedSqlName(`${column}_in_candidates`, scope);
1078
+ joins.push(` JOIN ${candidateTable} ${candidateAlias} ON ${candidateAlias}.id = ${columnRef}`);
1079
+ }
1080
+ else {
1081
+ fragments.push(`${columnRef} IN (${ids.map(() => '?').join(', ')})`);
1082
+ params.push(...ids);
1083
+ }
1084
+ queryPlan.push(`TermIn(${key})`);
1085
+ equality = true;
1086
+ }
1087
+ if (ops.$ne !== undefined) {
1088
+ const id = this.termOperatorValueId(ops.$ne, allowUnresolved);
1089
+ if (id !== undefined) {
1090
+ fragments.push(`${columnRef} != ?`);
1091
+ params.push(id);
1092
+ }
1093
+ }
1094
+ for (const operator of ['$gt', '$gte', '$lt', '$lte']) {
1095
+ if (ops[operator] !== undefined) {
1096
+ const range = this.termRangeCondition(key, column, operator, ops[operator], allowUnresolved, scope);
1097
+ if (range.unresolved) {
1098
+ return { joins, params: [], queryPlan, unresolved: true };
1099
+ }
1100
+ joins.push(...(range.joins ?? []));
1101
+ fragments.push(range.sql);
1102
+ params.push(...range.params);
1103
+ queryPlan.push(...(range.queryPlan ?? []));
1104
+ }
1105
+ }
1106
+ if (ops.$notIn !== undefined) {
1107
+ const ids = uniqueNumbers(ops.$notIn
1108
+ .map((value) => this.termOperatorValueId(value, allowUnresolved))
1109
+ .filter((value) => value !== undefined));
1110
+ if (ids.length > 0) {
1111
+ if (ids.length > TERM_IN_JOIN_THRESHOLD) {
1112
+ const candidateTable = this.populateTermInCandidateTable(column, 'not_in', ids, scope);
1113
+ const candidateAlias = this.scopedSqlName(`${column}_not_in_candidates`, scope);
1114
+ joins.push(` LEFT JOIN ${candidateTable} ${candidateAlias} ON ${candidateAlias}.id = ${columnRef}`);
1115
+ fragments.push(`${candidateAlias}.id IS NULL`);
1116
+ }
1117
+ else {
1118
+ fragments.push(`${columnRef} NOT IN (${ids.map(() => '?').join(', ')})`);
1119
+ params.push(...ids);
1120
+ }
1121
+ queryPlan.push(`TermNotIn(${key})`);
1122
+ }
1123
+ }
1124
+ if (ops.$termType !== undefined) {
1125
+ const condition = this.termTypeConditionJoin(key, column, ops.$termType, scope);
1126
+ joins.push(condition.join);
1127
+ fragments.push(condition.sql);
1128
+ params.push(...condition.params);
1129
+ queryPlan.push(condition.queryPlan);
1130
+ }
1131
+ if (ops.$language !== undefined) {
1132
+ const condition = this.languageConditionJoin(key, column, '$language', ops.$language, scope);
1133
+ joins.push(condition.join);
1134
+ fragments.push(condition.sql);
1135
+ params.push(...condition.params);
1136
+ queryPlan.push(condition.queryPlan);
1137
+ }
1138
+ if (ops.$notLanguage !== undefined) {
1139
+ const condition = this.languageConditionJoin(key, column, '$notLanguage', ops.$notLanguage, scope);
1140
+ joins.push(condition.join);
1141
+ fragments.push(condition.sql);
1142
+ params.push(...condition.params);
1143
+ queryPlan.push(condition.queryPlan);
1144
+ }
1145
+ if (ops.$langMatches !== undefined) {
1146
+ const condition = this.languageConditionJoin(key, column, '$langMatches', ops.$langMatches, scope);
1147
+ joins.push(condition.join);
1148
+ fragments.push(condition.sql);
1149
+ params.push(...condition.params);
1150
+ queryPlan.push(condition.queryPlan);
1151
+ }
1152
+ if (ops.$datatype !== undefined) {
1153
+ const condition = this.datatypeConditionJoin(key, column, '$datatype', ops.$datatype, allowUnresolved, scope);
1154
+ if (condition.unresolved) {
1155
+ return { joins, params: [], queryPlan, unresolved: true };
1156
+ }
1157
+ joins.push(condition.join);
1158
+ fragments.push(condition.sql);
1159
+ params.push(...condition.params);
1160
+ queryPlan.push(condition.queryPlan);
1161
+ }
1162
+ if (ops.$notDatatype !== undefined) {
1163
+ const condition = this.datatypeConditionJoin(key, column, '$notDatatype', ops.$notDatatype, allowUnresolved, scope);
1164
+ if (condition.unresolved) {
1165
+ return { joins, params: [], queryPlan, unresolved: true };
1166
+ }
1167
+ joins.push(condition.join);
1168
+ fragments.push(condition.sql);
1169
+ params.push(...condition.params);
1170
+ queryPlan.push(condition.queryPlan);
1171
+ }
1172
+ if (ops.$startsWith !== undefined) {
1173
+ const condition = this.prefixSearchConditionJoin(key, column, ops.$startsWith, scope);
1174
+ joins.push(condition.join);
1175
+ fragments.push(condition.sql);
1176
+ params.push(...condition.params);
1177
+ queryPlan.push(`PrefixRange(${key})`);
1178
+ equality = true;
1179
+ }
1180
+ equality = this.addTextSearchCondition(joins, fragments, params, queryPlan, key, column, '$contains', ops.$contains, scope) || equality;
1181
+ equality = this.addTextSearchCondition(joins, fragments, params, queryPlan, key, column, '$endsWith', ops.$endsWith, scope) || equality;
1182
+ equality = this.addTextSearchCondition(joins, fragments, params, queryPlan, key, column, '$regex', ops.$regex, scope) || equality;
1183
+ return {
1184
+ joins,
1185
+ sql: fragments.length > 0 ? fragments.join(' AND ') : undefined,
1186
+ params,
1187
+ equality,
1188
+ queryPlan,
1189
+ };
1190
+ }
1191
+ termTypeConditionJoin(key, column, termType, scope) {
1192
+ const alias = this.scopedSqlName(`${column}_term_type_${termType}`, scope);
1193
+ const columnRef = this.scopedQuadColumn(column, scope);
1194
+ const possibleKinds = this.termKindsForPatternKey(key);
1195
+ if (termType === 'numeric') {
1196
+ if (!possibleKinds.includes('literal')) {
1197
+ return {
1198
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`,
1199
+ sql: '1 = 0',
1200
+ params: [],
1201
+ queryPlan: `TermType(${key}:numeric)`,
1202
+ };
1203
+ }
1204
+ return {
1205
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`,
1206
+ sql: `${alias}.kind = 'literal' AND ${alias}.numeric_value IS NOT NULL`,
1207
+ params: [],
1208
+ queryPlan: `TermType(${key}:numeric)`,
1209
+ };
1210
+ }
1211
+ if (!possibleKinds.includes(termType)) {
1212
+ return {
1213
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`,
1214
+ sql: '1 = 0',
1215
+ params: [],
1216
+ queryPlan: `TermType(${key}:${termType})`,
1217
+ };
1218
+ }
1219
+ return {
1220
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`,
1221
+ sql: `${alias}.kind = ?`,
1222
+ params: [termType],
1223
+ queryPlan: `TermType(${key}:${termType})`,
1224
+ };
1225
+ }
1226
+ languageConditionJoin(key, column, operator, language, scope) {
1227
+ const alias = this.scopedSqlName(`${column}_${operator.slice(1).toLowerCase()}`, scope);
1228
+ const columnRef = this.scopedQuadColumn(column, scope);
1229
+ if (!this.termKindsForPatternKey(key).includes('literal')) {
1230
+ return {
1231
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`,
1232
+ sql: '1 = 0',
1233
+ params: [],
1234
+ queryPlan: `Language(${key}${operator})`,
1235
+ };
1236
+ }
1237
+ const join = ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`;
1238
+ if (operator === '$language') {
1239
+ return {
1240
+ join,
1241
+ sql: `${alias}.kind = 'literal' AND COALESCE(${alias}.lang, '') = ?`,
1242
+ params: [language],
1243
+ queryPlan: `Language(${key}${operator})`,
1244
+ };
1245
+ }
1246
+ if (operator === '$notLanguage') {
1247
+ return {
1248
+ join,
1249
+ sql: `${alias}.kind = 'literal' AND COALESCE(${alias}.lang, '') != ?`,
1250
+ params: [language],
1251
+ queryPlan: `Language(${key}${operator})`,
1252
+ };
1253
+ }
1254
+ if (language === '*') {
1255
+ return {
1256
+ join,
1257
+ sql: `${alias}.kind = 'literal' AND ${alias}.lang IS NOT NULL AND ${alias}.lang != ''`,
1258
+ params: [],
1259
+ queryPlan: `Language(${key}${operator})`,
1260
+ };
1261
+ }
1262
+ return {
1263
+ join,
1264
+ sql: `${alias}.kind = 'literal'
1265
+ AND (lower(${alias}.lang) = lower(?) OR lower(${alias}.lang) LIKE lower(?) ESCAPE '\\')`,
1266
+ params: [language, `${escapeLikePattern(language)}-%`],
1267
+ queryPlan: `Language(${key}${operator})`,
1268
+ };
1269
+ }
1270
+ datatypeConditionJoin(key, column, operator, datatype, allowUnresolved, scope) {
1271
+ const alias = this.scopedSqlName(`${column}_${operator.slice(1).toLowerCase()}`, scope);
1272
+ const columnRef = this.scopedQuadColumn(column, scope);
1273
+ if (datatype.termType !== 'NamedNode') {
1274
+ throw new Error('RdfQuadIndex datatype filters only support named node datatype terms');
1275
+ }
1276
+ if (!this.termKindsForPatternKey(key).includes('literal')) {
1277
+ return {
1278
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`,
1279
+ sql: '1 = 0',
1280
+ params: [],
1281
+ queryPlan: `Datatype(${key}${operator})`,
1282
+ };
1283
+ }
1284
+ const join = ` JOIN rdf_terms ${alias} ON ${alias}.id = ${columnRef}`;
1285
+ if (datatype.value === XSD_STRING) {
1286
+ return {
1287
+ join,
1288
+ sql: operator === '$datatype'
1289
+ ? `${alias}.kind = 'literal' AND ${alias}.lang IS NULL AND ${alias}.datatype_id IS NULL`
1290
+ : `${alias}.kind = 'literal' AND NOT (${alias}.lang IS NULL AND ${alias}.datatype_id IS NULL)`,
1291
+ params: [],
1292
+ queryPlan: `Datatype(${key}${operator})`,
1293
+ };
1294
+ }
1295
+ const datatypeId = this.requireDictionary().find(datatype);
1296
+ if (datatypeId === undefined) {
1297
+ if (operator === '$notDatatype') {
1298
+ return {
1299
+ join,
1300
+ sql: `${alias}.kind = 'literal'`,
1301
+ params: [],
1302
+ queryPlan: `Datatype(${key}${operator})`,
1303
+ };
1304
+ }
1305
+ if (allowUnresolved) {
1306
+ return { join, sql: '', params: [], queryPlan: `Datatype(${key}${operator})`, unresolved: true };
1307
+ }
1308
+ return {
1309
+ join,
1310
+ sql: operator === '$datatype' ? '1 = 0' : `${alias}.kind = 'literal'`,
1311
+ params: [],
1312
+ queryPlan: `Datatype(${key}${operator})`,
1313
+ };
1314
+ }
1315
+ return {
1316
+ join,
1317
+ sql: operator === '$datatype'
1318
+ ? `${alias}.kind = 'literal' AND ${alias}.datatype_id = ?`
1319
+ : `${alias}.kind = 'literal' AND (${alias}.datatype_id IS NULL OR ${alias}.datatype_id != ?)`,
1320
+ params: [datatypeId],
1321
+ queryPlan: `Datatype(${key}${operator})`,
1322
+ };
1323
+ }
1324
+ termRangeCondition(key, column, operator, value, allowUnresolved, scope) {
1325
+ const kinds = this.termKindsForPatternKey(key);
1326
+ const lexicalValue = this.termOperatorLexicalValue(value);
1327
+ if (lexicalValue === undefined) {
1328
+ if (allowUnresolved) {
1329
+ return { sql: '', params: [], unresolved: true };
1330
+ }
1331
+ return { sql: '1 = 0', params: [] };
1332
+ }
1333
+ const comparator = {
1334
+ $gt: '>',
1335
+ $gte: '>=',
1336
+ $lt: '<',
1337
+ $lte: '<=',
1338
+ }[operator];
1339
+ const numericValue = this.termOperatorNumericValue(value);
1340
+ if (numericValue !== undefined) {
1341
+ if (!kinds.includes('literal')) {
1342
+ if (allowUnresolved) {
1343
+ return { sql: '', params: [], unresolved: true };
1344
+ }
1345
+ return { sql: '1 = 0', params: [] };
1346
+ }
1347
+ const termAlias = this.scopedSqlName(`${column}_numeric_range_${operator.slice(1)}`, scope);
1348
+ return {
1349
+ joins: [` JOIN rdf_terms ${termAlias} ON ${termAlias}.id = ${this.scopedQuadColumn(column, scope)}`],
1350
+ sql: `${termAlias}.kind = 'literal'
1351
+ AND ${termAlias}.numeric_value IS NOT NULL
1352
+ AND ${termAlias}.numeric_value ${comparator} ?`,
1353
+ params: [numericValue],
1354
+ queryPlan: [`NumericRange(${key}${operator})`],
1355
+ };
1356
+ }
1357
+ const termAlias = this.scopedSqlName(`${column}_range_${operator.slice(1)}`, scope);
1358
+ return {
1359
+ joins: [` JOIN rdf_terms ${termAlias} ON ${termAlias}.id = ${this.scopedQuadColumn(column, scope)}`],
1360
+ sql: `${termAlias}.kind IN (${kinds.map(() => '?').join(', ')})
1361
+ AND ${termAlias}.value ${comparator} ?`,
1362
+ params: [...kinds, lexicalValue],
1363
+ queryPlan: [`LexicalRange(${key}${operator})`],
1364
+ };
1365
+ }
1366
+ termOperatorLexicalValue(value) {
1367
+ if (!value || typeof value !== 'object' || !('termType' in value)) {
1368
+ if (typeof value === 'string' || typeof value === 'number') {
1369
+ return String(value);
1370
+ }
1371
+ throw new Error('RdfQuadIndex range operators only support Term, string, or number values');
1372
+ }
1373
+ const term = value;
1374
+ switch (term.termType) {
1375
+ case 'NamedNode':
1376
+ case 'BlankNode':
1377
+ case 'Literal':
1378
+ return term.value;
1379
+ case 'DefaultGraph':
1380
+ return '';
1381
+ default:
1382
+ throw new Error(`RdfQuadIndex range operators do not support ${term.termType}`);
1383
+ }
1384
+ }
1385
+ termOperatorNumericValue(value) {
1386
+ if (typeof value === 'number') {
1387
+ return Number.isFinite(value) ? value : undefined;
1388
+ }
1389
+ if (!value || typeof value !== 'object' || !('termType' in value)) {
1390
+ return undefined;
1391
+ }
1392
+ const term = value;
1393
+ if (term.termType !== 'Literal' || !(0, RdfTermSemantics_1.isRdfNumericDatatype)(term.datatype.value)) {
1394
+ return undefined;
1395
+ }
1396
+ const parsed = (0, RdfTermSemantics_1.rdfNumericValue)(term.value);
1397
+ return Number.isFinite(parsed) ? parsed : undefined;
1398
+ }
1399
+ termKindsForPatternKey(key) {
1400
+ switch (key) {
1401
+ case 'object':
1402
+ return ['iri', 'literal', 'blank'];
1403
+ case 'subject':
1404
+ return ['iri', 'blank'];
1405
+ case 'graph':
1406
+ return ['iri', 'default_graph'];
1407
+ case 'predicate':
1408
+ return ['iri'];
1409
+ default: {
1410
+ const exhaustive = key;
1411
+ throw new Error(`Unsupported RDF pattern key: ${exhaustive}`);
1412
+ }
1413
+ }
1414
+ }
1415
+ termOperatorValueId(value, allowUnresolved) {
1416
+ if (!value || typeof value !== 'object' || !('termType' in value)) {
1417
+ throw new Error('RdfQuadIndex only supports Term values in first-stage exact scans');
1418
+ }
1419
+ const id = this.requireDictionary().find(value);
1420
+ if (id === undefined && !allowUnresolved) {
1421
+ return -1;
1422
+ }
1423
+ return id;
1424
+ }
1425
+ addTextSearchCondition(joins, fragments, params, queryPlan, key, column, operator, value, scope) {
1426
+ if (value === undefined) {
1427
+ return false;
1428
+ }
1429
+ if (typeof value !== 'string') {
1430
+ throw new Error(`RdfQuadIndex text search ${operator} only supports string values`);
1431
+ }
1432
+ const kind = this.termKindsForPatternKey(key);
1433
+ queryPlan.push(`TextSearch(${key}${operator})`);
1434
+ const condition = this.textSearchConditionJoin(kind, column, operator, value, scope);
1435
+ joins.push(condition.join);
1436
+ fragments.push(condition.sql);
1437
+ params.push(...condition.params);
1438
+ return true;
1439
+ }
1440
+ prefixSearchConditionJoin(key, column, prefix, scope) {
1441
+ const kind = this.termKindsForPatternKey(key);
1442
+ const alias = this.scopedSqlName(`prefix_${column}`, scope);
1443
+ return {
1444
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${this.scopedQuadColumn(column, scope)}`,
1445
+ sql: `${alias}.kind IN (${kind.map(() => '?').join(', ')})
1446
+ AND ${alias}.value >= ?
1447
+ AND ${alias}.value < ?`,
1448
+ params: [...kind, prefix, `${prefix}\uffff`],
1449
+ };
1450
+ }
1451
+ textSearchConditionJoin(kind, column, operator, value, scope) {
1452
+ const alias = this.scopedSqlName(`text_${column}_${operator.slice(1).toLowerCase()}`, scope);
1453
+ if (kind.length === 0) {
1454
+ return {
1455
+ join: ` JOIN rdf_terms ${alias} ON ${alias}.id = ${this.scopedQuadColumn(column, scope)}`,
1456
+ sql: '1 = 0',
1457
+ params: [],
1458
+ };
1459
+ }
1460
+ const kindPlaceholders = kind.map(() => '?').join(', ');
1461
+ const normalized = value.toLowerCase();
1462
+ const join = ` JOIN rdf_terms ${alias} ON ${alias}.id = ${this.scopedQuadColumn(column, scope)}`;
1463
+ switch (operator) {
1464
+ case '$contains':
1465
+ return {
1466
+ join,
1467
+ sql: `${alias}.kind IN (${kindPlaceholders})
1468
+ AND ${alias}.normalized_text LIKE ? ESCAPE '\\'
1469
+ AND instr(${alias}.value, ?) > 0`,
1470
+ params: [...kind, `%${escapeLikePattern(normalized)}%`, value],
1471
+ };
1472
+ case '$endsWith':
1473
+ return {
1474
+ join,
1475
+ sql: `${alias}.kind IN (${kindPlaceholders})
1476
+ AND ${alias}.normalized_text LIKE ? ESCAPE '\\'
1477
+ AND substr(${alias}.value, -length(?)) = ?`,
1478
+ params: [...kind, `%${escapeLikePattern(normalized)}`, value, value],
1479
+ };
1480
+ case '$regex':
1481
+ return this.regexTextSearchConditionJoin(kind, alias, column, value, scope);
1482
+ default:
1483
+ throw new Error(`Unsupported RDF text search operator: ${operator}`);
1484
+ }
1485
+ }
1486
+ regexTextSearchConditionJoin(kind, alias, column, pattern, scope) {
1487
+ const ids = this.requireDictionary().idsByNormalizedTextRegex(kind, pattern);
1488
+ const candidateTable = this.populateRegexCandidateTable(column, ids, scope);
1489
+ const candidateAlias = `${alias}_candidates`;
1490
+ const join = ` JOIN rdf_terms ${alias} ON ${alias}.id = ${this.scopedQuadColumn(column, scope)}
1491
+ JOIN ${candidateTable} ${candidateAlias} ON ${candidateAlias}.id = ${alias}.id`;
1492
+ if (ids.length === 0) {
1493
+ return { join, sql: '1 = 0', params: [] };
1494
+ }
1495
+ const kindPlaceholders = kind.map(() => '?').join(', ');
1496
+ return {
1497
+ join,
1498
+ sql: `${alias}.kind IN (${kindPlaceholders})`,
1499
+ params: kind,
1500
+ };
1501
+ }
1502
+ populateRegexCandidateTable(column, ids, scope) {
1503
+ const tableName = this.scopedSqlName(`rdf_regex_candidates_${column}`, scope);
1504
+ this.populateCandidateTable(tableName, ids);
1505
+ return tableName;
1506
+ }
1507
+ populateTermInCandidateTable(column, operator, ids, scope) {
1508
+ const tableName = this.scopedSqlName(`rdf_term_${operator}_candidates_${column}`, scope);
1509
+ this.populateCandidateTable(tableName, ids);
1510
+ return tableName;
1511
+ }
1512
+ scopedQuadColumn(column, scope) {
1513
+ return `${scope?.quadAlias ?? 'rdf_quads'}.${column}`;
1514
+ }
1515
+ scopedSqlName(base, scope) {
1516
+ const name = scope?.aliasPrefix ? `${scope.aliasPrefix}_${base}` : base;
1517
+ return name.replace(/[^A-Za-z0-9_]/g, '_');
1518
+ }
1519
+ buildTupleConstraintJoin(source) {
1520
+ const columns = Array.from(new Set(source.columns));
1521
+ if (columns.length === 0) {
1522
+ return { join: '' };
1523
+ }
1524
+ const candidateColumns = columns.map((key) => TERM_COLUMN[key]);
1525
+ const tableName = `rdf_tuple_values_${candidateColumns.join('_')}`;
1526
+ this.populateTupleConstraintTable(tableName, columns, source.rows);
1527
+ const alias = 'tuple_values';
1528
+ const onClause = columns
1529
+ .map((key) => `${alias}.${TERM_COLUMN[key]} = rdf_quads.${TERM_COLUMN[key]}`)
1530
+ .join(' AND ');
1531
+ return {
1532
+ join: ` JOIN ${tableName} ${alias} ON ${onClause}`,
1533
+ };
1534
+ }
1535
+ populateTupleConstraintTable(tableName, columns, rows) {
1536
+ const db = this.requireDb();
1537
+ const columnDefs = columns.map((key) => `${TERM_COLUMN[key]} INTEGER NOT NULL`).join(', ');
1538
+ const primaryKey = columns.map((key) => TERM_COLUMN[key]).join(', ');
1539
+ db.exec(`CREATE TEMP TABLE IF NOT EXISTS ${tableName} (${columnDefs}, PRIMARY KEY (${primaryKey}))`);
1540
+ db.prepare(`DELETE FROM ${tableName}`).run();
1541
+ const valueRows = rows
1542
+ .map((row) => columns.map((key) => this.termIdForTupleConstraint(row[key])))
1543
+ .filter((ids) => ids.every((id) => id !== undefined));
1544
+ if (valueRows.length === 0) {
1545
+ return;
1546
+ }
1547
+ const insertColumns = columns.map((key) => TERM_COLUMN[key]).join(', ');
1548
+ const placeholders = `(${columns.map(() => '?').join(', ')})`;
1549
+ const insert = db.prepare(`INSERT OR IGNORE INTO ${tableName} (${insertColumns}) VALUES ${placeholders}`);
1550
+ for (const valueRow of valueRows) {
1551
+ insert.run(...valueRow);
1552
+ }
1553
+ }
1554
+ termIdForTupleConstraint(term) {
1555
+ if (!term) {
1556
+ return undefined;
1557
+ }
1558
+ return this.requireDictionary().find(term);
1559
+ }
1560
+ populateCandidateTable(tableName, ids) {
1561
+ const db = this.requireDb();
1562
+ db.exec(`CREATE TEMP TABLE IF NOT EXISTS ${tableName} (id INTEGER PRIMARY KEY)`);
1563
+ db.prepare(`DELETE FROM ${tableName}`).run();
1564
+ for (let offset = 0; offset < ids.length; offset += 500) {
1565
+ const batch = ids.slice(offset, offset + 500);
1566
+ db.prepare(`INSERT OR IGNORE INTO ${tableName} (id) VALUES ${batch.map(() => '(?)').join(', ')}`).run(...batch);
1567
+ }
1568
+ }
1569
+ chooseIndex(columns) {
1570
+ const has = (column) => columns.has(column);
1571
+ if (has('graph_id') && has('subject_id'))
1572
+ return 'GSPO';
1573
+ if (has('graph_id') && has('predicate_id'))
1574
+ return 'GPOS';
1575
+ if (has('subject_id') && has('predicate_id'))
1576
+ return 'SPOG';
1577
+ if (has('subject_id') && has('object_id'))
1578
+ return 'SOPG';
1579
+ if (has('predicate_id') && has('object_id'))
1580
+ return 'POSG';
1581
+ if (has('object_id') && has('subject_id'))
1582
+ return 'OSPG';
1583
+ if (has('subject_id'))
1584
+ return 'SPOG';
1585
+ if (has('predicate_id'))
1586
+ return 'POSG';
1587
+ if (has('object_id'))
1588
+ return 'OSPG';
1589
+ if (has('graph_id'))
1590
+ return 'GSPO';
1591
+ return 'full-scan';
1592
+ }
1593
+ exactTermPattern(pattern) {
1594
+ const ids = {};
1595
+ const columns = new Set();
1596
+ for (const key of TERM_KEYS) {
1597
+ const match = pattern[key];
1598
+ if (!match) {
1599
+ continue;
1600
+ }
1601
+ if (!(0, types_1.isTerm)(match)) {
1602
+ return undefined;
1603
+ }
1604
+ const id = this.requireDictionary().find(match);
1605
+ if (id === undefined) {
1606
+ return {
1607
+ ids: { [key]: -1 },
1608
+ cacheKey: `${key}=-1`,
1609
+ indexChoice: this.chooseIndex(new Set([TERM_COLUMN[key]])),
1610
+ };
1611
+ }
1612
+ ids[key] = id;
1613
+ columns.add(TERM_COLUMN[key]);
1614
+ }
1615
+ return {
1616
+ ids,
1617
+ cacheKey: TERM_KEYS
1618
+ .map((key) => `${key}:${ids[key] ?? '*'}`)
1619
+ .join('|'),
1620
+ indexChoice: this.chooseIndex(columns),
1621
+ };
1622
+ }
1623
+ countExactTermPattern(ids, indexChoice) {
1624
+ if (Object.values(ids).some((id) => id === -1)) {
1625
+ return {
1626
+ rows: 0,
1627
+ source: 'exact-count',
1628
+ indexChoice,
1629
+ };
1630
+ }
1631
+ const conditions = [];
1632
+ const params = [];
1633
+ for (const key of TERM_KEYS) {
1634
+ const id = ids[key];
1635
+ if (id === undefined) {
1636
+ continue;
1637
+ }
1638
+ conditions.push(`${TERM_COLUMN[key]} = ?`);
1639
+ params.push(id);
1640
+ }
1641
+ const sql = `SELECT COUNT(*) AS count FROM rdf_quads${conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''}`;
1642
+ const row = this.requireDb().prepare(sql).get(...params);
1643
+ return {
1644
+ rows: row?.count ?? 0,
1645
+ source: 'exact-count',
1646
+ indexChoice,
1647
+ };
1648
+ }
1649
+ countExactDistinctTermPattern(ids, indexChoice, distinctKey) {
1650
+ if (Object.values(ids).some((id) => id === -1)) {
1651
+ return {
1652
+ rows: 0,
1653
+ source: 'exact-distinct-count',
1654
+ indexChoice,
1655
+ };
1656
+ }
1657
+ const conditions = [];
1658
+ const params = [];
1659
+ for (const key of TERM_KEYS) {
1660
+ const id = ids[key];
1661
+ if (id === undefined) {
1662
+ continue;
1663
+ }
1664
+ conditions.push(`${TERM_COLUMN[key]} = ?`);
1665
+ params.push(id);
1666
+ }
1667
+ const sql = `SELECT COUNT(DISTINCT ${TERM_COLUMN[distinctKey]}) AS count FROM rdf_quads${conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''}`;
1668
+ const row = this.requireDb().prepare(sql).get(...params);
1669
+ return {
1670
+ rows: row?.count ?? 0,
1671
+ source: 'exact-distinct-count',
1672
+ indexChoice,
1673
+ };
1674
+ }
1675
+ countExactDistinctTupleTermPattern(ids, indexChoice, distinctKeys) {
1676
+ if (Object.values(ids).some((id) => id === -1)) {
1677
+ return {
1678
+ rows: 0,
1679
+ source: 'exact-distinct-tuple-count',
1680
+ indexChoice,
1681
+ };
1682
+ }
1683
+ const conditions = [];
1684
+ const params = [];
1685
+ for (const key of TERM_KEYS) {
1686
+ const id = ids[key];
1687
+ if (id === undefined) {
1688
+ continue;
1689
+ }
1690
+ conditions.push(`${TERM_COLUMN[key]} = ?`);
1691
+ params.push(id);
1692
+ }
1693
+ const tupleProjection = distinctKeys.map((key) => TERM_COLUMN[key]).join(', ');
1694
+ const sql = `
1695
+ SELECT COUNT(*) AS count
1696
+ FROM (
1697
+ SELECT DISTINCT ${tupleProjection}
1698
+ FROM rdf_quads${conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''}
1699
+ ) distinct_tuple
1700
+ `;
1701
+ const row = this.requireDb().prepare(sql).get(...params);
1702
+ return {
1703
+ rows: row?.count ?? 0,
1704
+ source: 'exact-distinct-tuple-count',
1705
+ indexChoice,
1706
+ };
1707
+ }
1708
+ countDistinctPattern(pattern, distinctKey) {
1709
+ const { joins, whereClause, params, unresolved } = this.buildWhereClause(pattern, true);
1710
+ if (unresolved) {
1711
+ return 0;
1712
+ }
1713
+ const row = this.requireDb()
1714
+ .prepare(`SELECT COUNT(DISTINCT rdf_quads.${TERM_COLUMN[distinctKey]}) AS count FROM rdf_quads${joins}${whereClause}`)
1715
+ .get(...params);
1716
+ return row?.count ?? 0;
1717
+ }
1718
+ countDistinctTuplePattern(pattern, distinctKeys) {
1719
+ const { joins, whereClause, params, unresolved } = this.buildWhereClause(pattern, true);
1720
+ if (unresolved) {
1721
+ return 0;
1722
+ }
1723
+ const tupleProjection = distinctKeys.map((key) => `rdf_quads.${TERM_COLUMN[key]}`).join(', ');
1724
+ const row = this.requireDb()
1725
+ .prepare(`
1726
+ SELECT COUNT(*) AS count
1727
+ FROM (
1728
+ SELECT DISTINCT ${tupleProjection}
1729
+ FROM rdf_quads${joins}${whereClause}
1730
+ ) distinct_tuple
1731
+ `)
1732
+ .get(...params);
1733
+ return row?.count ?? 0;
1734
+ }
1735
+ buildOrderClause(options) {
1736
+ if (!options?.order || options.order.length === 0) {
1737
+ return { joins: '', orderBy: '' };
1738
+ }
1739
+ const columns = options.order.map((termName) => TERM_COLUMN[termName]);
1740
+ if (columns.some((column) => !column)) {
1741
+ throw new Error(`Unsupported RDF quad order fields: ${options.order.join(', ')}`);
1742
+ }
1743
+ const joins = options.order.map((termName, index) => {
1744
+ const column = TERM_COLUMN[termName];
1745
+ const direction = options.orderDirections?.[index] ?? (options.reverse ? 'desc' : 'asc');
1746
+ return {
1747
+ join: ` JOIN rdf_terms order_t${index} ON order_t${index}.id = rdf_quads.${column}`,
1748
+ order: `order_t${index}.value${direction === 'desc' ? ' DESC' : ''}`,
1749
+ };
1750
+ });
1751
+ return {
1752
+ joins: joins.map((join) => join.join).join(''),
1753
+ orderBy: ` ORDER BY ${joins.map((join) => join.order).join(', ')}`,
1754
+ };
1755
+ }
1756
+ paginationParamCount(options) {
1757
+ return (options?.limit !== undefined ? 1 : 0) + (options?.offset !== undefined ? 1 : 0);
1758
+ }
1759
+ rowsToQuads(rows) {
1760
+ const dictionary = this.requireDictionary();
1761
+ const termMap = dictionary.rowsForIds(rows.flatMap((row) => [
1762
+ row.graph_id,
1763
+ row.subject_id,
1764
+ row.predicate_id,
1765
+ row.object_id,
1766
+ ]));
1767
+ return rows.map((row) => n3_1.DataFactory.quad(this.requiredTerm(termMap, row.subject_id), this.requiredTerm(termMap, row.predicate_id), this.requiredTerm(termMap, row.object_id), this.requiredTerm(termMap, row.graph_id)));
1768
+ }
1769
+ requiredTerm(termMap, id) {
1770
+ const term = termMap.get(id);
1771
+ if (!term) {
1772
+ throw new Error(`RDF term not found while reading quad index: ${id}`);
1773
+ }
1774
+ return term;
1775
+ }
1776
+ metrics(indexChoice, matchedRows, returnedRows, start, queryPlan) {
1777
+ return {
1778
+ engine: 'solid-rdf',
1779
+ indexChoice,
1780
+ matchedRows,
1781
+ returnedRows,
1782
+ durationMs: Date.now() - start,
1783
+ queryPlan,
1784
+ };
1785
+ }
1786
+ requireDb() {
1787
+ if (!this.db) {
1788
+ throw new Error('RdfQuadIndex is not open');
1789
+ }
1790
+ return this.db;
1791
+ }
1792
+ requireDictionary() {
1793
+ if (!this.dictionary) {
1794
+ throw new Error('RdfQuadIndex is not open');
1795
+ }
1796
+ return this.dictionary;
1797
+ }
1798
+ }
1799
+ exports.RdfQuadIndex = RdfQuadIndex;
1800
+ function sumSpaceObjects(objects, kind) {
1801
+ return objects
1802
+ .filter((object) => object.kind === kind)
1803
+ .reduce((sum, object) => sum + object.bytes, 0);
1804
+ }
1805
+ function rdfSpaceObjectKind(name, schemaType, tableName) {
1806
+ if (schemaType === 'table' && name.startsWith('rdf_')) {
1807
+ return 'table';
1808
+ }
1809
+ if (schemaType === 'index' && (name.startsWith('rdf_') || tableName?.startsWith('rdf_'))) {
1810
+ return 'index';
1811
+ }
1812
+ if (name.startsWith('sqlite_')) {
1813
+ return 'internal';
1814
+ }
1815
+ return 'unknown';
1816
+ }
1817
+ function rdfTermKind(term) {
1818
+ switch (term.termType) {
1819
+ case 'NamedNode':
1820
+ return 'iri';
1821
+ case 'BlankNode':
1822
+ return 'blank';
1823
+ case 'Literal':
1824
+ return 'literal';
1825
+ case 'DefaultGraph':
1826
+ return 'default_graph';
1827
+ default:
1828
+ return 'iri';
1829
+ }
1830
+ }
1831
+ function escapeLikePattern(value) {
1832
+ return value.replace(/[\\%_]/g, (match) => `\\${match}`);
1833
+ }
1834
+ function uniqueNumbers(values) {
1835
+ return [...new Set(values)];
1836
+ }
1837
+ function uniquePatternKeys(values) {
1838
+ return TERM_KEYS.filter((key) => values.includes(key));
1839
+ }
1840
+ //# sourceMappingURL=RdfQuadIndex.js.map