@comake/skl-js-engine 1.4.2 → 1.5.0

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 (33) hide show
  1. package/dist/SklEngine.d.ts +9 -0
  2. package/dist/SklEngine.d.ts.map +1 -1
  3. package/dist/SklEngine.js +122 -12
  4. package/dist/SklEngine.js.map +1 -1
  5. package/dist/SklEngineOptions.d.ts +32 -0
  6. package/dist/SklEngineOptions.d.ts.map +1 -1
  7. package/dist/SklEngineOptions.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/storage/FindOptionsTypes.d.ts +4 -1
  13. package/dist/storage/FindOptionsTypes.d.ts.map +1 -1
  14. package/dist/storage/FindOptionsTypes.js.map +1 -1
  15. package/dist/storage/query-adapter/sparql/SparqlQueryBuilder.d.ts.map +1 -1
  16. package/dist/storage/query-adapter/sparql/SparqlQueryBuilder.js +21 -0
  17. package/dist/storage/query-adapter/sparql/SparqlQueryBuilder.js.map +1 -1
  18. package/dist/tools/explain-findall-sparql.d.ts +2 -0
  19. package/dist/tools/explain-findall-sparql.d.ts.map +1 -0
  20. package/dist/tools/explain-findall-sparql.js +303 -0
  21. package/dist/tools/explain-findall-sparql.js.map +1 -0
  22. package/dist/util/ReadCacheHelper.d.ts +14 -0
  23. package/dist/util/ReadCacheHelper.d.ts.map +1 -0
  24. package/dist/util/ReadCacheHelper.js +61 -0
  25. package/dist/util/ReadCacheHelper.js.map +1 -0
  26. package/package.json +2 -1
  27. package/src/SklEngine.ts +150 -13
  28. package/src/SklEngineOptions.ts +40 -0
  29. package/src/index.ts +1 -0
  30. package/src/storage/FindOptionsTypes.ts +4 -1
  31. package/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts +22 -0
  32. package/src/tools/explain-findall-sparql.ts +387 -0
  33. package/src/util/ReadCacheHelper.ts +64 -0
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /* eslint-disable */
7
+ /* eslint-comments/no-unlimited-disable */
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const data_model_1 = __importDefault(require("@rdfjs/data-model"));
11
+ const sparqljs_1 = require("sparqljs");
12
+ const SparqlQueryBuilder_1 = require("../storage/query-adapter/sparql/SparqlQueryBuilder");
13
+ const SparqlUtil_1 = require("../util/SparqlUtil");
14
+ const Util_1 = require("../util/Util");
15
+ function usageAndExit(exitCode) {
16
+ const msg = [
17
+ 'Usage:',
18
+ ' node dist/tools/explain-findall-sparql.js --input <file.json> [--format text|json] [--simulate-entity-values N]',
19
+ '',
20
+ 'Input JSON can be either:',
21
+ ' 1) a full FindAllOptions object: { "where": { ... }, "relations": { ... }, "order": { ... }, "limit": 10, ... }',
22
+ ' 2) a FindOptionsWhere object (treated as { where: <object> })',
23
+ '',
24
+ 'Notes:',
25
+ ' - This utility does not query a SPARQL endpoint. When findAll would inject VALUES(?entity) from a pre-SELECT,',
26
+ ' you can pass --simulate-entity-values N to show an example VALUES block with N placeholder IRIs.',
27
+ ''
28
+ ].join('\n');
29
+ console.error(msg);
30
+ process.exit(exitCode);
31
+ }
32
+ function parseArgs(argv) {
33
+ const out = {
34
+ format: 'text',
35
+ simulateEntityValues: 0
36
+ };
37
+ for (let i = 0; i < argv.length; i += 1) {
38
+ const arg = argv[i];
39
+ if (arg === '--help' || arg === '-h') {
40
+ usageAndExit(0);
41
+ }
42
+ if (arg === '--input' || arg === '-i') {
43
+ out.input = argv[i + 1];
44
+ i += 1;
45
+ continue;
46
+ }
47
+ if (arg === '--format' || arg === '-f') {
48
+ const v = argv[i + 1];
49
+ if (v !== 'text' && v !== 'json') {
50
+ console.error(`Invalid --format: ${String(v)}`);
51
+ usageAndExit(2);
52
+ }
53
+ out.format = v;
54
+ i += 1;
55
+ continue;
56
+ }
57
+ if (arg === '--simulate-entity-values') {
58
+ const v = Number.parseInt(argv[i + 1] ?? '', 10);
59
+ if (!Number.isFinite(v) || v < 0) {
60
+ console.error(`Invalid --simulate-entity-values: ${argv[i + 1]}`);
61
+ usageAndExit(2);
62
+ }
63
+ out.simulateEntityValues = v;
64
+ i += 1;
65
+ continue;
66
+ }
67
+ if (arg.startsWith('-')) {
68
+ console.error(`Unknown arg: ${arg}`);
69
+ usageAndExit(2);
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+ function readJsonFile(p) {
75
+ const abs = path_1.default.isAbsolute(p) ? p : path_1.default.join(process.cwd(), p);
76
+ const raw = fs_1.default.readFileSync(abs, 'utf8');
77
+ try {
78
+ return JSON.parse(raw);
79
+ }
80
+ catch (e) {
81
+ throw new Error(`Failed to parse JSON from ${abs}: ${e.message}`);
82
+ }
83
+ }
84
+ function coerceSparqlVariable(v) {
85
+ if (!v)
86
+ return undefined;
87
+ if (typeof v === 'string') {
88
+ const name = v.startsWith('?') ? v.slice(1) : v;
89
+ return data_model_1.default.variable(name);
90
+ }
91
+ if (typeof v === 'object' && v.termType === 'Variable' && typeof v.value === 'string') {
92
+ return v;
93
+ }
94
+ return undefined;
95
+ }
96
+ function normalizeFindAllOptions(input) {
97
+ const asOptions = typeof input === 'object' &&
98
+ input !== null &&
99
+ ('where' in input || 'select' in input || 'relations' in input || 'order' in input || 'limit' in input || 'offset' in input || 'subQueries' in input);
100
+ const options = asOptions ? input : { where: input };
101
+ // Allow simple JSON-friendly forms for variables.
102
+ const group = coerceSparqlVariable(options.group);
103
+ const entitySelectVariable = coerceSparqlVariable(options.entitySelectVariable);
104
+ const subQueries = Array.isArray(options.subQueries)
105
+ ? options.subQueries.map((sq) => {
106
+ const select = Array.isArray(sq?.select) ? sq.select.map(coerceSparqlVariable).filter(Boolean) : sq?.select;
107
+ return { ...sq, ...(select ? { select } : {}) };
108
+ })
109
+ : options.subQueries;
110
+ return {
111
+ ...options,
112
+ ...(group ? { group } : {}),
113
+ ...(entitySelectVariable ? { entitySelectVariable } : {}),
114
+ ...(subQueries ? { subQueries } : {})
115
+ };
116
+ }
117
+ function stringifyQuery(query) {
118
+ const gen = new sparqljs_1.Generator();
119
+ return gen.stringify(query);
120
+ }
121
+ function buildEntitySelectQueryForFindAll(selectQueryData, options) {
122
+ // Mirrors SparqlQueryAdapter.buildFindAllQueryData() for the entitySelectQuery creation.
123
+ const wherePatterns = [...selectQueryData.where, ...selectQueryData.graphWhere];
124
+ wherePatterns.push({
125
+ type: 'bgp',
126
+ triples: [
127
+ {
128
+ subject: SparqlUtil_1.entityVariable,
129
+ predicate: SparqlUtil_1.rdfTypeNamedNode,
130
+ object: SparqlUtil_1.rdfTypeVariable
131
+ }
132
+ ]
133
+ });
134
+ const entitySelectVariable = options?.entitySelectVariable ?? SparqlUtil_1.entityVariable;
135
+ const groupBy = (0, Util_1.ensureArray)(selectQueryData?.group ?? options?.group ?? []);
136
+ groupBy.push(entitySelectVariable);
137
+ // All non-aggregated variables in SELECT must be in GROUP BY
138
+ for (const selectVariable of selectQueryData.selectVariables ?? []) {
139
+ const expr = selectVariable.expression;
140
+ if (!('aggregation' in expr) && expr?.constructor?.name === 'Variable') {
141
+ groupBy.push(expr);
142
+ }
143
+ }
144
+ if (selectQueryData.where.length === 0) {
145
+ return undefined;
146
+ }
147
+ return (0, SparqlUtil_1.createSparqlSelectQuery)([
148
+ entitySelectVariable,
149
+ // (GROUP_CONCAT(DISTINCT str(?rdfType); SEPARATOR = " | ") AS ?rdfTypes)
150
+ {
151
+ expression: {
152
+ type: 'aggregate',
153
+ aggregation: 'group_concat',
154
+ separator: ' | ',
155
+ distinct: true,
156
+ expression: {
157
+ type: 'operation',
158
+ operator: 'STR',
159
+ args: [SparqlUtil_1.rdfTypeVariable]
160
+ }
161
+ },
162
+ variable: SparqlUtil_1.rdfTypesVariable
163
+ },
164
+ ...(selectQueryData.selectVariables?.map(({ variable, expression }) => {
165
+ if (!expression)
166
+ return variable;
167
+ return { variable, expression };
168
+ }) ?? [])
169
+ ], wherePatterns, selectQueryData.orders, groupBy, options?.limit, options?.offset);
170
+ }
171
+ function simulateEntityIdValues(n) {
172
+ const values = [];
173
+ for (let i = 1; i <= n; i += 1) {
174
+ values.push(data_model_1.default.namedNode(`urn:skl-dry-run:entity-${i}`));
175
+ }
176
+ return values;
177
+ }
178
+ async function main() {
179
+ const args = parseArgs(process.argv.slice(2));
180
+ if (!args.input) {
181
+ usageAndExit(2);
182
+ }
183
+ const input = readJsonFile(args.input);
184
+ const options = normalizeFindAllOptions(input);
185
+ const qb = new SparqlQueryBuilder_1.SparqlQueryBuilder();
186
+ const queryData = qb.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, options);
187
+ const selectQueryData = qb.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, {
188
+ ...options,
189
+ relations: undefined
190
+ });
191
+ // Mirrors SparqlQueryAdapter.buildFindAllQueryData() for the relations union tweak.
192
+ if ((queryData?.relationsQueryData?.unionPatterns ?? []).length > 0) {
193
+ queryData?.relationsQueryData?.unionPatterns.push((0, SparqlUtil_1.createSparqlGraphPattern)(SparqlUtil_1.entityVariable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])]));
194
+ }
195
+ const entitySelectQuery = buildEntitySelectQueryForFindAll(selectQueryData, options);
196
+ const wouldPreSelectForOrderingAndValues = queryData.orders.length > 0 && options?.limit !== 1 && !!entitySelectQuery;
197
+ const steps = [];
198
+ if (entitySelectQuery) {
199
+ const notes = [];
200
+ if (wouldPreSelectForOrderingAndValues) {
201
+ notes.push('In SparqlQueryAdapter.findAll(), this SELECT is executed first to compute entity ordering.', 'Its results are then used to inject a VALUES block over ?entity into the main CONSTRUCT query.');
202
+ if (options?.limit === undefined) {
203
+ notes.push('Warning: limit is undefined, so this pre-SELECT may return all matching entity IDs (potentially huge).');
204
+ }
205
+ else {
206
+ notes.push(`VALUES size is <= limit (${options.limit}).`);
207
+ }
208
+ }
209
+ else {
210
+ notes.push('In some cases (relations/type constraints), findAll executes this SELECT to support framing/type handling.');
211
+ notes.push('In that path, it also embeds this SELECT as a subquery inside the CONSTRUCT.');
212
+ }
213
+ steps.push({
214
+ kind: 'select',
215
+ name: 'Entity Pre-SELECT',
216
+ wouldExecute: wouldPreSelectForOrderingAndValues,
217
+ sparql: stringifyQuery(entitySelectQuery),
218
+ notes
219
+ });
220
+ }
221
+ let constructWhere = queryData.graphWhere;
222
+ let constructNotes = [];
223
+ if (wouldPreSelectForOrderingAndValues) {
224
+ if (args.simulateEntityValues > 0) {
225
+ const variableValueFilters = (0, SparqlUtil_1.createValuesPatternsForVariables)({
226
+ [SparqlUtil_1.entityVariable.value]: simulateEntityIdValues(args.simulateEntityValues)
227
+ });
228
+ constructWhere = [...variableValueFilters, ...constructWhere];
229
+ constructNotes = [
230
+ `This CONSTRUCT includes a simulated VALUES(?${SparqlUtil_1.entityVariable.value}) with ${args.simulateEntityValues} placeholder IRIs.`,
231
+ 'In real execution, those VALUES come from the pre-SELECT results.'
232
+ ];
233
+ }
234
+ else {
235
+ constructNotes = [
236
+ 'In real execution, this CONSTRUCT is preceded by the pre-SELECT and will have a VALUES(?entity) block injected.',
237
+ 'Pass --simulate-entity-values N to show an example VALUES block.'
238
+ ];
239
+ }
240
+ }
241
+ else if (entitySelectQuery) {
242
+ // Mirrors the else-if path where the entity select is embedded into the CONSTRUCT.
243
+ const entitySelectGroupQuery = (0, SparqlUtil_1.createSparqlSelectGroup)([entitySelectQuery]);
244
+ constructWhere = [entitySelectGroupQuery, ...constructWhere];
245
+ constructNotes = ['This CONSTRUCT embeds the entity SELECT as a subquery (GROUP pattern) in its WHERE.'];
246
+ }
247
+ const constructQuery = qb.buildConstructFromEntitySelectQuery(constructWhere, queryData.graphSelectionTriples, options?.select, queryData.selectVariables);
248
+ steps.push({
249
+ kind: 'construct',
250
+ name: 'Main CONSTRUCT',
251
+ wouldExecute: true,
252
+ sparql: stringifyQuery(constructQuery),
253
+ ...(constructNotes.length > 0 ? { notes: constructNotes } : {})
254
+ });
255
+ if (wouldPreSelectForOrderingAndValues && options?.limit === undefined) {
256
+ steps.push({
257
+ kind: 'note',
258
+ name: 'Performance Hint',
259
+ wouldExecute: false,
260
+ notes: [
261
+ 'findAll() with no limit triggers a pre-SELECT that can return a very large ID set, then injects it into VALUES(?entity).',
262
+ 'This often causes slow queries due to large intermediate results and huge VALUES blocks.'
263
+ ]
264
+ });
265
+ }
266
+ const plan = {
267
+ input,
268
+ normalizedOptions: options,
269
+ steps,
270
+ meta: {
271
+ wouldPreSelectForOrderingAndValues,
272
+ hasRelations: (queryData?.relationsQueryData?.unionPatterns ?? []).length > 0,
273
+ hasTypeConstraint: options?.where?.type !== undefined,
274
+ limit: options?.limit,
275
+ offset: options?.offset
276
+ }
277
+ };
278
+ if (args.format === 'json') {
279
+ console.log(JSON.stringify(plan, null, 2));
280
+ return;
281
+ }
282
+ // Text
283
+ for (const step of plan.steps) {
284
+ if (step.kind === 'note') {
285
+ console.log(`\n# ${step.name}\n`);
286
+ for (const n of step.notes)
287
+ console.log(`- ${n}`);
288
+ continue;
289
+ }
290
+ console.log(`\n# ${step.name}\n`);
291
+ if ('notes' in step && step.notes?.length) {
292
+ for (const n of step.notes)
293
+ console.log(`- ${n}`);
294
+ console.log('');
295
+ }
296
+ console.log(step.sparql.trim());
297
+ }
298
+ }
299
+ main().catch((err) => {
300
+ console.error(err);
301
+ process.exit(1);
302
+ });
303
+ //# sourceMappingURL=explain-findall-sparql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain-findall-sparql.js","sourceRoot":"","sources":["../../src/tools/explain-findall-sparql.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable */\n/* eslint-comments/no-unlimited-disable */\nimport fs from 'fs';\nimport path from 'path';\n\nimport DataFactory from '@rdfjs/data-model';\nimport type { NamedNode, Literal } from '@rdfjs/types';\nimport type { AggregateExpression, ConstructQuery, Pattern, SelectQuery, Variable } from 'sparqljs';\nimport { Generator } from 'sparqljs';\n\nimport type { FindAllOptions, FindOptionsWhere } from '../storage/FindOptionsTypes';\nimport { SparqlQueryBuilder } from '../storage/query-adapter/sparql/SparqlQueryBuilder';\nimport {\n createSparqlBasicGraphPattern,\n createSparqlGraphPattern,\n createSparqlSelectQuery,\n createValuesPatternsForVariables,\n entityGraphTriple,\n entityVariable,\n rdfTypeNamedNode,\n rdfTypesVariable,\n rdfTypeVariable,\n createSparqlSelectGroup\n} from '../util/SparqlUtil';\nimport { ensureArray } from '../util/Util';\n\ntype OutputFormat = 'text' | 'json';\n\ntype PlanStep =\n | {\n kind: 'select';\n name: string;\n wouldExecute: boolean;\n sparql: string;\n notes?: string[];\n }\n | {\n kind: 'construct';\n name: string;\n wouldExecute: boolean;\n sparql: string;\n notes?: string[];\n }\n | {\n kind: 'note';\n name: string;\n wouldExecute: boolean;\n notes: string[];\n };\n\ninterface ExplainPlan {\n input: unknown;\n normalizedOptions: FindAllOptions;\n steps: PlanStep[];\n meta: Record<string, any>;\n}\n\nfunction usageAndExit(exitCode: number): never {\n const msg = [\n 'Usage:',\n ' node dist/tools/explain-findall-sparql.js --input <file.json> [--format text|json] [--simulate-entity-values N]',\n '',\n 'Input JSON can be either:',\n ' 1) a full FindAllOptions object: { \"where\": { ... }, \"relations\": { ... }, \"order\": { ... }, \"limit\": 10, ... }',\n ' 2) a FindOptionsWhere object (treated as { where: <object> })',\n '',\n 'Notes:',\n ' - This utility does not query a SPARQL endpoint. When findAll would inject VALUES(?entity) from a pre-SELECT,',\n ' you can pass --simulate-entity-values N to show an example VALUES block with N placeholder IRIs.',\n ''\n ].join('\\n');\n\n console.error(msg);\n\n process.exit(exitCode);\n}\n\nfunction parseArgs(argv: string[]): { input?: string; format: OutputFormat; simulateEntityValues: number } {\n const out: { input?: string; format: OutputFormat; simulateEntityValues: number } = {\n format: 'text',\n simulateEntityValues: 0\n };\n\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === '--help' || arg === '-h') {\n usageAndExit(0);\n }\n if (arg === '--input' || arg === '-i') {\n out.input = argv[i + 1];\n i += 1;\n continue;\n }\n if (arg === '--format' || arg === '-f') {\n const v = argv[i + 1] as OutputFormat | undefined;\n if (v !== 'text' && v !== 'json') {\n console.error(`Invalid --format: ${String(v)}`);\n usageAndExit(2);\n }\n out.format = v;\n i += 1;\n continue;\n }\n if (arg === '--simulate-entity-values') {\n const v = Number.parseInt(argv[i + 1] ?? '', 10);\n if (!Number.isFinite(v) || v < 0) {\n console.error(`Invalid --simulate-entity-values: ${argv[i + 1]}`);\n usageAndExit(2);\n }\n out.simulateEntityValues = v;\n i += 1;\n continue;\n }\n if (arg.startsWith('-')) {\n console.error(`Unknown arg: ${arg}`);\n usageAndExit(2);\n }\n }\n\n return out;\n}\n\nfunction readJsonFile(p: string): any {\n const abs = path.isAbsolute(p) ? p : path.join(process.cwd(), p);\n const raw = fs.readFileSync(abs, 'utf8');\n try {\n return JSON.parse(raw);\n } catch (e) {\n throw new Error(`Failed to parse JSON from ${abs}: ${(e as Error).message}`);\n }\n}\n\nfunction coerceSparqlVariable(v: any): Variable | undefined {\n if (!v) return undefined;\n if (typeof v === 'string') {\n const name = v.startsWith('?') ? v.slice(1) : v;\n return DataFactory.variable(name) as any;\n }\n if (typeof v === 'object' && v.termType === 'Variable' && typeof v.value === 'string') {\n return v;\n }\n return undefined;\n}\n\nfunction normalizeFindAllOptions(input: any): FindAllOptions {\n const asOptions =\n typeof input === 'object' &&\n input !== null &&\n ('where' in input || 'select' in input || 'relations' in input || 'order' in input || 'limit' in input || 'offset' in input || 'subQueries' in input);\n\n const options: FindAllOptions = asOptions ? input : { where: input as FindOptionsWhere };\n\n // Allow simple JSON-friendly forms for variables.\n const group = coerceSparqlVariable((options as any).group);\n const entitySelectVariable = coerceSparqlVariable((options as any).entitySelectVariable);\n\n const subQueries = Array.isArray((options as any).subQueries)\n ? (options as any).subQueries.map((sq: any) => {\n const select = Array.isArray(sq?.select) ? sq.select.map(coerceSparqlVariable).filter(Boolean) : sq?.select;\n return { ...sq, ...(select ? { select } : {}) };\n })\n : (options as any).subQueries;\n\n return {\n ...options,\n ...(group ? { group } : {}),\n ...(entitySelectVariable ? { entitySelectVariable } : {}),\n ...(subQueries ? { subQueries } : {})\n };\n}\n\nfunction stringifyQuery(query: SelectQuery | ConstructQuery): string {\n const gen = new Generator();\n return gen.stringify(query);\n}\n\nfunction buildEntitySelectQueryForFindAll(\n selectQueryData: ReturnType<SparqlQueryBuilder['buildEntitySelectPatternsFromOptions']>,\n options?: FindAllOptions\n): SelectQuery | undefined {\n // Mirrors SparqlQueryAdapter.buildFindAllQueryData() for the entitySelectQuery creation.\n const wherePatterns: Pattern[] = [...selectQueryData.where, ...selectQueryData.graphWhere];\n wherePatterns.push({\n type: 'bgp',\n triples: [\n {\n subject: entityVariable,\n predicate: rdfTypeNamedNode,\n object: rdfTypeVariable\n }\n ]\n });\n\n const entitySelectVariable = options?.entitySelectVariable ?? entityVariable;\n const groupBy = ensureArray(selectQueryData?.group ?? options?.group ?? []);\n groupBy.push(entitySelectVariable);\n\n // All non-aggregated variables in SELECT must be in GROUP BY\n for (const selectVariable of selectQueryData.selectVariables ?? []) {\n const expr = selectVariable.expression as any;\n if (!('aggregation' in (expr as AggregateExpression)) && expr?.constructor?.name === 'Variable') {\n groupBy.push(expr as Variable);\n }\n }\n\n if (selectQueryData.where.length === 0) {\n return undefined;\n }\n\n return createSparqlSelectQuery(\n [\n entitySelectVariable,\n // (GROUP_CONCAT(DISTINCT str(?rdfType); SEPARATOR = \" | \") AS ?rdfTypes)\n {\n expression: {\n type: 'aggregate',\n aggregation: 'group_concat',\n separator: ' | ',\n distinct: true,\n expression: {\n type: 'operation',\n operator: 'STR',\n args: [rdfTypeVariable]\n }\n },\n variable: rdfTypesVariable\n } as any,\n ...(selectQueryData.selectVariables?.map(({ variable, expression }) => {\n if (!expression) return variable as any;\n return { variable, expression } as any;\n }) ?? [])\n ] as any,\n wherePatterns,\n selectQueryData.orders,\n groupBy as any,\n options?.limit,\n options?.offset\n );\n}\n\nfunction simulateEntityIdValues(n: number): (NamedNode | Literal)[] {\n const values: NamedNode[] = [];\n for (let i = 1; i <= n; i += 1) {\n values.push(DataFactory.namedNode(`urn:skl-dry-run:entity-${i}`));\n }\n return values;\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n if (!args.input) {\n usageAndExit(2);\n }\n\n const input = readJsonFile(args.input);\n const options = normalizeFindAllOptions(input);\n\n const qb = new SparqlQueryBuilder();\n const queryData = qb.buildEntitySelectPatternsFromOptions(entityVariable, options);\n const selectQueryData = qb.buildEntitySelectPatternsFromOptions(entityVariable, {\n ...options,\n relations: undefined\n });\n\n // Mirrors SparqlQueryAdapter.buildFindAllQueryData() for the relations union tweak.\n if ((queryData?.relationsQueryData?.unionPatterns ?? []).length > 0) {\n queryData?.relationsQueryData?.unionPatterns.push(createSparqlGraphPattern(entityVariable, [createSparqlBasicGraphPattern([entityGraphTriple])]));\n }\n\n const entitySelectQuery = buildEntitySelectQueryForFindAll(selectQueryData, options);\n const wouldPreSelectForOrderingAndValues = queryData.orders.length > 0 && options?.limit !== 1 && !!entitySelectQuery;\n\n const steps: PlanStep[] = [];\n\n if (entitySelectQuery) {\n const notes: string[] = [];\n if (wouldPreSelectForOrderingAndValues) {\n notes.push(\n 'In SparqlQueryAdapter.findAll(), this SELECT is executed first to compute entity ordering.',\n 'Its results are then used to inject a VALUES block over ?entity into the main CONSTRUCT query.'\n );\n if (options?.limit === undefined) {\n notes.push('Warning: limit is undefined, so this pre-SELECT may return all matching entity IDs (potentially huge).');\n } else {\n notes.push(`VALUES size is <= limit (${options.limit}).`);\n }\n } else {\n notes.push('In some cases (relations/type constraints), findAll executes this SELECT to support framing/type handling.');\n notes.push('In that path, it also embeds this SELECT as a subquery inside the CONSTRUCT.');\n }\n\n steps.push({\n kind: 'select',\n name: 'Entity Pre-SELECT',\n wouldExecute: wouldPreSelectForOrderingAndValues,\n sparql: stringifyQuery(entitySelectQuery),\n notes\n });\n }\n\n let constructWhere = queryData.graphWhere;\n let constructNotes: string[] = [];\n\n if (wouldPreSelectForOrderingAndValues) {\n if (args.simulateEntityValues > 0) {\n const variableValueFilters = createValuesPatternsForVariables({\n [entityVariable.value]: simulateEntityIdValues(args.simulateEntityValues) as any\n });\n constructWhere = [...variableValueFilters, ...constructWhere];\n constructNotes = [\n `This CONSTRUCT includes a simulated VALUES(?${entityVariable.value}) with ${args.simulateEntityValues} placeholder IRIs.`,\n 'In real execution, those VALUES come from the pre-SELECT results.'\n ];\n } else {\n constructNotes = [\n 'In real execution, this CONSTRUCT is preceded by the pre-SELECT and will have a VALUES(?entity) block injected.',\n 'Pass --simulate-entity-values N to show an example VALUES block.'\n ];\n }\n } else if (entitySelectQuery) {\n // Mirrors the else-if path where the entity select is embedded into the CONSTRUCT.\n const entitySelectGroupQuery = createSparqlSelectGroup([entitySelectQuery]);\n constructWhere = [entitySelectGroupQuery, ...constructWhere];\n constructNotes = ['This CONSTRUCT embeds the entity SELECT as a subquery (GROUP pattern) in its WHERE.'];\n }\n\n const constructQuery = qb.buildConstructFromEntitySelectQuery(constructWhere, queryData.graphSelectionTriples, options?.select, queryData.selectVariables);\n\n steps.push({\n kind: 'construct',\n name: 'Main CONSTRUCT',\n wouldExecute: true,\n sparql: stringifyQuery(constructQuery),\n ...(constructNotes.length > 0 ? { notes: constructNotes } : {})\n });\n\n if (wouldPreSelectForOrderingAndValues && options?.limit === undefined) {\n steps.push({\n kind: 'note',\n name: 'Performance Hint',\n wouldExecute: false,\n notes: [\n 'findAll() with no limit triggers a pre-SELECT that can return a very large ID set, then injects it into VALUES(?entity).',\n 'This often causes slow queries due to large intermediate results and huge VALUES blocks.'\n ]\n });\n }\n\n const plan: ExplainPlan = {\n input,\n normalizedOptions: options,\n steps,\n meta: {\n wouldPreSelectForOrderingAndValues,\n hasRelations: (queryData?.relationsQueryData?.unionPatterns ?? []).length > 0,\n hasTypeConstraint: options?.where?.type !== undefined,\n limit: options?.limit,\n offset: options?.offset\n }\n };\n\n if (args.format === 'json') {\n console.log(JSON.stringify(plan, null, 2));\n return;\n }\n\n // Text\n for (const step of plan.steps) {\n if (step.kind === 'note') {\n console.log(`\\n# ${step.name}\\n`);\n for (const n of step.notes) console.log(`- ${n}`);\n continue;\n }\n console.log(`\\n# ${step.name}\\n`);\n if ('notes' in step && step.notes?.length) {\n for (const n of step.notes) console.log(`- ${n}`);\n console.log('');\n }\n console.log(step.sparql.trim());\n }\n}\n\nmain().catch((err: unknown) => {\n console.error(err);\n\n process.exit(1);\n});\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ReadCacheOperation } from '../SklEngineOptions';
2
+ export interface BuildReadCacheKeyInput {
3
+ operation: ReadCacheOperation;
4
+ args: readonly unknown[];
5
+ endpointUrl?: string;
6
+ namespace?: string;
7
+ keyHint?: string;
8
+ }
9
+ export declare function buildReadCacheKey(input: BuildReadCacheKeyInput): string;
10
+ export declare class ReadCacheSingleflight {
11
+ private readonly inflight;
12
+ do<T>(key: string, fn: () => Promise<T>): Promise<T>;
13
+ }
14
+ //# sourceMappingURL=ReadCacheHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadCacheHelper.d.ts","sourceRoot":"","sources":["../../src/util/ReadCacheHelper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,kBAAkB,CAAC;IAC9B,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CASvE;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEnD,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAiBlE"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ReadCacheSingleflight = exports.buildReadCacheKey = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ function stableStringify(value) {
9
+ if (value === null)
10
+ return 'null';
11
+ const valueType = typeof value;
12
+ if (valueType === 'number' || valueType === 'boolean')
13
+ return String(value);
14
+ if (valueType === 'string')
15
+ return JSON.stringify(value);
16
+ if (valueType !== 'object') {
17
+ const serializedPrimitive = JSON.stringify(value);
18
+ return serializedPrimitive ?? String(value);
19
+ }
20
+ if (Array.isArray(value)) {
21
+ return `[${value.map(stableStringify).join(',')}]`;
22
+ }
23
+ const objectValue = value;
24
+ // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
25
+ const keys = Object.keys(objectValue).sort();
26
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(objectValue[key])}`).join(',')}}`;
27
+ }
28
+ function buildReadCacheKey(input) {
29
+ const keyParts = {
30
+ namespace: input.namespace ?? '',
31
+ endpointUrl: input.endpointUrl ?? '',
32
+ operation: input.operation,
33
+ keyHint: input.keyHint ?? '',
34
+ args: input.args
35
+ };
36
+ return crypto_1.default.createHash('sha256').update(stableStringify(keyParts)).digest('hex');
37
+ }
38
+ exports.buildReadCacheKey = buildReadCacheKey;
39
+ class ReadCacheSingleflight {
40
+ constructor() {
41
+ this.inflight = new Map();
42
+ }
43
+ async do(key, fn) {
44
+ const existing = this.inflight.get(key);
45
+ if (existing) {
46
+ return existing;
47
+ }
48
+ const promise = (async () => {
49
+ try {
50
+ return await fn();
51
+ }
52
+ finally {
53
+ this.inflight.delete(key);
54
+ }
55
+ })();
56
+ this.inflight.set(key, promise);
57
+ return promise;
58
+ }
59
+ }
60
+ exports.ReadCacheSingleflight = ReadCacheSingleflight;
61
+ //# sourceMappingURL=ReadCacheHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadCacheHelper.js","sourceRoot":"","sources":["../../src/util/ReadCacheHelper.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAW5B,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAElC,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;IAC/B,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5E,IAAI,SAAS,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,SAAS,KAAK,QAAQ,EAAE;QAC1B,MAAM,mBAAmB,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAClD,OAAO,mBAAmB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;KAC7C;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACxB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;KACpD;IAED,MAAM,WAAW,GAAG,KAAgC,CAAC;IACrD,yEAAyE;IACzE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC3G,CAAC;AAED,SAAgB,iBAAiB,CAAC,KAA6B;IAC7D,MAAM,QAAQ,GAAG;QACf,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;QAChC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;QACpC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC;IACF,OAAO,gBAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACrF,CAAC;AATD,8CASC;AAED,MAAa,qBAAqB;IAAlC;QACmB,aAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAmBlE,CAAC;IAjBQ,KAAK,CAAC,EAAE,CAAI,GAAW,EAAE,EAAoB;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAA2B,CAAC;QAClE,IAAI,QAAQ,EAAE;YACZ,OAAO,QAAQ,CAAC;SACjB;QAED,MAAM,OAAO,GAAG,CAAC,KAAK,IAAe,EAAE;YACrC,IAAI;gBACF,OAAO,MAAM,EAAE,EAAE,CAAC;aACnB;oBAAS;gBACR,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;aAC3B;QACH,CAAC,CAAC,EAAE,CAAC;QAEL,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAA2B,CAAC,CAAC;QACpD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AApBD,sDAoBC","sourcesContent":["import crypto from 'crypto';\nimport type { ReadCacheOperation } from '../SklEngineOptions';\n\nexport interface BuildReadCacheKeyInput {\n operation: ReadCacheOperation;\n args: readonly unknown[];\n endpointUrl?: string;\n namespace?: string;\n keyHint?: string;\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null) return 'null';\n\n const valueType = typeof value;\n if (valueType === 'number' || valueType === 'boolean') return String(value);\n if (valueType === 'string') return JSON.stringify(value);\n if (valueType !== 'object') {\n const serializedPrimitive = JSON.stringify(value);\n return serializedPrimitive ?? String(value);\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(',')}]`;\n }\n\n const objectValue = value as Record<string, unknown>;\n // eslint-disable-next-line @typescript-eslint/require-array-sort-compare\n const keys = Object.keys(objectValue).sort();\n return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(objectValue[key])}`).join(',')}}`;\n}\n\nexport function buildReadCacheKey(input: BuildReadCacheKeyInput): string {\n const keyParts = {\n namespace: input.namespace ?? '',\n endpointUrl: input.endpointUrl ?? '',\n operation: input.operation,\n keyHint: input.keyHint ?? '',\n args: input.args\n };\n return crypto.createHash('sha256').update(stableStringify(keyParts)).digest('hex');\n}\n\nexport class ReadCacheSingleflight {\n private readonly inflight = new Map<string, Promise<unknown>>();\n\n public async do<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const existing = this.inflight.get(key) as Promise<T> | undefined;\n if (existing) {\n return existing;\n }\n\n const promise = (async(): Promise<T> => {\n try {\n return await fn();\n } finally {\n this.inflight.delete(key);\n }\n })();\n\n this.inflight.set(key, promise as Promise<unknown>);\n return promise;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comake/skl-js-engine",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Standard Knowledge Language Javascript Engine",
5
5
  "keywords": [
6
6
  "skl",
@@ -24,6 +24,7 @@
24
24
  "build": "tsc -p tsconfig.json && cp executor.js dist/",
25
25
  "build:dev": "tsc -p tsconfig.dev.json && cp executor.js dist/",
26
26
  "build:watch": "tsc -p tsconfig.dev.json --watch",
27
+ "sparql:explain:findAll": "ts-node src/tools/explain-findall-sparql.ts",
27
28
  "jest": "jest --coverage",
28
29
  "lint": "eslint . --cache --ignore-path .gitignore --ignore-pattern test/ --max-warnings 0 --fix",
29
30
  "test": "npm run test:ts && npm run jest",