@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.
- package/config/cli.json +1 -1
- package/config/cloud.json +54 -22
- package/config/local.json +56 -12
- package/config/resolver.json +10 -2
- package/config/xpod.base.json +50 -0
- package/config/xpod.json +8 -8
- package/dist/agents/config/resolve.js +10 -10
- package/dist/agents/config/resolve.js.map +1 -1
- package/dist/api/chatkit/index.d.ts +1 -1
- package/dist/api/chatkit/index.js.map +1 -1
- package/dist/api/chatkit/pod-store.d.ts +14 -11
- package/dist/api/chatkit/pod-store.js +114 -78
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/runtime/AcpAgentRuntime.js +1 -1
- package/dist/api/chatkit/runtime/AcpAgentRuntime.js.map +1 -1
- package/dist/api/chatkit/service.js +1 -1
- package/dist/api/chatkit/service.js.map +1 -1
- package/dist/api/chatkit/types.d.ts +11 -11
- package/dist/api/chatkit/types.js +3 -3
- package/dist/api/chatkit/types.js.map +1 -1
- package/dist/api/container/cloud.js +0 -8
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/index.js +2 -1
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +0 -7
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +3 -17
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +0 -2
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/PodManagementHandler.d.ts +3 -0
- package/dist/api/handlers/PodManagementHandler.js +71 -1
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/RunHandler.js +5 -5
- package/dist/api/handlers/RunHandler.js.map +1 -1
- package/dist/api/runs/AgentRuntimeTypes.d.ts +7 -8
- package/dist/api/runs/AgentRuntimeTypes.js.map +1 -1
- package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
- package/dist/api/runs/ManagedRunWorker.d.ts +1 -1
- package/dist/api/runs/ManagedRunWorker.js +6 -6
- package/dist/api/runs/ManagedRunWorker.js.map +1 -1
- package/dist/api/runs/PiAgentRuntimeDriver.d.ts +16 -1
- package/dist/api/runs/PiAgentRuntimeDriver.js +182 -23
- package/dist/api/runs/PiAgentRuntimeDriver.js.map +1 -1
- package/dist/api/runs/RunStateCenter.d.ts +3 -3
- package/dist/api/runs/RunStateCenter.js +13 -13
- package/dist/api/runs/RunStateCenter.js.map +1 -1
- package/dist/api/runs/store.d.ts +4 -4
- package/dist/api/runs/store.js +2 -2
- package/dist/api/runs/store.js.map +1 -1
- package/dist/api/service/VectorStoreService.d.ts +1 -1
- package/dist/api/service/VectorStoreService.js +16 -16
- package/dist/api/service/VectorStoreService.js.map +1 -1
- package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
- package/dist/api/tasks/TaskMaterializer.d.ts +3 -3
- package/dist/api/tasks/TaskMaterializer.js +11 -11
- package/dist/api/tasks/TaskMaterializer.js.map +1 -1
- package/dist/api/tasks/TaskService.d.ts +3 -3
- package/dist/api/tasks/TaskService.js +11 -7
- package/dist/api/tasks/TaskService.js.map +1 -1
- package/dist/api/tasks/store.d.ts +10 -4
- package/dist/api/tasks/store.js +14 -4
- package/dist/api/tasks/store.js.map +1 -1
- package/dist/api/workspace/types.d.ts +3 -3
- package/dist/api/workspace/types.js +6 -6
- package/dist/api/workspace/types.js.map +1 -1
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/start.js +9 -3
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/components/components.jsonld +8 -2
- package/dist/components/context.jsonld +308 -51
- package/dist/http/search/SearchHttpHandler.js +8 -8
- package/dist/http/search/SearchHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/PodLookupRepository.d.ts +11 -1
- package/dist/identity/drizzle/PodLookupRepository.js +95 -4
- package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
- package/dist/identity/drizzle/db.js +4 -43
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +0 -5
- package/dist/identity/drizzle/schema.pg.js +2 -16
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +19 -176
- package/dist/identity/drizzle/schema.sqlite.js +2 -16
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +4 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +7 -7
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +6 -6
- package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +4 -4
- package/dist/identity/oidc/AutoDetectOidcHandler.js +6 -6
- package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
- package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +6 -6
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +37 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +211 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +158 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/main.js +8 -2
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.d.ts +3 -4
- package/dist/provision/ProvisionPodCreator.js +8 -13
- package/dist/provision/ProvisionPodCreator.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.jsonld +7 -7
- package/dist/runtime/Proxy.d.ts +0 -1
- package/dist/runtime/Proxy.js +0 -9
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts +1 -0
- package/dist/runtime/bootstrap.js +5 -2
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/runtime/css-process.d.ts +12 -4
- package/dist/runtime/css-process.js +61 -14
- package/dist/runtime/css-process.js.map +1 -1
- package/dist/runtime/oidc-issuer.d.ts +3 -2
- package/dist/runtime/oidc-issuer.js +3 -2
- package/dist/runtime/oidc-issuer.js.map +1 -1
- package/dist/runtime/runtime-types.d.ts +1 -0
- package/dist/runtime/runtime-types.js.map +1 -1
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.d.ts +21 -0
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.js +38 -0
- package/dist/solidfs/LocalFirstRdfRepresentationResolver.js.map +1 -0
- package/dist/solidfs/LocalSolidFS.d.ts +18 -0
- package/dist/solidfs/LocalSolidFS.js +539 -0
- package/dist/solidfs/LocalSolidFS.js.map +1 -0
- package/dist/solidfs/PodSolidFsHttpClient.d.ts +16 -0
- package/dist/solidfs/PodSolidFsHttpClient.js +93 -0
- package/dist/solidfs/PodSolidFsHttpClient.js.map +1 -0
- package/dist/solidfs/PodSolidFsHydrator.d.ts +27 -0
- package/dist/solidfs/PodSolidFsHydrator.js +127 -0
- package/dist/solidfs/PodSolidFsHydrator.js.map +1 -0
- package/dist/solidfs/PodSolidFsSyncer.d.ts +21 -0
- package/dist/solidfs/PodSolidFsSyncer.js +78 -0
- package/dist/solidfs/PodSolidFsSyncer.js.map +1 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.d.ts +22 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.js +131 -0
- package/dist/solidfs/RdfIndexSolidFsSyncer.js.map +1 -0
- package/dist/solidfs/index.d.ts +7 -0
- package/dist/solidfs/index.js +24 -0
- package/dist/solidfs/index.js.map +1 -0
- package/dist/solidfs/types.d.ts +131 -0
- package/dist/solidfs/types.js +19 -0
- package/dist/solidfs/types.js.map +1 -0
- package/dist/storage/RepresentationPartialConvertingStore.js +6 -13
- package/dist/storage/RepresentationPartialConvertingStore.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.d.ts +4 -0
- package/dist/storage/SparqlUpdateResourceStore.js +13 -0
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.jsonld +26 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +85 -4
- package/dist/storage/accessors/MixDataAccessor.js +511 -16
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +176 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.d.ts +7 -0
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +72 -4
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.jsonld +24 -0
- package/dist/storage/quint/BaseQuintStore.d.ts +3 -0
- package/dist/storage/quint/BaseQuintStore.js +51 -27
- package/dist/storage/quint/BaseQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.d.ts +1 -0
- package/dist/storage/quint/PgQuintStore.js +50 -32
- package/dist/storage/quint/PgQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.jsonld +4 -3
- package/dist/storage/quint/SqliteQuintStore.d.ts +5 -0
- package/dist/storage/quint/SqliteQuintStore.js +100 -0
- package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
- package/dist/storage/quint/SqliteQuintStore.jsonld +20 -0
- package/dist/storage/quint/types.d.ts +16 -0
- package/dist/storage/quint/types.js.map +1 -1
- package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +55 -0
- package/dist/storage/rdf/Rdf3xTripleIndex.js +1235 -0
- package/dist/storage/rdf/Rdf3xTripleIndex.js.map +1 -0
- package/dist/storage/rdf/RdfContentTypes.d.ts +9 -0
- package/dist/storage/rdf/RdfContentTypes.js +79 -0
- package/dist/storage/rdf/RdfContentTypes.js.map +1 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +79 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.js +2705 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -0
- package/dist/storage/rdf/RdfQuadIndex.d.ts +98 -0
- package/dist/storage/rdf/RdfQuadIndex.js +1840 -0
- package/dist/storage/rdf/RdfQuadIndex.js.map +1 -0
- package/dist/storage/rdf/RdfQuadIndex.jsonld +416 -0
- package/dist/storage/rdf/RdfShadowComparator.d.ts +12 -0
- package/dist/storage/rdf/RdfShadowComparator.js +47 -0
- package/dist/storage/rdf/RdfShadowComparator.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.d.ts +147 -0
- package/dist/storage/rdf/RdfSparqlAdapter.js +2420 -0
- package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.jsonld +414 -0
- package/dist/storage/rdf/RdfTermDictionary.d.ts +27 -0
- package/dist/storage/rdf/RdfTermDictionary.js +352 -0
- package/dist/storage/rdf/RdfTermDictionary.js.map +1 -0
- package/dist/storage/rdf/RdfTermDictionary.jsonld +114 -0
- package/dist/storage/rdf/RdfTermSemantics.d.ts +6 -0
- package/dist/storage/rdf/RdfTermSemantics.js +40 -0
- package/dist/storage/rdf/RdfTermSemantics.js.map +1 -0
- package/dist/storage/rdf/RdfTextIndex.d.ts +23 -0
- package/dist/storage/rdf/RdfTextIndex.js +569 -0
- package/dist/storage/rdf/RdfTextIndex.js.map +1 -0
- package/dist/storage/rdf/RdfVectorIndex.d.ts +22 -0
- package/dist/storage/rdf/RdfVectorIndex.js +631 -0
- package/dist/storage/rdf/RdfVectorIndex.js.map +1 -0
- package/dist/storage/rdf/RdfXmlSerializer.d.ts +2 -0
- package/dist/storage/rdf/RdfXmlSerializer.js +123 -0
- package/dist/storage/rdf/RdfXmlSerializer.js.map +1 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.d.ts +58 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.js +202 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.js.map +1 -0
- package/dist/storage/rdf/ShadowRdfQuintStore.jsonld +308 -0
- package/dist/storage/rdf/SolidRdfEngine.d.ts +56 -0
- package/dist/storage/rdf/SolidRdfEngine.js +292 -0
- package/dist/storage/rdf/SolidRdfEngine.js.map +1 -0
- package/dist/storage/rdf/SolidRdfEngine.jsonld +372 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +92 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.js +477 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -0
- package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +257 -0
- package/dist/storage/rdf/index.d.ts +15 -0
- package/dist/storage/rdf/index.js +61 -0
- package/dist/storage/rdf/index.js.map +1 -0
- package/dist/storage/rdf/models-benchmark.d.ts +260 -0
- package/dist/storage/rdf/models-benchmark.js +1405 -0
- package/dist/storage/rdf/models-benchmark.js.map +1 -0
- package/dist/storage/rdf/types.d.ts +726 -0
- package/dist/storage/rdf/types.js +3 -0
- package/dist/storage/rdf/types.js.map +1 -0
- package/dist/storage/rdf/types.jsonld +316 -0
- package/dist/storage/vector/VectorIndexingListener.d.ts +5 -5
- package/dist/storage/vector/VectorIndexingListener.js +19 -19
- package/dist/storage/vector/VectorIndexingListener.js.map +1 -1
- package/package.json +3 -2
- package/templates/pod/acp/.acr.hbs +39 -0
- package/templates/pod/acp/README.acr +18 -0
- package/templates/pod/acp/profile/card.acr +22 -0
- package/templates/pod/base/README$.md.hbs +27 -0
- package/templates/pod/base/profile/card$.ttl.hbs +13 -0
- package/templates/pod/wac/.acl.hbs +26 -0
- package/templates/pod/wac/README.acl.hbs +14 -0
- package/templates/pod/wac/profile/card.acl.hbs +19 -0
- package/dist/api/handlers/WebIdProfileHandler.d.ts +0 -16
- package/dist/api/handlers/WebIdProfileHandler.js +0 -423
- package/dist/api/handlers/WebIdProfileHandler.js.map +0 -1
- package/dist/identity/drizzle/WebIdProfileRepository.d.ts +0 -63
- package/dist/identity/drizzle/WebIdProfileRepository.js +0 -168
- package/dist/identity/drizzle/WebIdProfileRepository.js.map +0 -1
- package/dist/identity/drizzle/WebIdProfileRepository.jsonld +0 -112
- package/dist/storage/quint/BaseQuintStore.jsonld +0 -257
|
@@ -0,0 +1,2705 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RdfLocalQueryEngine = void 0;
|
|
4
|
+
exports.variable = variable;
|
|
5
|
+
const n3_1 = require("n3");
|
|
6
|
+
const types_1 = require("../quint/types");
|
|
7
|
+
const RdfTermSemantics_1 = require("./RdfTermSemantics");
|
|
8
|
+
const TERM_KEYS = ['graph', 'subject', 'predicate', 'object'];
|
|
9
|
+
const PLANNER_SAMPLE_BINDINGS = 16;
|
|
10
|
+
const XSD_DECIMAL = 'http://www.w3.org/2001/XMLSchema#decimal';
|
|
11
|
+
const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
|
|
12
|
+
class RdfLocalQueryEngine {
|
|
13
|
+
constructor(index, textIndex, vectorIndex, rdf3xPrimaryIndex) {
|
|
14
|
+
this.index = index;
|
|
15
|
+
this.textIndex = textIndex;
|
|
16
|
+
this.vectorIndex = vectorIndex;
|
|
17
|
+
this.rdf3xPrimaryIndex = rdf3xPrimaryIndex;
|
|
18
|
+
}
|
|
19
|
+
query(query) {
|
|
20
|
+
const start = Date.now();
|
|
21
|
+
const metrics = {
|
|
22
|
+
engine: 'solid-rdf',
|
|
23
|
+
plan: [],
|
|
24
|
+
scannedRows: 0,
|
|
25
|
+
joinedRows: 0,
|
|
26
|
+
returnedRows: 0,
|
|
27
|
+
durationMs: 0,
|
|
28
|
+
indexChoices: [],
|
|
29
|
+
cardinalityEstimates: 0,
|
|
30
|
+
distinctCardinalityEstimates: 0,
|
|
31
|
+
searchCardinalityEstimates: 0,
|
|
32
|
+
filtersApplied: 0,
|
|
33
|
+
filtersPushedDown: 0,
|
|
34
|
+
};
|
|
35
|
+
const hasNonPatternSource = (query.values?.length ?? 0) > 0
|
|
36
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
37
|
+
|| (query.vectorSearch?.length ?? 0) > 0;
|
|
38
|
+
const requiredPatterns = query.patterns.length > 0
|
|
39
|
+
? query.patterns
|
|
40
|
+
: query.unions?.length || hasNonPatternSource
|
|
41
|
+
? []
|
|
42
|
+
: [{}];
|
|
43
|
+
let bindings = [{}];
|
|
44
|
+
const requiredFilters = query.filters ?? [];
|
|
45
|
+
const aggregates = queryAggregates(query);
|
|
46
|
+
const singleScanPushdown = this.singleScanPushdown(query, requiredPatterns, requiredFilters);
|
|
47
|
+
const countPushdown = this.countPushdown(query, requiredPatterns, requiredFilters);
|
|
48
|
+
const groupAggregatePushdown = this.groupAggregatePushdown(query, requiredPatterns, requiredFilters, aggregates);
|
|
49
|
+
const joinCountPushdown = this.joinCountPushdown(query, requiredPatterns, requiredFilters, aggregates);
|
|
50
|
+
const joinBasicAggregatePushdown = this.joinBasicAggregatePushdown(query, requiredPatterns, requiredFilters, aggregates);
|
|
51
|
+
let groupedAggregatePushed = false;
|
|
52
|
+
if (countPushdown) {
|
|
53
|
+
const countEstimate = countPushdown.distinctKey
|
|
54
|
+
? this.index.countDistinct(countPushdown.pattern, countPushdown.distinctKey)
|
|
55
|
+
: undefined;
|
|
56
|
+
const count = countEstimate?.rows ?? this.index.count(countPushdown.pattern);
|
|
57
|
+
const result = countLiteral(count);
|
|
58
|
+
metrics.scannedRows = count;
|
|
59
|
+
metrics.joinedRows = count;
|
|
60
|
+
metrics.returnedRows = 1;
|
|
61
|
+
metrics.durationMs = Date.now() - start;
|
|
62
|
+
metrics.indexChoices.push('count');
|
|
63
|
+
metrics.filtersPushedDown += countPushdown.pushedDownFilters;
|
|
64
|
+
metrics.plan.push(`IndexCount(${describePattern(requiredPatterns[0])})`);
|
|
65
|
+
metrics.plan.push(countPushdown.distinctKey ? 'Aggregate(count-distinct-index)' : 'Aggregate(count-index)');
|
|
66
|
+
return {
|
|
67
|
+
bindings: [{ [countPushdown.as]: result }],
|
|
68
|
+
count,
|
|
69
|
+
metrics,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (joinCountPushdown) {
|
|
73
|
+
const scan = this.index.countJoinPatterns(joinCountPushdown.patterns, {
|
|
74
|
+
aggregates,
|
|
75
|
+
});
|
|
76
|
+
const firstAggregate = aggregates[0];
|
|
77
|
+
const firstCount = firstAggregate ? Number(scan.bindings[0]?.[firstAggregate.as]?.value ?? 0) : 0;
|
|
78
|
+
metrics.scannedRows = scan.metrics.matchedRows;
|
|
79
|
+
metrics.joinedRows = scan.metrics.matchedRows;
|
|
80
|
+
metrics.returnedRows = scan.bindings.length;
|
|
81
|
+
metrics.durationMs = Date.now() - start;
|
|
82
|
+
metrics.indexChoices.push(scan.metrics.indexChoice);
|
|
83
|
+
metrics.filtersPushedDown += joinCountPushdown.pushedDownFilters;
|
|
84
|
+
if (joinCountPushdown.reorderPlan) {
|
|
85
|
+
metrics.plan.push(joinCountPushdown.reorderPlan);
|
|
86
|
+
}
|
|
87
|
+
metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
|
|
88
|
+
metrics.plan.push(`IndexJoinCount(${joinCountPushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
|
|
89
|
+
metrics.plan.push(aggregatePlan(aggregates, false));
|
|
90
|
+
metrics.plan.push(aggregates.some((aggregate) => aggregate.distinct)
|
|
91
|
+
? 'Aggregate(join-count-distinct-index)'
|
|
92
|
+
: 'Aggregate(join-count-index)');
|
|
93
|
+
return {
|
|
94
|
+
bindings: scan.bindings,
|
|
95
|
+
count: firstCount,
|
|
96
|
+
metrics,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (joinBasicAggregatePushdown) {
|
|
100
|
+
const scan = this.index.aggregateJoinPatterns(joinBasicAggregatePushdown.patterns, {
|
|
101
|
+
aggregates,
|
|
102
|
+
});
|
|
103
|
+
metrics.scannedRows = scan.metrics.matchedRows;
|
|
104
|
+
metrics.joinedRows = scan.metrics.matchedRows;
|
|
105
|
+
metrics.returnedRows = scan.bindings.length;
|
|
106
|
+
metrics.durationMs = Date.now() - start;
|
|
107
|
+
metrics.indexChoices.push(scan.metrics.indexChoice);
|
|
108
|
+
metrics.filtersPushedDown += joinBasicAggregatePushdown.pushedDownFilters;
|
|
109
|
+
if (joinBasicAggregatePushdown.reorderPlan) {
|
|
110
|
+
metrics.plan.push(joinBasicAggregatePushdown.reorderPlan);
|
|
111
|
+
}
|
|
112
|
+
metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
|
|
113
|
+
metrics.plan.push(`IndexJoinAggregate(${joinBasicAggregatePushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
|
|
114
|
+
metrics.plan.push(aggregatePlan(aggregates, false));
|
|
115
|
+
metrics.plan.push(aggregates.length > 1
|
|
116
|
+
? 'Aggregate(join-basic-multi-index)'
|
|
117
|
+
: 'Aggregate(join-basic-index)');
|
|
118
|
+
return {
|
|
119
|
+
bindings: scan.bindings,
|
|
120
|
+
metrics,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const remainingSources = buildRequiredSources(requiredPatterns, query);
|
|
124
|
+
const requiredBgpPushdown = this.requiredBgpPushdown(query, requiredPatterns, requiredFilters);
|
|
125
|
+
if (groupAggregatePushdown) {
|
|
126
|
+
const scan = groupAggregatePushdown.countOnly
|
|
127
|
+
? this.index.groupCountJoinPatterns(groupAggregatePushdown.patterns, {
|
|
128
|
+
groupBy: query.groupBy ?? [],
|
|
129
|
+
aggregates,
|
|
130
|
+
...(groupAggregatePushdown.having ? { having: groupAggregatePushdown.having } : {}),
|
|
131
|
+
...(groupAggregatePushdown.orderPushed ? { orderBy: query.orderBy } : {}),
|
|
132
|
+
...(groupAggregatePushdown.paginationPushed && query.limit !== undefined ? { limit: Math.max(0, query.limit) } : {}),
|
|
133
|
+
...(groupAggregatePushdown.paginationPushed && query.offset !== undefined ? { offset: Math.max(0, query.offset) } : {}),
|
|
134
|
+
})
|
|
135
|
+
: this.index.groupAggregateJoinPatterns(groupAggregatePushdown.patterns, {
|
|
136
|
+
groupBy: query.groupBy ?? [],
|
|
137
|
+
aggregates,
|
|
138
|
+
...(groupAggregatePushdown.having ? { having: groupAggregatePushdown.having } : {}),
|
|
139
|
+
...(groupAggregatePushdown.orderPushed ? { orderBy: query.orderBy } : {}),
|
|
140
|
+
...(groupAggregatePushdown.paginationPushed && query.limit !== undefined ? { limit: Math.max(0, query.limit) } : {}),
|
|
141
|
+
...(groupAggregatePushdown.paginationPushed && query.offset !== undefined ? { offset: Math.max(0, query.offset) } : {}),
|
|
142
|
+
});
|
|
143
|
+
const groupPlanPrefix = groupAggregatePushdown.countOnly ? 'IndexGroupCount' : 'IndexGroupAggregate';
|
|
144
|
+
bindings = scan.bindings;
|
|
145
|
+
metrics.scannedRows += scan.metrics.matchedRows;
|
|
146
|
+
metrics.joinedRows = scan.metrics.matchedRows;
|
|
147
|
+
metrics.indexChoices.push(scan.metrics.indexChoice);
|
|
148
|
+
metrics.filtersPushedDown += groupAggregatePushdown.pushedDownFilters;
|
|
149
|
+
metrics.filtersPushedDown += groupAggregatePushdown.pushedDownHaving;
|
|
150
|
+
if (groupAggregatePushdown.reorderPlan) {
|
|
151
|
+
metrics.plan.push(groupAggregatePushdown.reorderPlan);
|
|
152
|
+
}
|
|
153
|
+
metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
|
|
154
|
+
metrics.plan.push(`${groupPlanPrefix}(${(query.groupBy ?? []).map((variableName) => `?${variableName}`).join(',')})`);
|
|
155
|
+
if (groupAggregatePushdown.pushedDownHaving > 0) {
|
|
156
|
+
metrics.plan.push(`${groupPlanPrefix}Having(${(query.having ?? []).map(describeFilter).join(',')})`);
|
|
157
|
+
}
|
|
158
|
+
if (groupAggregatePushdown.orderPushed) {
|
|
159
|
+
metrics.plan.push(`${groupPlanPrefix}Order(${describeQueryOrder(query.orderBy ?? [])})`);
|
|
160
|
+
}
|
|
161
|
+
if (groupAggregatePushdown.paginationPushed) {
|
|
162
|
+
metrics.plan.push(`${groupPlanPrefix}Limit`);
|
|
163
|
+
}
|
|
164
|
+
metrics.plan.push(aggregatePlan(aggregates, true));
|
|
165
|
+
metrics.plan.push(groupAggregatePushdown.countOnly
|
|
166
|
+
? 'Aggregate(group-count-index)'
|
|
167
|
+
: aggregates.length > 1
|
|
168
|
+
? 'Aggregate(group-basic-multi-index)'
|
|
169
|
+
: 'Aggregate(group-basic-index)');
|
|
170
|
+
remainingSources.splice(0, remainingSources.length);
|
|
171
|
+
groupedAggregatePushed = true;
|
|
172
|
+
}
|
|
173
|
+
else if (requiredBgpPushdown) {
|
|
174
|
+
const useRdf3xPrimary = this.canUseRdf3xPrimaryJoin(requiredBgpPushdown.patterns);
|
|
175
|
+
const scanOptions = {
|
|
176
|
+
...(requiredBgpPushdown.project ? { project: requiredBgpPushdown.project } : {}),
|
|
177
|
+
...(requiredBgpPushdown.distinctPushed ? { distinct: true } : {}),
|
|
178
|
+
...(requiredBgpPushdown.orderPushed ? { orderBy: query.orderBy } : {}),
|
|
179
|
+
...(requiredBgpPushdown.paginationPushed && query.limit !== undefined ? { limit: Math.max(0, query.limit) } : {}),
|
|
180
|
+
...(requiredBgpPushdown.paginationPushed && query.offset !== undefined ? { offset: Math.max(0, query.offset) } : {}),
|
|
181
|
+
...(requiredBgpPushdown.paginationPushed ? { countMatchedRows: false } : {}),
|
|
182
|
+
};
|
|
183
|
+
const scan = useRdf3xPrimary
|
|
184
|
+
? this.rdf3xPrimaryIndex.joinPatterns(requiredBgpPushdown.patterns, scanOptions)
|
|
185
|
+
: this.index.joinPatterns(requiredBgpPushdown.patterns, scanOptions);
|
|
186
|
+
bindings = scan.bindings;
|
|
187
|
+
metrics.scannedRows += scan.metrics.matchedRows;
|
|
188
|
+
metrics.indexChoices.push(scan.metrics.indexChoice);
|
|
189
|
+
metrics.filtersPushedDown += requiredBgpPushdown.pushedDownFilters;
|
|
190
|
+
if (!useRdf3xPrimary && requiredBgpPushdown.reorderPlan) {
|
|
191
|
+
metrics.plan.push(requiredBgpPushdown.reorderPlan);
|
|
192
|
+
}
|
|
193
|
+
metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
|
|
194
|
+
metrics.plan.push(`${useRdf3xPrimary ? 'Rdf3xPrimaryJoin' : 'IndexJoin'}(${requiredBgpPushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
|
|
195
|
+
if (requiredBgpPushdown.orderPushed) {
|
|
196
|
+
metrics.plan.push(`${useRdf3xPrimary ? 'Rdf3xPrimaryJoinOrder' : 'IndexJoinOrder'}(${describeQueryOrder(query.orderBy ?? [])})`);
|
|
197
|
+
}
|
|
198
|
+
if (requiredBgpPushdown.distinctPushed) {
|
|
199
|
+
metrics.plan.push(`${useRdf3xPrimary ? 'Rdf3xPrimaryJoinDistinct' : 'IndexJoinDistinct'}(${(requiredBgpPushdown.project ?? []).map((variableName) => `?${variableName}`).join(',')})`);
|
|
200
|
+
}
|
|
201
|
+
if (requiredBgpPushdown.paginationPushed) {
|
|
202
|
+
metrics.plan.push(useRdf3xPrimary ? 'Rdf3xPrimaryJoinLimit' : 'IndexJoinLimit');
|
|
203
|
+
}
|
|
204
|
+
remainingSources.splice(0, remainingSources.length);
|
|
205
|
+
}
|
|
206
|
+
while (remainingSources.length > 0) {
|
|
207
|
+
const sourceIndex = this.chooseRequiredSourceIndex(remainingSources, bindings, requiredFilters, metrics);
|
|
208
|
+
const [source] = remainingSources.splice(sourceIndex, 1);
|
|
209
|
+
bindings = this.joinRequiredSource(bindings, source, requiredFilters, metrics, singleScanPushdown);
|
|
210
|
+
if (bindings.length === 0) {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if ((query.binds?.length ?? 0) > 0) {
|
|
215
|
+
bindings = this.applyBinds(bindings, query.binds ?? []);
|
|
216
|
+
metrics.plan.push(`Bind(${(query.binds ?? []).map(describeBind).join(',')})`);
|
|
217
|
+
}
|
|
218
|
+
for (const rawOptionalGroup of query.optional ?? []) {
|
|
219
|
+
const optionalGroup = normalizeOptionalGroup(rawOptionalGroup);
|
|
220
|
+
bindings = this.joinOptionalGroup(bindings, optionalGroup, metrics);
|
|
221
|
+
metrics.plan.push(`OptionalJoin(${optionalGroup.patterns.map(describePattern).join(',')})`);
|
|
222
|
+
}
|
|
223
|
+
for (const unionGroup of query.unions ?? []) {
|
|
224
|
+
bindings = this.joinUnionGroup(bindings, unionGroup.branches, requiredFilters, metrics);
|
|
225
|
+
metrics.plan.push(`Union(${unionGroup.branches.map((branch) => branch.patterns.map(describePattern).join(',')).join('|')})`);
|
|
226
|
+
if (bindings.length === 0) {
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const minusGroup of query.minus ?? []) {
|
|
231
|
+
bindings = this.applyMinusGroup(bindings, minusGroup, metrics);
|
|
232
|
+
metrics.plan.push(`Minus(${minusGroup.patterns.map(describePattern).join(',')})`);
|
|
233
|
+
if (bindings.length === 0) {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const existsGroup of query.exists ?? []) {
|
|
238
|
+
bindings = this.applyExistsGroup(bindings, existsGroup, metrics);
|
|
239
|
+
metrics.plan.push(`Exists(${existsGroup.patterns.map(describePattern).join(',')})`);
|
|
240
|
+
if (bindings.length === 0) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const postRequiredFilters = this.requiredFiltersNeedingPostApply(query, requiredPatterns, requiredFilters, requiredBgpPushdown);
|
|
245
|
+
if (postRequiredFilters.length > 0 && !groupedAggregatePushed) {
|
|
246
|
+
bindings = bindings.filter((binding) => this.matchesFilters(binding, postRequiredFilters));
|
|
247
|
+
metrics.filtersApplied += postRequiredFilters.length;
|
|
248
|
+
metrics.plan.push(`Filter(${postRequiredFilters.map(describeFilter).join(',')})`);
|
|
249
|
+
}
|
|
250
|
+
if (groupedAggregatePushed && groupAggregatePushdown) {
|
|
251
|
+
if ((query.having?.length ?? 0) > groupAggregatePushdown.pushedDownHaving) {
|
|
252
|
+
bindings = bindings.filter((binding) => this.matchesFilters(binding, query.having ?? []));
|
|
253
|
+
metrics.filtersApplied += query.having?.length ?? 0;
|
|
254
|
+
metrics.plan.push(`Having(${(query.having ?? []).map(describeFilter).join(',')})`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (aggregates.length > 0 && (query.groupBy?.length ?? 0) > 0) {
|
|
258
|
+
const joinedRows = bindings.length;
|
|
259
|
+
bindings = this.groupAggregateBindings(bindings, query.groupBy ?? [], aggregates);
|
|
260
|
+
metrics.joinedRows = joinedRows;
|
|
261
|
+
metrics.plan.push(aggregatePlan(aggregates, true));
|
|
262
|
+
if ((query.having?.length ?? 0) > 0) {
|
|
263
|
+
bindings = bindings.filter((binding) => this.matchesFilters(binding, query.having ?? []));
|
|
264
|
+
metrics.filtersApplied += query.having?.length ?? 0;
|
|
265
|
+
metrics.plan.push(`Having(${(query.having ?? []).map(describeFilter).join(',')})`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (aggregates.length > 0) {
|
|
269
|
+
const { binding: aggregateBinding, firstCount } = this.aggregateBindings(bindings, aggregates);
|
|
270
|
+
const having = query.having ?? [];
|
|
271
|
+
if (having.length > 0 && !this.matchesFilters(aggregateBinding, having)) {
|
|
272
|
+
metrics.joinedRows = bindings.length;
|
|
273
|
+
metrics.returnedRows = 0;
|
|
274
|
+
metrics.durationMs = Date.now() - start;
|
|
275
|
+
metrics.filtersApplied += having.length;
|
|
276
|
+
metrics.plan.push(aggregatePlan(aggregates, false));
|
|
277
|
+
metrics.plan.push(`Having(${having.map(describeFilter).join(',')})`);
|
|
278
|
+
return {
|
|
279
|
+
bindings: [],
|
|
280
|
+
count: firstCount,
|
|
281
|
+
metrics,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
metrics.joinedRows = bindings.length;
|
|
285
|
+
metrics.returnedRows = 1;
|
|
286
|
+
metrics.durationMs = Date.now() - start;
|
|
287
|
+
metrics.plan.push(aggregatePlan(aggregates, false));
|
|
288
|
+
if (having.length > 0) {
|
|
289
|
+
metrics.filtersApplied += having.length;
|
|
290
|
+
metrics.plan.push(`Having(${having.map(describeFilter).join(',')})`);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
bindings: [aggregateBinding],
|
|
294
|
+
count: firstCount,
|
|
295
|
+
metrics,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const joinedRows = metrics.joinedRows > 0
|
|
299
|
+
? metrics.joinedRows
|
|
300
|
+
: singleScanPushdown?.paginationPushed
|
|
301
|
+
|| requiredBgpPushdown?.paginationPushed
|
|
302
|
+
? metrics.scannedRows
|
|
303
|
+
: bindings.length;
|
|
304
|
+
if (query.orderBy
|
|
305
|
+
&& query.orderBy.length > 0
|
|
306
|
+
&& !singleScanPushdown?.orderPushed
|
|
307
|
+
&& !requiredBgpPushdown?.orderPushed
|
|
308
|
+
&& !groupAggregatePushdown?.orderPushed) {
|
|
309
|
+
bindings = [...bindings].sort((left, right) => compareBindings(left, right, query.orderBy ?? []));
|
|
310
|
+
metrics.plan.push('Sort');
|
|
311
|
+
}
|
|
312
|
+
let projected = query.select && query.select.length > 0
|
|
313
|
+
? bindings.map((binding) => projectBinding(binding, query.select ?? []))
|
|
314
|
+
: bindings;
|
|
315
|
+
if (query.distinct && !requiredBgpPushdown?.distinctPushed) {
|
|
316
|
+
projected = distinctBindings(projected);
|
|
317
|
+
metrics.plan.push('Distinct');
|
|
318
|
+
}
|
|
319
|
+
if ((query.offset !== undefined || query.limit !== undefined)
|
|
320
|
+
&& !singleScanPushdown?.paginationPushed
|
|
321
|
+
&& !requiredBgpPushdown?.paginationPushed
|
|
322
|
+
&& !groupAggregatePushdown?.paginationPushed) {
|
|
323
|
+
const startOffset = Math.max(0, query.offset ?? 0);
|
|
324
|
+
const endOffset = query.limit === undefined
|
|
325
|
+
? undefined
|
|
326
|
+
: startOffset + Math.max(0, query.limit);
|
|
327
|
+
projected = projected.slice(startOffset, endOffset);
|
|
328
|
+
metrics.plan.push('Limit');
|
|
329
|
+
}
|
|
330
|
+
metrics.joinedRows = joinedRows;
|
|
331
|
+
metrics.returnedRows = projected.length;
|
|
332
|
+
metrics.durationMs = Date.now() - start;
|
|
333
|
+
return {
|
|
334
|
+
bindings: projected,
|
|
335
|
+
metrics,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
chooseRequiredSourceIndex(sources, bindings, filters, metrics) {
|
|
339
|
+
const boundVariables = this.boundVariables(bindings);
|
|
340
|
+
const hasBoundVariables = boundVariables.size > 0;
|
|
341
|
+
const sampleBinding = bindings[0] ?? {};
|
|
342
|
+
const choices = sources.map((source, index) => {
|
|
343
|
+
const sourceVariables = variablesInRequiredSource(source);
|
|
344
|
+
const connected = sourceVariables.length === 0
|
|
345
|
+
|| sourceVariables.some((variableName) => boundVariables.has(variableName));
|
|
346
|
+
return {
|
|
347
|
+
index,
|
|
348
|
+
disconnectedPenalty: hasBoundVariables && !connected ? 1 : 0,
|
|
349
|
+
estimatedRows: this.estimateSourceRows(source, bindings, filters, metrics),
|
|
350
|
+
rank: this.sourceRank(source, sampleBinding),
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
choices.sort((left, right) => (left.disconnectedPenalty - right.disconnectedPenalty
|
|
354
|
+
|| left.estimatedRows - right.estimatedRows
|
|
355
|
+
|| left.rank - right.rank
|
|
356
|
+
|| left.index - right.index));
|
|
357
|
+
return choices[0]?.index ?? 0;
|
|
358
|
+
}
|
|
359
|
+
estimatePatternRows(pattern, bindings, filters, metrics) {
|
|
360
|
+
const sample = (bindings.length > 0 ? bindings : [{}]).slice(0, PLANNER_SAMPLE_BINDINGS);
|
|
361
|
+
const fanoutEstimate = this.estimatePatternRowsByDistinctFanout(pattern, sample, filters, metrics);
|
|
362
|
+
if (fanoutEstimate !== undefined) {
|
|
363
|
+
if (bindings.length > sample.length && sample.length > 0) {
|
|
364
|
+
return Math.ceil(fanoutEstimate * (bindings.length / sample.length));
|
|
365
|
+
}
|
|
366
|
+
return fanoutEstimate;
|
|
367
|
+
}
|
|
368
|
+
const estimates = new Map();
|
|
369
|
+
for (const binding of sample) {
|
|
370
|
+
const compiled = this.compilePattern(pattern, binding, filters);
|
|
371
|
+
if (!compiled) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
metrics.cardinalityEstimates = (metrics.cardinalityEstimates ?? 0) + 1;
|
|
375
|
+
const key = compiledPatternKey(compiled);
|
|
376
|
+
if (!estimates.has(key)) {
|
|
377
|
+
estimates.set(key, this.index.estimateCardinality(compiled).rows);
|
|
378
|
+
metrics.distinctCardinalityEstimates = (metrics.distinctCardinalityEstimates ?? 0) + 1;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const rows = sample.reduce((sum, binding) => {
|
|
382
|
+
const compiled = this.compilePattern(pattern, binding, filters);
|
|
383
|
+
return compiled ? sum + (estimates.get(compiledPatternKey(compiled)) ?? 0) : sum;
|
|
384
|
+
}, 0);
|
|
385
|
+
if (bindings.length > sample.length && sample.length > 0) {
|
|
386
|
+
return Math.ceil(rows * (bindings.length / sample.length));
|
|
387
|
+
}
|
|
388
|
+
return rows;
|
|
389
|
+
}
|
|
390
|
+
estimatePatternRowsByDistinctFanout(pattern, sample, filters, metrics) {
|
|
391
|
+
if (sample.length < 2) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
const boundSlots = this.boundPatternSlots(pattern, sample);
|
|
395
|
+
if (boundSlots.length === 0) {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
const boundVariables = new Set(boundSlots.map((slot) => slot.variable));
|
|
399
|
+
if (filters.some((filter) => boundVariables.has(filter.variable))) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const generalized = this.compilePattern(pattern, {}, filters);
|
|
403
|
+
if (!generalized) {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
const total = this.index.estimateCardinality(generalized).rows;
|
|
407
|
+
const distinct = this.index.countDistinctTuple(generalized, boundSlots.map((slot) => slot.key)).rows;
|
|
408
|
+
metrics.cardinalityEstimates = (metrics.cardinalityEstimates ?? 0) + sample.length;
|
|
409
|
+
metrics.distinctCardinalityEstimates = (metrics.distinctCardinalityEstimates ?? 0) + 2;
|
|
410
|
+
if (distinct === 0 || total === 0) {
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
return Math.ceil((total / distinct) * sample.length);
|
|
414
|
+
}
|
|
415
|
+
boundPatternSlots(pattern, sample) {
|
|
416
|
+
return TERM_KEYS
|
|
417
|
+
.map((key) => ({ key, value: pattern[key] }))
|
|
418
|
+
.filter((slot) => {
|
|
419
|
+
if (!isVariable(slot.value)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const variableName = slot.value.variable;
|
|
423
|
+
return sample.every((binding) => Boolean(binding[variableName]));
|
|
424
|
+
})
|
|
425
|
+
.map((slot) => ({ key: slot.key, variable: slot.value.variable }));
|
|
426
|
+
}
|
|
427
|
+
estimateSourceRows(source, bindings, filters, metrics) {
|
|
428
|
+
if (source.kind === 'pattern') {
|
|
429
|
+
if (source.tupleValues) {
|
|
430
|
+
return this.estimateTuplePatternRows(source, bindings, filters, metrics);
|
|
431
|
+
}
|
|
432
|
+
return this.estimatePatternRows(source.pattern, bindings, filters, metrics);
|
|
433
|
+
}
|
|
434
|
+
if (source.kind === 'values') {
|
|
435
|
+
return source.source.rows.length * Math.max(1, bindings.length);
|
|
436
|
+
}
|
|
437
|
+
const sample = (bindings.length > 0 ? bindings : [{}]).slice(0, PLANNER_SAMPLE_BINDINGS);
|
|
438
|
+
if (!this.searchSourceHasBoundVariables(source, sample)) {
|
|
439
|
+
metrics.searchCardinalityEstimates = (metrics.searchCardinalityEstimates ?? 0) + 1;
|
|
440
|
+
const estimate = source.kind === 'text'
|
|
441
|
+
? this.estimateTextSearchRows(source)
|
|
442
|
+
: this.estimateVectorSearchRows(source);
|
|
443
|
+
return estimate * Math.max(1, bindings.length);
|
|
444
|
+
}
|
|
445
|
+
const sourceVariable = source.pattern.source;
|
|
446
|
+
const boundSourceEstimate = sourceVariable && this.canUseBoundSourceSearch(sample, source, sourceVariable)
|
|
447
|
+
? this.estimateSearchRowsByBoundSource(source, sample, metrics)
|
|
448
|
+
: undefined;
|
|
449
|
+
if (boundSourceEstimate !== undefined) {
|
|
450
|
+
if (bindings.length > sample.length && sample.length > 0) {
|
|
451
|
+
return Math.ceil(boundSourceEstimate * (bindings.length / sample.length));
|
|
452
|
+
}
|
|
453
|
+
return boundSourceEstimate;
|
|
454
|
+
}
|
|
455
|
+
const results = source.kind === 'text'
|
|
456
|
+
? this.textSearchResults(source)
|
|
457
|
+
: this.vectorSearchResults(source);
|
|
458
|
+
let rows = 0;
|
|
459
|
+
for (const binding of sample) {
|
|
460
|
+
for (const result of results) {
|
|
461
|
+
const next = source.kind === 'text'
|
|
462
|
+
? bindTextSearchResult(binding, source.pattern, result)
|
|
463
|
+
: bindVectorSearchResult(binding, source.pattern, result);
|
|
464
|
+
if (next) {
|
|
465
|
+
rows++;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (bindings.length > sample.length && sample.length > 0) {
|
|
470
|
+
return Math.ceil(rows * (bindings.length / sample.length));
|
|
471
|
+
}
|
|
472
|
+
return rows;
|
|
473
|
+
}
|
|
474
|
+
estimateTuplePatternRows(source, bindings, filters, metrics) {
|
|
475
|
+
const sample = (bindings.length > 0 ? bindings : [{}]).slice(0, PLANNER_SAMPLE_BINDINGS);
|
|
476
|
+
const estimates = new Map();
|
|
477
|
+
let rows = 0;
|
|
478
|
+
for (const binding of sample) {
|
|
479
|
+
for (const row of source.tupleValues?.rows ?? []) {
|
|
480
|
+
const tupleBinding = mergeTupleValuesBinding(binding, source.tupleValues?.variables ?? [], row);
|
|
481
|
+
if (!tupleBinding) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const compiled = this.compilePattern(source.pattern, tupleBinding, filters);
|
|
485
|
+
if (!compiled) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
metrics.cardinalityEstimates = (metrics.cardinalityEstimates ?? 0) + 1;
|
|
489
|
+
const key = compiledPatternKey(compiled);
|
|
490
|
+
if (!estimates.has(key)) {
|
|
491
|
+
estimates.set(key, this.index.estimateCardinality(compiled).rows);
|
|
492
|
+
metrics.distinctCardinalityEstimates = (metrics.distinctCardinalityEstimates ?? 0) + 1;
|
|
493
|
+
}
|
|
494
|
+
rows += estimates.get(key) ?? 0;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (bindings.length > sample.length && sample.length > 0) {
|
|
498
|
+
return Math.ceil(rows * (bindings.length / sample.length));
|
|
499
|
+
}
|
|
500
|
+
return rows;
|
|
501
|
+
}
|
|
502
|
+
searchSourceHasBoundVariables(source, sample) {
|
|
503
|
+
const variables = variablesInRequiredSource(source);
|
|
504
|
+
return variables.some((variableName) => sample.some((binding) => Boolean(binding[variableName])));
|
|
505
|
+
}
|
|
506
|
+
estimateSearchRowsByBoundSource(source, sample, metrics) {
|
|
507
|
+
const sourceVariable = source.pattern.source;
|
|
508
|
+
if (!sourceVariable) {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
const estimates = new Map();
|
|
512
|
+
let rows = 0;
|
|
513
|
+
let sawBoundSource = false;
|
|
514
|
+
for (const binding of sample) {
|
|
515
|
+
const term = binding[sourceVariable];
|
|
516
|
+
if (!term) {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
sawBoundSource = true;
|
|
520
|
+
if (term.termType !== 'NamedNode') {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (!estimates.has(term.value)) {
|
|
524
|
+
metrics.searchCardinalityEstimates = (metrics.searchCardinalityEstimates ?? 0) + 1;
|
|
525
|
+
estimates.set(term.value, source.kind === 'text'
|
|
526
|
+
? this.estimateTextSearchRows(source, term.value)
|
|
527
|
+
: this.estimateVectorSearchRows(source, term.value));
|
|
528
|
+
}
|
|
529
|
+
rows += estimates.get(term.value) ?? 0;
|
|
530
|
+
}
|
|
531
|
+
return sawBoundSource ? rows : undefined;
|
|
532
|
+
}
|
|
533
|
+
estimateTextSearchRows(source, exactSource) {
|
|
534
|
+
if (!this.textIndex) {
|
|
535
|
+
throw new Error('RdfLocalQuery textSearch requires a configured RdfTextIndex');
|
|
536
|
+
}
|
|
537
|
+
const options = this.textSearchOptions(source.pattern, exactSource);
|
|
538
|
+
return options ? this.textIndex.estimateSearchCardinality(options).rows : 0;
|
|
539
|
+
}
|
|
540
|
+
estimateVectorSearchRows(source, exactSource) {
|
|
541
|
+
if (!this.vectorIndex) {
|
|
542
|
+
throw new Error('RdfLocalQuery vectorSearch requires a configured RdfVectorIndex');
|
|
543
|
+
}
|
|
544
|
+
const options = this.vectorSearchOptions(source.pattern, exactSource);
|
|
545
|
+
return options ? this.vectorIndex.estimateSearchCardinality(options).rows : 0;
|
|
546
|
+
}
|
|
547
|
+
boundVariables(bindings) {
|
|
548
|
+
const names = new Set();
|
|
549
|
+
for (const binding of bindings.slice(0, PLANNER_SAMPLE_BINDINGS)) {
|
|
550
|
+
for (const name of Object.keys(binding)) {
|
|
551
|
+
names.add(name);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return names;
|
|
555
|
+
}
|
|
556
|
+
patternRank(pattern, binding) {
|
|
557
|
+
let rank = 0;
|
|
558
|
+
for (const key of TERM_KEYS) {
|
|
559
|
+
const value = pattern[key];
|
|
560
|
+
if (!value)
|
|
561
|
+
continue;
|
|
562
|
+
if (isVariable(value)) {
|
|
563
|
+
if (binding[value.variable]) {
|
|
564
|
+
rank += key === 'graph' ? 0 : 1;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
rank += 8;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
rank += key === 'graph' ? 0 : 1;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return rank;
|
|
575
|
+
}
|
|
576
|
+
sourceRank(source, binding) {
|
|
577
|
+
if (source.kind === 'pattern') {
|
|
578
|
+
return this.patternRank(source.pattern, binding);
|
|
579
|
+
}
|
|
580
|
+
if (source.kind === 'values') {
|
|
581
|
+
return 0;
|
|
582
|
+
}
|
|
583
|
+
const variables = variablesInRequiredSource(source);
|
|
584
|
+
if (variables.some((variableName) => binding[variableName])) {
|
|
585
|
+
return 0;
|
|
586
|
+
}
|
|
587
|
+
return source.kind === 'text' ? 4 : 5;
|
|
588
|
+
}
|
|
589
|
+
joinRequiredSource(input, source, filters, metrics, singleScanPushdown) {
|
|
590
|
+
switch (source.kind) {
|
|
591
|
+
case 'pattern': {
|
|
592
|
+
const bindings = this.joinPattern(input, source, filters, metrics, false, singleScanPushdown?.options);
|
|
593
|
+
metrics.plan.push(`IndexScan(${describePattern(source.pattern)})`);
|
|
594
|
+
if (singleScanPushdown?.orderPushed) {
|
|
595
|
+
metrics.plan.push(`IndexOrder(${describeScanOrder(singleScanPushdown.options)})`);
|
|
596
|
+
}
|
|
597
|
+
if (singleScanPushdown?.paginationPushed) {
|
|
598
|
+
metrics.plan.push('IndexLimit');
|
|
599
|
+
}
|
|
600
|
+
return bindings;
|
|
601
|
+
}
|
|
602
|
+
case 'text': {
|
|
603
|
+
const bindings = this.joinTextSearch(input, source, metrics);
|
|
604
|
+
metrics.plan.push(`TextSearch(${describeTextSearch(source.pattern)})`);
|
|
605
|
+
return bindings;
|
|
606
|
+
}
|
|
607
|
+
case 'vector': {
|
|
608
|
+
const bindings = this.joinVectorSearch(input, source, metrics);
|
|
609
|
+
metrics.plan.push(`VectorSearch(${describeVectorSearch(source.pattern)})`);
|
|
610
|
+
return bindings;
|
|
611
|
+
}
|
|
612
|
+
case 'values': {
|
|
613
|
+
const bindings = joinValuesSource(input, source.source);
|
|
614
|
+
metrics.scannedRows += source.source.rows.length;
|
|
615
|
+
metrics.plan.push(`Values(${source.source.variables.map((variableName) => `?${variableName}`).join(',')})`);
|
|
616
|
+
return bindings;
|
|
617
|
+
}
|
|
618
|
+
default: {
|
|
619
|
+
const exhaustive = source;
|
|
620
|
+
throw new Error(`Unsupported RDF required source: ${JSON.stringify(exhaustive)}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
joinOptionalGroup(input, optionalGroup, metrics) {
|
|
625
|
+
const optionalFilters = optionalGroup.filters ?? [];
|
|
626
|
+
if (optionalFilters.length > 0) {
|
|
627
|
+
metrics.plan.push(`OptionalFilter(${optionalFilters.map(describeFilter).join(',')})`);
|
|
628
|
+
}
|
|
629
|
+
return input.flatMap((binding) => {
|
|
630
|
+
let matches = [binding];
|
|
631
|
+
for (const source of optionalGroup.values ?? []) {
|
|
632
|
+
matches = joinValuesSource(matches, source);
|
|
633
|
+
metrics.scannedRows += source.rows.length;
|
|
634
|
+
metrics.plan.push(`OptionalValues(${source.variables.map((variableName) => `?${variableName}`).join(',')})`);
|
|
635
|
+
if (matches.length === 0) {
|
|
636
|
+
return [binding];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
for (const pattern of optionalGroup.patterns) {
|
|
640
|
+
matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, optionalFilters, metrics, true);
|
|
641
|
+
if (matches.length === 0) {
|
|
642
|
+
return [binding];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (const unionGroup of optionalGroup.unions ?? []) {
|
|
646
|
+
matches = this.joinUnionGroup(matches, unionGroup.branches, optionalFilters, metrics);
|
|
647
|
+
metrics.plan.push(`OptionalUnion(${unionGroup.branches.map((branch) => branch.patterns.map(describePattern).join(',')).join('|')})`);
|
|
648
|
+
if (matches.length === 0) {
|
|
649
|
+
return [binding];
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
for (const rawNestedOptionalGroup of optionalGroup.optional ?? []) {
|
|
653
|
+
const nestedOptionalGroup = normalizeOptionalGroup(rawNestedOptionalGroup);
|
|
654
|
+
matches = this.joinOptionalGroup(matches, nestedOptionalGroup, metrics);
|
|
655
|
+
metrics.plan.push(`OptionalNestedJoin(${nestedOptionalGroup.patterns.map(describePattern).join(',')})`);
|
|
656
|
+
if (matches.length === 0) {
|
|
657
|
+
return [binding];
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for (const minusGroup of optionalGroup.minus ?? []) {
|
|
661
|
+
matches = this.applyMinusGroup(matches, minusGroup, metrics);
|
|
662
|
+
metrics.plan.push(`OptionalMinus(${minusGroup.patterns.map(describePattern).join(',')})`);
|
|
663
|
+
if (matches.length === 0) {
|
|
664
|
+
return [binding];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
for (const existsGroup of optionalGroup.exists ?? []) {
|
|
668
|
+
matches = this.applyExistsGroup(matches, existsGroup, metrics);
|
|
669
|
+
metrics.plan.push(`OptionalExists(${existsGroup.patterns.map(describePattern).join(',')})`);
|
|
670
|
+
if (matches.length === 0) {
|
|
671
|
+
return [binding];
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if ((optionalGroup.binds?.length ?? 0) > 0) {
|
|
675
|
+
matches = this.applyBinds(matches, optionalGroup.binds ?? []);
|
|
676
|
+
metrics.plan.push(`OptionalBind(${(optionalGroup.binds ?? []).map(describeBind).join(',')})`);
|
|
677
|
+
}
|
|
678
|
+
if (optionalFilters.length > 0) {
|
|
679
|
+
matches = matches.filter((match) => this.matchesFilters(match, optionalFilters));
|
|
680
|
+
metrics.filtersApplied += optionalFilters.length;
|
|
681
|
+
if (matches.length === 0) {
|
|
682
|
+
return [binding];
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return matches;
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
joinUnionGroup(input, branches, outerFilters, metrics) {
|
|
689
|
+
const output = [];
|
|
690
|
+
for (const binding of input) {
|
|
691
|
+
for (const branch of branches) {
|
|
692
|
+
let matches = [binding];
|
|
693
|
+
const branchFilters = [...outerFilters, ...(branch.filters ?? [])];
|
|
694
|
+
for (const source of branch.values ?? []) {
|
|
695
|
+
matches = joinValuesSource(matches, source);
|
|
696
|
+
metrics.scannedRows += source.rows.length;
|
|
697
|
+
metrics.plan.push(`UnionValues(${source.variables.map((variableName) => `?${variableName}`).join(',')})`);
|
|
698
|
+
if (matches.length === 0) {
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (matches.length === 0) {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
for (const pattern of branch.patterns) {
|
|
706
|
+
matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, branchFilters, metrics, false);
|
|
707
|
+
if (matches.length === 0) {
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (matches.length === 0) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if ((branch.binds?.length ?? 0) > 0) {
|
|
715
|
+
matches = this.applyBinds(matches, branch.binds ?? []);
|
|
716
|
+
metrics.plan.push(`UnionBind(${(branch.binds ?? []).map(describeBind).join(',')})`);
|
|
717
|
+
}
|
|
718
|
+
if (matches.length === 0) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
for (const rawOptionalGroup of branch.optional ?? []) {
|
|
722
|
+
const optionalGroup = normalizeOptionalGroup(rawOptionalGroup);
|
|
723
|
+
matches = this.joinOptionalGroup(matches, optionalGroup, metrics);
|
|
724
|
+
metrics.plan.push(`UnionOptionalJoin(${optionalGroup.patterns.map(describePattern).join(',')})`);
|
|
725
|
+
if (matches.length === 0) {
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (matches.length === 0) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (branch.filters?.length) {
|
|
733
|
+
matches = matches.filter((match) => this.matchesFilters(match, branch.filters ?? []));
|
|
734
|
+
metrics.filtersApplied += branch.filters.length;
|
|
735
|
+
metrics.plan.push(`UnionFilter(${branch.filters.map(describeFilter).join(',')})`);
|
|
736
|
+
}
|
|
737
|
+
output.push(...matches);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return output;
|
|
741
|
+
}
|
|
742
|
+
applyDependentValues(input, sources, metrics, label) {
|
|
743
|
+
let matches = input;
|
|
744
|
+
for (const source of sources ?? []) {
|
|
745
|
+
matches = joinValuesSource(matches, source);
|
|
746
|
+
metrics.scannedRows += source.rows.length;
|
|
747
|
+
metrics.plan.push(`${label}Values(${source.variables.map((variableName) => `?${variableName}`).join(',')})`);
|
|
748
|
+
if (matches.length === 0) {
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return matches;
|
|
753
|
+
}
|
|
754
|
+
applyMinusGroup(input, minusGroup, metrics) {
|
|
755
|
+
const filters = minusGroup.filters ?? [];
|
|
756
|
+
const output = [];
|
|
757
|
+
for (const binding of input) {
|
|
758
|
+
let matches = [binding];
|
|
759
|
+
matches = this.applyDependentValues(matches, minusGroup.values, metrics, 'Minus');
|
|
760
|
+
for (const pattern of minusGroup.patterns) {
|
|
761
|
+
matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, filters, metrics, false);
|
|
762
|
+
if (matches.length === 0) {
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (matches.length > 0) {
|
|
767
|
+
for (const unionGroup of minusGroup.unions ?? []) {
|
|
768
|
+
matches = this.joinUnionGroup(matches, unionGroup.branches, filters, metrics);
|
|
769
|
+
metrics.plan.push(`MinusUnion(${unionGroup.branches.map((branch) => branch.patterns.map(describePattern).join(',')).join('|')})`);
|
|
770
|
+
if (matches.length === 0) {
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
for (const rawOptionalGroup of minusGroup.optional ?? []) {
|
|
776
|
+
const optionalGroup = normalizeOptionalGroup(rawOptionalGroup);
|
|
777
|
+
matches = this.joinOptionalGroup(matches, optionalGroup, metrics);
|
|
778
|
+
metrics.plan.push(`MinusOptionalJoin(${optionalGroup.patterns.map(describePattern).join(',')})`);
|
|
779
|
+
}
|
|
780
|
+
if ((minusGroup.binds?.length ?? 0) > 0) {
|
|
781
|
+
matches = this.applyBinds(matches, minusGroup.binds ?? []);
|
|
782
|
+
metrics.plan.push(`MinusBind(${(minusGroup.binds ?? []).map(describeBind).join(',')})`);
|
|
783
|
+
}
|
|
784
|
+
if (filters.length > 0) {
|
|
785
|
+
matches = matches.filter((match) => this.matchesFilters(match, filters));
|
|
786
|
+
metrics.filtersApplied += filters.length;
|
|
787
|
+
metrics.plan.push(`MinusFilter(${filters.map(describeFilter).join(',')})`);
|
|
788
|
+
}
|
|
789
|
+
if (matches.length === 0) {
|
|
790
|
+
output.push(binding);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return output;
|
|
794
|
+
}
|
|
795
|
+
applyExistsGroup(input, existsGroup, metrics) {
|
|
796
|
+
const filters = existsGroup.filters ?? [];
|
|
797
|
+
const output = [];
|
|
798
|
+
for (const binding of input) {
|
|
799
|
+
let matches = [binding];
|
|
800
|
+
matches = this.applyDependentValues(matches, existsGroup.values, metrics, 'Exists');
|
|
801
|
+
for (const pattern of existsGroup.patterns) {
|
|
802
|
+
matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, filters, metrics, false);
|
|
803
|
+
if (matches.length === 0) {
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (matches.length > 0) {
|
|
808
|
+
for (const unionGroup of existsGroup.unions ?? []) {
|
|
809
|
+
matches = this.joinUnionGroup(matches, unionGroup.branches, filters, metrics);
|
|
810
|
+
metrics.plan.push(`ExistsUnion(${unionGroup.branches.map((branch) => branch.patterns.map(describePattern).join(',')).join('|')})`);
|
|
811
|
+
if (matches.length === 0) {
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
for (const rawOptionalGroup of existsGroup.optional ?? []) {
|
|
817
|
+
const optionalGroup = normalizeOptionalGroup(rawOptionalGroup);
|
|
818
|
+
matches = this.joinOptionalGroup(matches, optionalGroup, metrics);
|
|
819
|
+
metrics.plan.push(`ExistsOptionalJoin(${optionalGroup.patterns.map(describePattern).join(',')})`);
|
|
820
|
+
}
|
|
821
|
+
if ((existsGroup.binds?.length ?? 0) > 0) {
|
|
822
|
+
matches = this.applyBinds(matches, existsGroup.binds ?? []);
|
|
823
|
+
metrics.plan.push(`ExistsBind(${(existsGroup.binds ?? []).map(describeBind).join(',')})`);
|
|
824
|
+
}
|
|
825
|
+
if (filters.length > 0) {
|
|
826
|
+
matches = matches.filter((match) => this.matchesFilters(match, filters));
|
|
827
|
+
metrics.filtersApplied += filters.length;
|
|
828
|
+
metrics.plan.push(`ExistsFilter(${filters.map(describeFilter).join(',')})`);
|
|
829
|
+
}
|
|
830
|
+
if (matches.length > 0) {
|
|
831
|
+
output.push(binding);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return output;
|
|
835
|
+
}
|
|
836
|
+
joinPattern(input, source, filters, metrics, optional, scanOptions) {
|
|
837
|
+
const output = [];
|
|
838
|
+
const { pattern } = source;
|
|
839
|
+
for (const binding of input) {
|
|
840
|
+
const compiled = this.compilePattern(pattern, binding, filters);
|
|
841
|
+
if (!compiled) {
|
|
842
|
+
if (optional) {
|
|
843
|
+
output.push(binding);
|
|
844
|
+
}
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
const tupleValues = this.tupleValuesForBinding(source, binding);
|
|
848
|
+
const scan = tupleValues
|
|
849
|
+
? this.index.scanWithTupleConstraints(compiled, tupleValues, scanOptions)
|
|
850
|
+
: this.index.scan(compiled, scanOptions);
|
|
851
|
+
metrics.scannedRows += scan.metrics.matchedRows;
|
|
852
|
+
metrics.indexChoices.push(scan.metrics.indexChoice);
|
|
853
|
+
metrics.filtersPushedDown += compiled.pushedDownFilters;
|
|
854
|
+
metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
|
|
855
|
+
for (const quad of scan.quads) {
|
|
856
|
+
const next = this.bindQuad(pattern, binding, quad);
|
|
857
|
+
const remainingFilters = filtersWithoutIndexes(filters, compiled.pushedDownFilterIndexes);
|
|
858
|
+
if (next && this.matchesNewlyBoundFilters(next, binding, remainingFilters)) {
|
|
859
|
+
output.push(next);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return output;
|
|
864
|
+
}
|
|
865
|
+
requiredBgpPushdown(query, requiredPatterns, filters) {
|
|
866
|
+
if (requiredPatterns.length < 1
|
|
867
|
+
|| (query.values?.length ?? 0) > 0
|
|
868
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
869
|
+
|| (query.vectorSearch?.length ?? 0) > 0
|
|
870
|
+
|| (query.unions?.length ?? 0) > 0
|
|
871
|
+
|| (query.minus?.length ?? 0) > 0
|
|
872
|
+
|| (query.exists?.length ?? 0) > 0
|
|
873
|
+
|| (query.optional?.length ?? 0) > 0
|
|
874
|
+
|| (query.binds?.length ?? 0) > 0
|
|
875
|
+
|| queryAggregates(query).length > 0) {
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
if (requiredPatterns.length === 1 && !query.distinct) {
|
|
879
|
+
return undefined;
|
|
880
|
+
}
|
|
881
|
+
if (!this.canPushRequiredBgpFilters(requiredPatterns, filters)) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
const distinctProject = query.distinct
|
|
885
|
+
? this.requiredBgpDistinctProject(query, requiredPatterns, filters)
|
|
886
|
+
: undefined;
|
|
887
|
+
if (query.distinct && !distinctProject) {
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
const orderPushed = (query.orderBy?.length ?? 0) > 0;
|
|
891
|
+
if (orderPushed && !this.canPushRequiredBgpOrder(requiredPatterns, query.orderBy ?? [])) {
|
|
892
|
+
return undefined;
|
|
893
|
+
}
|
|
894
|
+
const sharedVariables = variablesSharedAcrossPatterns(requiredPatterns);
|
|
895
|
+
if (requiredPatterns.length > 1 && sharedVariables.size === 0) {
|
|
896
|
+
return undefined;
|
|
897
|
+
}
|
|
898
|
+
const compiled = requiredPatterns.map((pattern) => this.compileJoinPattern(pattern, filters));
|
|
899
|
+
if (compiled.some((entry) => !entry)) {
|
|
900
|
+
return undefined;
|
|
901
|
+
}
|
|
902
|
+
const reordered = this.reorderJoinPatterns(requiredPatterns, compiled, filters);
|
|
903
|
+
return {
|
|
904
|
+
patterns: reordered.patterns,
|
|
905
|
+
...(reordered.reorderPlan ? { reorderPlan: reordered.reorderPlan } : {}),
|
|
906
|
+
orderPushed,
|
|
907
|
+
paginationPushed: query.limit !== undefined || query.offset !== undefined,
|
|
908
|
+
distinctPushed: Boolean(distinctProject),
|
|
909
|
+
...(distinctProject ? { project: distinctProject } : {}),
|
|
910
|
+
pushedDownFilters: filters.length,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
requiredBgpDistinctProject(query, requiredPatterns, filters) {
|
|
914
|
+
const visibleVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
915
|
+
const projectedVariables = uniqueStrings(query.select && query.select.length > 0
|
|
916
|
+
? query.select
|
|
917
|
+
: [...visibleVariables]);
|
|
918
|
+
if (projectedVariables.some((variableName) => !visibleVariables.has(variableName))) {
|
|
919
|
+
return undefined;
|
|
920
|
+
}
|
|
921
|
+
const requiredForRecheck = new Set(filters.flatMap((filter) => filterVariables(filter)));
|
|
922
|
+
for (const order of query.orderBy ?? []) {
|
|
923
|
+
requiredForRecheck.add(order.variable);
|
|
924
|
+
}
|
|
925
|
+
if ([...requiredForRecheck].some((variableName) => !projectedVariables.includes(variableName))) {
|
|
926
|
+
return undefined;
|
|
927
|
+
}
|
|
928
|
+
return projectedVariables;
|
|
929
|
+
}
|
|
930
|
+
canPushRequiredBgpFilters(requiredPatterns, filters) {
|
|
931
|
+
const variables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
932
|
+
return filters.every((filter) => (!filter.variable2
|
|
933
|
+
&& variables.has(filter.variable)
|
|
934
|
+
&& isPushdownFilter(filter)
|
|
935
|
+
&& this.compilePushdownFilter(filter.variable, [filter]) !== null));
|
|
936
|
+
}
|
|
937
|
+
canPushRequiredBgpOrder(requiredPatterns, orderBy) {
|
|
938
|
+
if (orderBy.length === 0) {
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
const variables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
942
|
+
return orderBy.every((entry) => variables.has(entry.variable));
|
|
943
|
+
}
|
|
944
|
+
reorderJoinPatterns(requiredPatterns, compiledPatterns, filters) {
|
|
945
|
+
if (compiledPatterns.length < 2) {
|
|
946
|
+
return { patterns: compiledPatterns };
|
|
947
|
+
}
|
|
948
|
+
const candidates = requiredPatterns.map((pattern, index) => {
|
|
949
|
+
const compiled = this.compilePattern(pattern, {}, filters);
|
|
950
|
+
return {
|
|
951
|
+
index,
|
|
952
|
+
variables: new Set(variablesInPattern(pattern)),
|
|
953
|
+
estimatedRows: compiled ? this.index.estimateCardinality(compiled).rows : Number.MAX_SAFE_INTEGER,
|
|
954
|
+
};
|
|
955
|
+
});
|
|
956
|
+
const remaining = [...candidates];
|
|
957
|
+
const selected = [];
|
|
958
|
+
const selectedVariables = new Set();
|
|
959
|
+
while (remaining.length > 0) {
|
|
960
|
+
const hasSelectedVariables = selectedVariables.size > 0;
|
|
961
|
+
remaining.sort((left, right) => {
|
|
962
|
+
const leftConnected = !hasSelectedVariables || hasSharedVariable(left.variables, selectedVariables);
|
|
963
|
+
const rightConnected = !hasSelectedVariables || hasSharedVariable(right.variables, selectedVariables);
|
|
964
|
+
return Number(rightConnected) - Number(leftConnected)
|
|
965
|
+
|| left.estimatedRows - right.estimatedRows
|
|
966
|
+
|| left.index - right.index;
|
|
967
|
+
});
|
|
968
|
+
const [next] = remaining.splice(0, 1);
|
|
969
|
+
selected.push(next);
|
|
970
|
+
for (const variableName of next.variables) {
|
|
971
|
+
selectedVariables.add(variableName);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const order = selected.map((entry) => entry.index);
|
|
975
|
+
if (order.every((index, position) => index === position)) {
|
|
976
|
+
return { patterns: compiledPatterns };
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
patterns: order.map((index) => compiledPatterns[index]),
|
|
980
|
+
reorderPlan: `JoinReorder(${order.join('>')})`,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
compileJoinPattern(pattern, filters) {
|
|
984
|
+
const variables = {};
|
|
985
|
+
for (const key of TERM_KEYS) {
|
|
986
|
+
const value = pattern[key];
|
|
987
|
+
if (isVariable(value)) {
|
|
988
|
+
variables[key] = value.variable;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const compiled = this.compilePattern(pattern, {}, filters);
|
|
992
|
+
return compiled ? { pattern: compiled, variables } : undefined;
|
|
993
|
+
}
|
|
994
|
+
canUseRdf3xPrimaryJoin(patterns) {
|
|
995
|
+
return Boolean(this.rdf3xPrimaryIndex)
|
|
996
|
+
&& patterns.length > 0
|
|
997
|
+
&& patterns.every((entry) => isRdf3xCompatiblePattern(entry.pattern));
|
|
998
|
+
}
|
|
999
|
+
tupleValuesForBinding(source, binding) {
|
|
1000
|
+
if (!source.tupleValues) {
|
|
1001
|
+
return undefined;
|
|
1002
|
+
}
|
|
1003
|
+
const rows = source.tupleValues.rows.filter((row) => (source.tupleValues?.variables.every((variableName) => {
|
|
1004
|
+
const existing = binding[variableName];
|
|
1005
|
+
const value = row[variableName];
|
|
1006
|
+
return !existing || !value || sameTerm(existing, value);
|
|
1007
|
+
})));
|
|
1008
|
+
return tupleConstraintSourceForPattern({ ...source.tupleValues, rows }, source.pattern);
|
|
1009
|
+
}
|
|
1010
|
+
joinTextSearch(input, source, metrics) {
|
|
1011
|
+
metrics.indexChoices.push('text-chunk');
|
|
1012
|
+
const sourceVariable = source.pattern.source;
|
|
1013
|
+
if (sourceVariable && this.canUseBoundSourceSearch(input, source, sourceVariable)) {
|
|
1014
|
+
return this.joinTextSearchByBoundSource(input, source, sourceVariable, metrics);
|
|
1015
|
+
}
|
|
1016
|
+
const results = this.textSearchResults(source);
|
|
1017
|
+
metrics.scannedRows += results.length;
|
|
1018
|
+
const output = [];
|
|
1019
|
+
for (const binding of input) {
|
|
1020
|
+
for (const result of results) {
|
|
1021
|
+
const next = bindTextSearchResult(binding, source.pattern, result);
|
|
1022
|
+
if (next) {
|
|
1023
|
+
output.push(next);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return output;
|
|
1028
|
+
}
|
|
1029
|
+
joinVectorSearch(input, source, metrics) {
|
|
1030
|
+
metrics.indexChoices.push('vector-chunk');
|
|
1031
|
+
const sourceVariable = source.pattern.source;
|
|
1032
|
+
if (sourceVariable && this.canUseBoundSourceSearch(input, source, sourceVariable)) {
|
|
1033
|
+
return this.joinVectorSearchByBoundSource(input, source, sourceVariable, metrics);
|
|
1034
|
+
}
|
|
1035
|
+
const results = this.vectorSearchResults(source);
|
|
1036
|
+
metrics.scannedRows += results.length;
|
|
1037
|
+
const output = [];
|
|
1038
|
+
for (const binding of input) {
|
|
1039
|
+
for (const result of results) {
|
|
1040
|
+
const next = bindVectorSearchResult(binding, source.pattern, result);
|
|
1041
|
+
if (next) {
|
|
1042
|
+
output.push(next);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return output;
|
|
1047
|
+
}
|
|
1048
|
+
joinTextSearchByBoundSource(input, source, sourceVariable, metrics) {
|
|
1049
|
+
const cache = new Map();
|
|
1050
|
+
const countedSources = new Set();
|
|
1051
|
+
let globalResults;
|
|
1052
|
+
let countedGlobal = false;
|
|
1053
|
+
const output = [];
|
|
1054
|
+
for (const binding of input) {
|
|
1055
|
+
const term = binding[sourceVariable];
|
|
1056
|
+
let results;
|
|
1057
|
+
if (!term) {
|
|
1058
|
+
globalResults ??= this.textSearchResults(source);
|
|
1059
|
+
results = globalResults;
|
|
1060
|
+
if (!countedGlobal) {
|
|
1061
|
+
metrics.scannedRows += results.length;
|
|
1062
|
+
countedGlobal = true;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
else if (term.termType !== 'NamedNode') {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
if (!cache.has(term.value)) {
|
|
1070
|
+
cache.set(term.value, this.textSearchResults(source, term.value));
|
|
1071
|
+
}
|
|
1072
|
+
results = cache.get(term.value) ?? [];
|
|
1073
|
+
if (!countedSources.has(term.value)) {
|
|
1074
|
+
metrics.scannedRows += results.length;
|
|
1075
|
+
countedSources.add(term.value);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
for (const result of results) {
|
|
1079
|
+
const next = bindTextSearchResult(binding, source.pattern, result);
|
|
1080
|
+
if (next) {
|
|
1081
|
+
output.push(next);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return output;
|
|
1086
|
+
}
|
|
1087
|
+
joinVectorSearchByBoundSource(input, source, sourceVariable, metrics) {
|
|
1088
|
+
const cache = new Map();
|
|
1089
|
+
const countedSources = new Set();
|
|
1090
|
+
let globalResults;
|
|
1091
|
+
let countedGlobal = false;
|
|
1092
|
+
const output = [];
|
|
1093
|
+
for (const binding of input) {
|
|
1094
|
+
const term = binding[sourceVariable];
|
|
1095
|
+
let results;
|
|
1096
|
+
if (!term) {
|
|
1097
|
+
globalResults ??= this.vectorSearchResults(source);
|
|
1098
|
+
results = globalResults;
|
|
1099
|
+
if (!countedGlobal) {
|
|
1100
|
+
metrics.scannedRows += results.length;
|
|
1101
|
+
countedGlobal = true;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
else if (term.termType !== 'NamedNode') {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
if (!cache.has(term.value)) {
|
|
1109
|
+
cache.set(term.value, this.vectorSearchResults(source, term.value));
|
|
1110
|
+
}
|
|
1111
|
+
results = cache.get(term.value) ?? [];
|
|
1112
|
+
if (!countedSources.has(term.value)) {
|
|
1113
|
+
metrics.scannedRows += results.length;
|
|
1114
|
+
countedSources.add(term.value);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
for (const result of results) {
|
|
1118
|
+
const next = bindVectorSearchResult(binding, source.pattern, result);
|
|
1119
|
+
if (next) {
|
|
1120
|
+
output.push(next);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return output;
|
|
1125
|
+
}
|
|
1126
|
+
canUseBoundSourceSearch(input, source, sourceVariable) {
|
|
1127
|
+
if (hasSearchWindow(source)) {
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
const boundSources = new Set();
|
|
1131
|
+
let sawBound = false;
|
|
1132
|
+
for (const binding of input) {
|
|
1133
|
+
const term = binding[sourceVariable];
|
|
1134
|
+
if (!term) {
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
sawBound = true;
|
|
1138
|
+
if (term.termType !== 'NamedNode') {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
boundSources.add(term.value);
|
|
1142
|
+
}
|
|
1143
|
+
return sawBound && boundSources.size > 0;
|
|
1144
|
+
}
|
|
1145
|
+
textSearchResults(source, exactSource) {
|
|
1146
|
+
if (exactSource === undefined && source.results) {
|
|
1147
|
+
return source.results;
|
|
1148
|
+
}
|
|
1149
|
+
if (!this.textIndex) {
|
|
1150
|
+
throw new Error('RdfLocalQuery textSearch requires a configured RdfTextIndex');
|
|
1151
|
+
}
|
|
1152
|
+
const options = this.textSearchOptions(source.pattern, exactSource);
|
|
1153
|
+
const results = options ? this.textIndex.search(options) : [];
|
|
1154
|
+
if (exactSource === undefined) {
|
|
1155
|
+
source.results = results;
|
|
1156
|
+
}
|
|
1157
|
+
return results;
|
|
1158
|
+
}
|
|
1159
|
+
vectorSearchResults(source, exactSource) {
|
|
1160
|
+
if (exactSource === undefined && source.results) {
|
|
1161
|
+
return source.results;
|
|
1162
|
+
}
|
|
1163
|
+
if (!this.vectorIndex) {
|
|
1164
|
+
throw new Error('RdfLocalQuery vectorSearch requires a configured RdfVectorIndex');
|
|
1165
|
+
}
|
|
1166
|
+
const options = this.vectorSearchOptions(source.pattern, exactSource);
|
|
1167
|
+
const results = options ? this.vectorIndex.search(options) : [];
|
|
1168
|
+
if (exactSource === undefined) {
|
|
1169
|
+
source.results = results;
|
|
1170
|
+
}
|
|
1171
|
+
return results;
|
|
1172
|
+
}
|
|
1173
|
+
textSearchOptions(pattern, exactSource) {
|
|
1174
|
+
return {
|
|
1175
|
+
query: pattern.query,
|
|
1176
|
+
source: exactSource,
|
|
1177
|
+
workspace: pattern.scope?.workspace,
|
|
1178
|
+
sourcePrefix: pattern.scope?.sourcePrefix,
|
|
1179
|
+
limit: pattern.limit,
|
|
1180
|
+
offset: pattern.offset,
|
|
1181
|
+
orderBy: pattern.orderBy,
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
vectorSearchOptions(pattern, exactSource) {
|
|
1185
|
+
return {
|
|
1186
|
+
embedding: pattern.embedding,
|
|
1187
|
+
metric: pattern.metric,
|
|
1188
|
+
model: pattern.vectorModel,
|
|
1189
|
+
source: exactSource,
|
|
1190
|
+
workspace: pattern.scope?.workspace,
|
|
1191
|
+
sourcePrefix: pattern.scope?.sourcePrefix,
|
|
1192
|
+
limit: pattern.limit,
|
|
1193
|
+
offset: pattern.offset,
|
|
1194
|
+
threshold: pattern.threshold,
|
|
1195
|
+
orderBy: pattern.orderBy,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
applyBinds(input, binds) {
|
|
1199
|
+
let output = input;
|
|
1200
|
+
for (const bind of binds) {
|
|
1201
|
+
output = output.flatMap((binding) => {
|
|
1202
|
+
if (binding[bind.variable]) {
|
|
1203
|
+
return [];
|
|
1204
|
+
}
|
|
1205
|
+
const value = this.evaluateBindExpression(bind.expression, binding);
|
|
1206
|
+
return value ? [{ ...binding, [bind.variable]: value }] : [binding];
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
return output;
|
|
1210
|
+
}
|
|
1211
|
+
evaluateBindExpression(expression, binding) {
|
|
1212
|
+
switch (expression.type) {
|
|
1213
|
+
case 'term':
|
|
1214
|
+
return expression.term;
|
|
1215
|
+
case 'variable':
|
|
1216
|
+
return binding[expression.variable];
|
|
1217
|
+
case 'stringValue': {
|
|
1218
|
+
const value = binding[expression.variable];
|
|
1219
|
+
return value ? n3_1.DataFactory.literal(value.value) : undefined;
|
|
1220
|
+
}
|
|
1221
|
+
case 'stringLength': {
|
|
1222
|
+
const value = binding[expression.variable];
|
|
1223
|
+
return value ? countLiteral(value.value.length) : undefined;
|
|
1224
|
+
}
|
|
1225
|
+
case 'lowerCase': {
|
|
1226
|
+
const value = this.evaluateBindExpression(expression.expression, binding);
|
|
1227
|
+
return value ? n3_1.DataFactory.literal(value.value.toLocaleLowerCase('en-US')) : undefined;
|
|
1228
|
+
}
|
|
1229
|
+
case 'upperCase': {
|
|
1230
|
+
const value = this.evaluateBindExpression(expression.expression, binding);
|
|
1231
|
+
return value ? n3_1.DataFactory.literal(value.value.toLocaleUpperCase('en-US')) : undefined;
|
|
1232
|
+
}
|
|
1233
|
+
case 'substring': {
|
|
1234
|
+
const value = this.evaluateBindExpression(expression.expression, binding);
|
|
1235
|
+
const startTerm = this.evaluateBindExpression(expression.start, binding);
|
|
1236
|
+
const startValue = startTerm ? finiteBindNumber(startTerm) : undefined;
|
|
1237
|
+
const lengthTerm = expression.length ? this.evaluateBindExpression(expression.length, binding) : undefined;
|
|
1238
|
+
const lengthValue = lengthTerm ? finiteBindNumber(lengthTerm) : undefined;
|
|
1239
|
+
if (!value || startValue === undefined || (expression.length && lengthValue === undefined)) {
|
|
1240
|
+
return undefined;
|
|
1241
|
+
}
|
|
1242
|
+
const start = Math.max(0, Math.round(startValue) - 1);
|
|
1243
|
+
const length = lengthValue === undefined ? undefined : Math.max(0, Math.round(lengthValue));
|
|
1244
|
+
return n3_1.DataFactory.literal(value.value.slice(start, length === undefined ? undefined : start + length));
|
|
1245
|
+
}
|
|
1246
|
+
case 'concat': {
|
|
1247
|
+
const values = expression.expressions.map((item) => this.evaluateBindExpression(item, binding));
|
|
1248
|
+
return values.every((value) => Boolean(value))
|
|
1249
|
+
? n3_1.DataFactory.literal(values.map((value) => value.value).join(''))
|
|
1250
|
+
: undefined;
|
|
1251
|
+
}
|
|
1252
|
+
case 'iri': {
|
|
1253
|
+
const value = this.evaluateBindExpression(expression.expression, binding);
|
|
1254
|
+
if (!value) {
|
|
1255
|
+
return undefined;
|
|
1256
|
+
}
|
|
1257
|
+
try {
|
|
1258
|
+
return n3_1.DataFactory.namedNode(new URL(value.value, expression.base).href);
|
|
1259
|
+
}
|
|
1260
|
+
catch {
|
|
1261
|
+
return undefined;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
default: {
|
|
1265
|
+
const exhaustive = expression;
|
|
1266
|
+
throw new Error(`Unsupported RDF local BIND expression: ${JSON.stringify(exhaustive)}`);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
singleScanPushdown(query, requiredPatterns, filters) {
|
|
1271
|
+
if (requiredPatterns.length !== 1
|
|
1272
|
+
|| (query.values?.length ?? 0) > 0
|
|
1273
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
1274
|
+
|| (query.vectorSearch?.length ?? 0) > 0
|
|
1275
|
+
|| (query.unions?.length ?? 0) > 0
|
|
1276
|
+
|| (query.minus?.length ?? 0) > 0
|
|
1277
|
+
|| (query.exists?.length ?? 0) > 0
|
|
1278
|
+
|| (query.optional?.length ?? 0) > 0
|
|
1279
|
+
|| queryAggregates(query).length > 0) {
|
|
1280
|
+
return undefined;
|
|
1281
|
+
}
|
|
1282
|
+
const pattern = requiredPatterns[0];
|
|
1283
|
+
const order = this.scanOrderForPattern(pattern, query.orderBy ?? []);
|
|
1284
|
+
const orderRequested = (query.orderBy?.length ?? 0) > 0;
|
|
1285
|
+
const orderPushed = Boolean(order);
|
|
1286
|
+
const paginationRequested = query.limit !== undefined || query.offset !== undefined;
|
|
1287
|
+
const paginationPushed = paginationRequested
|
|
1288
|
+
&& !query.distinct
|
|
1289
|
+
&& (!orderRequested || orderPushed)
|
|
1290
|
+
&& !patternHasRepeatedVariables(pattern)
|
|
1291
|
+
&& this.canPushAllFiltersForPattern(pattern, filters);
|
|
1292
|
+
if (!orderPushed && !paginationPushed) {
|
|
1293
|
+
return undefined;
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
options: {
|
|
1297
|
+
...(order ?? {}),
|
|
1298
|
+
...(paginationPushed && query.limit !== undefined ? { limit: Math.max(0, query.limit) } : {}),
|
|
1299
|
+
...(paginationPushed && query.offset !== undefined ? { offset: Math.max(0, query.offset) } : {}),
|
|
1300
|
+
},
|
|
1301
|
+
orderPushed,
|
|
1302
|
+
paginationPushed,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
countPushdown(query, requiredPatterns, filters) {
|
|
1306
|
+
const aggregates = queryAggregates(query);
|
|
1307
|
+
if (aggregates.length !== 1
|
|
1308
|
+
|| aggregates[0].type !== 'count'
|
|
1309
|
+
|| (query.having?.length ?? 0) > 0
|
|
1310
|
+
|| requiredPatterns.length !== 1
|
|
1311
|
+
|| (query.values?.length ?? 0) > 0
|
|
1312
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
1313
|
+
|| (query.vectorSearch?.length ?? 0) > 0
|
|
1314
|
+
|| (query.unions?.length ?? 0) > 0
|
|
1315
|
+
|| (query.minus?.length ?? 0) > 0
|
|
1316
|
+
|| (query.exists?.length ?? 0) > 0
|
|
1317
|
+
|| (query.optional?.length ?? 0) > 0
|
|
1318
|
+
|| (query.groupBy?.length ?? 0) > 0
|
|
1319
|
+
|| query.orderBy?.length
|
|
1320
|
+
|| query.limit !== undefined
|
|
1321
|
+
|| query.offset !== undefined) {
|
|
1322
|
+
return undefined;
|
|
1323
|
+
}
|
|
1324
|
+
const pattern = requiredPatterns[0];
|
|
1325
|
+
if (patternHasRepeatedVariables(pattern)) {
|
|
1326
|
+
return undefined;
|
|
1327
|
+
}
|
|
1328
|
+
const aggregate = aggregates[0];
|
|
1329
|
+
if (aggregate.variable && !variablesInPattern(pattern).includes(aggregate.variable)) {
|
|
1330
|
+
return undefined;
|
|
1331
|
+
}
|
|
1332
|
+
const distinctKey = aggregate.distinct
|
|
1333
|
+
? this.distinctCountKey(pattern, aggregate.variable)
|
|
1334
|
+
: undefined;
|
|
1335
|
+
if (aggregate.distinct && !distinctKey) {
|
|
1336
|
+
return undefined;
|
|
1337
|
+
}
|
|
1338
|
+
if (!this.canPushAllFiltersForPattern(pattern, filters)) {
|
|
1339
|
+
return undefined;
|
|
1340
|
+
}
|
|
1341
|
+
const compiled = this.compilePattern(pattern, {}, filters);
|
|
1342
|
+
return compiled
|
|
1343
|
+
? {
|
|
1344
|
+
as: aggregate.as,
|
|
1345
|
+
pattern: compiled,
|
|
1346
|
+
distinctKey,
|
|
1347
|
+
pushedDownFilters: compiled.pushedDownFilters,
|
|
1348
|
+
}
|
|
1349
|
+
: undefined;
|
|
1350
|
+
}
|
|
1351
|
+
joinCountPushdown(query, requiredPatterns, filters, aggregates) {
|
|
1352
|
+
if (aggregates.length === 0
|
|
1353
|
+
|| requiredPatterns.length < 2
|
|
1354
|
+
|| (query.having?.length ?? 0) > 0
|
|
1355
|
+
|| (query.groupBy?.length ?? 0) > 0
|
|
1356
|
+
|| (query.values?.length ?? 0) > 0
|
|
1357
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
1358
|
+
|| (query.vectorSearch?.length ?? 0) > 0
|
|
1359
|
+
|| (query.unions?.length ?? 0) > 0
|
|
1360
|
+
|| (query.minus?.length ?? 0) > 0
|
|
1361
|
+
|| (query.exists?.length ?? 0) > 0
|
|
1362
|
+
|| (query.optional?.length ?? 0) > 0
|
|
1363
|
+
|| (query.binds?.length ?? 0) > 0
|
|
1364
|
+
|| query.orderBy?.length
|
|
1365
|
+
|| query.limit !== undefined
|
|
1366
|
+
|| query.offset !== undefined
|
|
1367
|
+
|| query.distinct) {
|
|
1368
|
+
return undefined;
|
|
1369
|
+
}
|
|
1370
|
+
if (aggregates.some((aggregate) => aggregate.type !== 'count')) {
|
|
1371
|
+
return undefined;
|
|
1372
|
+
}
|
|
1373
|
+
if (!this.canPushRequiredBgpFilters(requiredPatterns, filters)) {
|
|
1374
|
+
return undefined;
|
|
1375
|
+
}
|
|
1376
|
+
const visibleVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
1377
|
+
if (aggregates.some((aggregate) => aggregate.variable && !visibleVariables.has(aggregate.variable))) {
|
|
1378
|
+
return undefined;
|
|
1379
|
+
}
|
|
1380
|
+
const sharedVariables = variablesSharedAcrossPatterns(requiredPatterns);
|
|
1381
|
+
if (sharedVariables.size === 0) {
|
|
1382
|
+
return undefined;
|
|
1383
|
+
}
|
|
1384
|
+
const compiled = requiredPatterns.map((pattern) => this.compileJoinPattern(pattern, filters));
|
|
1385
|
+
if (compiled.some((entry) => !entry)) {
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
const reordered = this.reorderJoinPatterns(requiredPatterns, compiled, filters);
|
|
1389
|
+
return {
|
|
1390
|
+
patterns: reordered.patterns,
|
|
1391
|
+
...(reordered.reorderPlan ? { reorderPlan: reordered.reorderPlan } : {}),
|
|
1392
|
+
pushedDownFilters: filters.length,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
joinBasicAggregatePushdown(query, requiredPatterns, filters, aggregates) {
|
|
1396
|
+
if (aggregates.length === 0
|
|
1397
|
+
|| !aggregates.some((aggregate) => aggregate.type !== 'count')
|
|
1398
|
+
|| requiredPatterns.length === 0
|
|
1399
|
+
|| (query.having?.length ?? 0) > 0
|
|
1400
|
+
|| (query.groupBy?.length ?? 0) > 0
|
|
1401
|
+
|| (query.values?.length ?? 0) > 0
|
|
1402
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
1403
|
+
|| (query.vectorSearch?.length ?? 0) > 0
|
|
1404
|
+
|| (query.unions?.length ?? 0) > 0
|
|
1405
|
+
|| (query.minus?.length ?? 0) > 0
|
|
1406
|
+
|| (query.exists?.length ?? 0) > 0
|
|
1407
|
+
|| (query.optional?.length ?? 0) > 0
|
|
1408
|
+
|| (query.binds?.length ?? 0) > 0
|
|
1409
|
+
|| query.orderBy?.length
|
|
1410
|
+
|| query.limit !== undefined
|
|
1411
|
+
|| query.offset !== undefined
|
|
1412
|
+
|| query.distinct) {
|
|
1413
|
+
return undefined;
|
|
1414
|
+
}
|
|
1415
|
+
if (aggregates.some((aggregate) => aggregate.distinct || aggregate.type !== 'count' && !aggregate.variable)) {
|
|
1416
|
+
return undefined;
|
|
1417
|
+
}
|
|
1418
|
+
const visibleVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
1419
|
+
if (aggregates.some((aggregate) => aggregate.variable && !visibleVariables.has(aggregate.variable))) {
|
|
1420
|
+
return undefined;
|
|
1421
|
+
}
|
|
1422
|
+
const numericAggregateVariables = new Set(aggregates
|
|
1423
|
+
.filter((aggregate) => aggregate.type !== 'count')
|
|
1424
|
+
.map((aggregate) => aggregate.variable)
|
|
1425
|
+
.filter((variableName) => Boolean(variableName)));
|
|
1426
|
+
if (!this.canPushAggregateFilters(requiredPatterns, filters, numericAggregateVariables)) {
|
|
1427
|
+
return undefined;
|
|
1428
|
+
}
|
|
1429
|
+
const sharedVariables = variablesSharedAcrossPatterns(requiredPatterns);
|
|
1430
|
+
if (requiredPatterns.length > 1 && sharedVariables.size === 0) {
|
|
1431
|
+
return undefined;
|
|
1432
|
+
}
|
|
1433
|
+
const compiled = requiredPatterns.map((pattern) => this.compileJoinPattern(pattern, filters));
|
|
1434
|
+
if (compiled.some((entry) => !entry)) {
|
|
1435
|
+
return undefined;
|
|
1436
|
+
}
|
|
1437
|
+
const reordered = this.reorderJoinPatterns(requiredPatterns, compiled, filters);
|
|
1438
|
+
return {
|
|
1439
|
+
patterns: reordered.patterns,
|
|
1440
|
+
...(reordered.reorderPlan ? { reorderPlan: reordered.reorderPlan } : {}),
|
|
1441
|
+
pushedDownFilters: filters.length,
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
canPushAggregateFilters(requiredPatterns, filters, numericAggregateVariables) {
|
|
1445
|
+
const variables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
1446
|
+
return filters.every((filter) => {
|
|
1447
|
+
if (!variables.has(filter.variable) || filter.variable2) {
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
if (isNumericGuardFilter(filter) && numericAggregateVariables.has(filter.variable)) {
|
|
1451
|
+
return true;
|
|
1452
|
+
}
|
|
1453
|
+
return isPushdownFilter(filter) && this.compilePushdownFilter(filter.variable, [filter]) !== null;
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
hasNumericAggregateGuards(filters, numericAggregateVariables) {
|
|
1457
|
+
return [...numericAggregateVariables].every((variableName) => (filters.some((filter) => isNumericGuardFilter(filter) && filter.variable === variableName)));
|
|
1458
|
+
}
|
|
1459
|
+
groupAggregatePushdown(query, requiredPatterns, filters, aggregates) {
|
|
1460
|
+
if (aggregates.length === 0
|
|
1461
|
+
|| (query.groupBy?.length ?? 0) === 0
|
|
1462
|
+
|| (query.values?.length ?? 0) > 0
|
|
1463
|
+
|| (query.textSearch?.length ?? 0) > 0
|
|
1464
|
+
|| (query.vectorSearch?.length ?? 0) > 0
|
|
1465
|
+
|| (query.unions?.length ?? 0) > 0
|
|
1466
|
+
|| (query.minus?.length ?? 0) > 0
|
|
1467
|
+
|| (query.exists?.length ?? 0) > 0
|
|
1468
|
+
|| (query.optional?.length ?? 0) > 0
|
|
1469
|
+
|| (query.binds?.length ?? 0) > 0
|
|
1470
|
+
|| query.distinct) {
|
|
1471
|
+
return undefined;
|
|
1472
|
+
}
|
|
1473
|
+
if (aggregates.some((aggregate) => (aggregate.type !== 'count'
|
|
1474
|
+
&& (!aggregate.variable || aggregate.distinct)))) {
|
|
1475
|
+
return undefined;
|
|
1476
|
+
}
|
|
1477
|
+
const numericAggregateVariables = new Set(aggregates
|
|
1478
|
+
.filter((aggregate) => aggregate.type !== 'count')
|
|
1479
|
+
.map((aggregate) => aggregate.variable)
|
|
1480
|
+
.filter((variableName) => Boolean(variableName)));
|
|
1481
|
+
if (!this.hasNumericAggregateGuards(filters, numericAggregateVariables)
|
|
1482
|
+
|| !this.canPushAggregateFilters(requiredPatterns, filters, numericAggregateVariables)) {
|
|
1483
|
+
return undefined;
|
|
1484
|
+
}
|
|
1485
|
+
const visibleVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
1486
|
+
if ((query.groupBy ?? []).some((variableName) => !visibleVariables.has(variableName))) {
|
|
1487
|
+
return undefined;
|
|
1488
|
+
}
|
|
1489
|
+
if (aggregates.some((aggregate) => aggregate.variable && !visibleVariables.has(aggregate.variable))) {
|
|
1490
|
+
return undefined;
|
|
1491
|
+
}
|
|
1492
|
+
const countOnly = numericAggregateVariables.size === 0;
|
|
1493
|
+
const aggregateVariables = new Set(aggregates.map((aggregate) => aggregate.as));
|
|
1494
|
+
if ((query.orderBy ?? []).some((entry) => (!(query.groupBy ?? []).includes(entry.variable) && !aggregateVariables.has(entry.variable)))) {
|
|
1495
|
+
return undefined;
|
|
1496
|
+
}
|
|
1497
|
+
const orderPushed = (query.orderBy?.length ?? 0) > 0;
|
|
1498
|
+
const havingPushdown = this.groupAggregateHavingPushdown(query.having ?? [], aggregateVariables);
|
|
1499
|
+
const havingPushed = (query.having?.length ?? 0) === 0 || havingPushdown !== undefined;
|
|
1500
|
+
const paginationPushed = (query.limit !== undefined || query.offset !== undefined) && havingPushed;
|
|
1501
|
+
const sharedVariables = variablesSharedAcrossPatterns(requiredPatterns);
|
|
1502
|
+
if (requiredPatterns.length > 1 && sharedVariables.size === 0) {
|
|
1503
|
+
return undefined;
|
|
1504
|
+
}
|
|
1505
|
+
const compiled = requiredPatterns.map((pattern) => this.compileJoinPattern(pattern, filters));
|
|
1506
|
+
if (compiled.some((entry) => !entry)) {
|
|
1507
|
+
return undefined;
|
|
1508
|
+
}
|
|
1509
|
+
const reordered = this.reorderJoinPatterns(requiredPatterns, compiled, filters);
|
|
1510
|
+
return {
|
|
1511
|
+
patterns: reordered.patterns,
|
|
1512
|
+
...(reordered.reorderPlan ? { reorderPlan: reordered.reorderPlan } : {}),
|
|
1513
|
+
...(havingPushdown && havingPushdown.length > 0 ? { having: havingPushdown } : {}),
|
|
1514
|
+
pushedDownFilters: filters.length,
|
|
1515
|
+
pushedDownHaving: havingPushdown?.length ?? 0,
|
|
1516
|
+
orderPushed,
|
|
1517
|
+
paginationPushed,
|
|
1518
|
+
countOnly,
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
groupAggregateHavingPushdown(having, aggregateVariables) {
|
|
1522
|
+
const compiled = [];
|
|
1523
|
+
for (const filter of having) {
|
|
1524
|
+
if (!aggregateVariables.has(filter.variable)
|
|
1525
|
+
|| filter.operand
|
|
1526
|
+
|| filter.variable2
|
|
1527
|
+
|| filter.value === undefined
|
|
1528
|
+
|| !isGroupAggregateHavingOperator(filter.operator)) {
|
|
1529
|
+
return undefined;
|
|
1530
|
+
}
|
|
1531
|
+
const value = filterValueToNumber(filter.value);
|
|
1532
|
+
if (value === undefined) {
|
|
1533
|
+
return undefined;
|
|
1534
|
+
}
|
|
1535
|
+
compiled.push({
|
|
1536
|
+
aggregate: filter.variable,
|
|
1537
|
+
operator: filter.operator,
|
|
1538
|
+
value,
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
return compiled;
|
|
1542
|
+
}
|
|
1543
|
+
distinctCountKey(pattern, variableName) {
|
|
1544
|
+
if (!variableName) {
|
|
1545
|
+
return undefined;
|
|
1546
|
+
}
|
|
1547
|
+
const keys = TERM_KEYS.filter((key) => {
|
|
1548
|
+
const value = pattern[key];
|
|
1549
|
+
return isVariable(value) && value.variable === variableName;
|
|
1550
|
+
});
|
|
1551
|
+
return keys.length === 1 ? keys[0] : undefined;
|
|
1552
|
+
}
|
|
1553
|
+
scanOrderForPattern(pattern, orderBy) {
|
|
1554
|
+
if (orderBy.length === 0) {
|
|
1555
|
+
return undefined;
|
|
1556
|
+
}
|
|
1557
|
+
const order = orderBy.map((entry) => termKeyForVariable(pattern, entry.variable));
|
|
1558
|
+
if (order.some((key) => key === undefined)) {
|
|
1559
|
+
return undefined;
|
|
1560
|
+
}
|
|
1561
|
+
const orderDirections = orderBy.map((entry) => entry.direction ?? 'asc');
|
|
1562
|
+
const firstDirection = orderDirections[0];
|
|
1563
|
+
const sameDirection = orderDirections.every((direction) => direction === firstDirection);
|
|
1564
|
+
return {
|
|
1565
|
+
order: order,
|
|
1566
|
+
...(sameDirection
|
|
1567
|
+
? { reverse: firstDirection === 'desc' || undefined }
|
|
1568
|
+
: { orderDirections }),
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
canPushAllFiltersForPattern(pattern, filters) {
|
|
1572
|
+
const variables = new Set(variablesInPattern(pattern));
|
|
1573
|
+
return filters.every((filter) => (variables.has(filter.variable)
|
|
1574
|
+
&& isPushdownFilter(filter)
|
|
1575
|
+
&& this.compilePushdownFilter(filter.variable, [filter]) !== null));
|
|
1576
|
+
}
|
|
1577
|
+
requiredFiltersNeedingPostApply(query, requiredPatterns, filters, requiredBgpPushdown) {
|
|
1578
|
+
if (filters.length === 0) {
|
|
1579
|
+
return filters;
|
|
1580
|
+
}
|
|
1581
|
+
if (requiredBgpPushdown) {
|
|
1582
|
+
return [];
|
|
1583
|
+
}
|
|
1584
|
+
if (!this.canElideRequiredPatternFilterRechecks(query, requiredPatterns)) {
|
|
1585
|
+
return filters;
|
|
1586
|
+
}
|
|
1587
|
+
const requiredVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
|
|
1588
|
+
return filters.filter((filter) => !(requiredVariables.has(filter.variable)
|
|
1589
|
+
&& this.compilePushdownFilter(filter.variable, [filter]) !== null));
|
|
1590
|
+
}
|
|
1591
|
+
canElideRequiredPatternFilterRechecks(query, requiredPatterns) {
|
|
1592
|
+
return requiredPatterns.length > 0
|
|
1593
|
+
&& (query.values?.length ?? 0) === 0
|
|
1594
|
+
&& (query.textSearch?.length ?? 0) === 0
|
|
1595
|
+
&& (query.vectorSearch?.length ?? 0) === 0
|
|
1596
|
+
&& (query.unions?.length ?? 0) === 0
|
|
1597
|
+
&& (query.minus?.length ?? 0) === 0
|
|
1598
|
+
&& (query.exists?.length ?? 0) === 0
|
|
1599
|
+
&& (query.optional?.length ?? 0) === 0
|
|
1600
|
+
&& (query.binds?.length ?? 0) === 0
|
|
1601
|
+
&& queryAggregates(query).length === 0;
|
|
1602
|
+
}
|
|
1603
|
+
compilePattern(pattern, binding, filters) {
|
|
1604
|
+
const compiled = {};
|
|
1605
|
+
const pushedDownFilterIndexes = new Set();
|
|
1606
|
+
for (const key of TERM_KEYS) {
|
|
1607
|
+
const value = pattern[key];
|
|
1608
|
+
if (!value)
|
|
1609
|
+
continue;
|
|
1610
|
+
if (isVariable(value)) {
|
|
1611
|
+
const bound = binding[value.variable];
|
|
1612
|
+
if (bound) {
|
|
1613
|
+
compiled[key] = bound;
|
|
1614
|
+
}
|
|
1615
|
+
else {
|
|
1616
|
+
const pushdown = this.compilePushdownFilterWithIndexes(value.variable, filters);
|
|
1617
|
+
if (pushdown) {
|
|
1618
|
+
compiled[key] = pushdown.pattern;
|
|
1619
|
+
pushdown.filterIndexes.forEach((index) => pushedDownFilterIndexes.add(index));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
else {
|
|
1624
|
+
compiled[key] = value;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return isConsistentPattern(compiled)
|
|
1628
|
+
? {
|
|
1629
|
+
...compiled,
|
|
1630
|
+
pushedDownFilters: pushedDownFilterIndexes.size,
|
|
1631
|
+
pushedDownFilterIndexes: [...pushedDownFilterIndexes],
|
|
1632
|
+
}
|
|
1633
|
+
: null;
|
|
1634
|
+
}
|
|
1635
|
+
bindQuad(pattern, binding, quad) {
|
|
1636
|
+
const next = { ...binding };
|
|
1637
|
+
for (const key of TERM_KEYS) {
|
|
1638
|
+
const value = pattern[key];
|
|
1639
|
+
if (!isVariable(value))
|
|
1640
|
+
continue;
|
|
1641
|
+
const term = quad[key];
|
|
1642
|
+
const existing = next[value.variable];
|
|
1643
|
+
if (existing && !sameTerm(existing, term)) {
|
|
1644
|
+
return null;
|
|
1645
|
+
}
|
|
1646
|
+
next[value.variable] = term;
|
|
1647
|
+
}
|
|
1648
|
+
return next;
|
|
1649
|
+
}
|
|
1650
|
+
countBindings(bindings, variable, distinct) {
|
|
1651
|
+
if (!distinct) {
|
|
1652
|
+
return variable ? bindings.filter((binding) => binding[variable]).length : bindings.length;
|
|
1653
|
+
}
|
|
1654
|
+
if (!variable) {
|
|
1655
|
+
return new Set(bindings.map((binding) => bindingKey(binding))).size;
|
|
1656
|
+
}
|
|
1657
|
+
return new Set(bindings
|
|
1658
|
+
.map((binding) => binding[variable])
|
|
1659
|
+
.filter((term) => Boolean(term))
|
|
1660
|
+
.map((term) => (0, n3_1.termToId)(term))).size;
|
|
1661
|
+
}
|
|
1662
|
+
aggregateBindings(bindings, aggregates) {
|
|
1663
|
+
const binding = {};
|
|
1664
|
+
let firstCount = 0;
|
|
1665
|
+
aggregates.forEach((aggregate, index) => {
|
|
1666
|
+
const count = aggregate.type === 'count'
|
|
1667
|
+
? this.countBindings(bindings, aggregate.variable, aggregate.distinct)
|
|
1668
|
+
: 0;
|
|
1669
|
+
if (index === 0) {
|
|
1670
|
+
firstCount = count;
|
|
1671
|
+
}
|
|
1672
|
+
const term = this.aggregateLiteral(bindings, aggregate);
|
|
1673
|
+
if (term) {
|
|
1674
|
+
binding[aggregate.as] = term;
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
return { binding, firstCount };
|
|
1678
|
+
}
|
|
1679
|
+
groupAggregateBindings(bindings, groupBy, aggregates) {
|
|
1680
|
+
const groups = new Map();
|
|
1681
|
+
for (const binding of bindings) {
|
|
1682
|
+
const groupKey = groupBy.map((variableName) => {
|
|
1683
|
+
const term = binding[variableName];
|
|
1684
|
+
return term ? (0, n3_1.termToId)(term) : '__UNBOUND__';
|
|
1685
|
+
}).join('\u001f');
|
|
1686
|
+
const existing = groups.get(groupKey);
|
|
1687
|
+
if (existing) {
|
|
1688
|
+
existing.push(binding);
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
groups.set(groupKey, [binding]);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
return [...groups.values()].map((groupBindings) => {
|
|
1695
|
+
const first = groupBindings[0];
|
|
1696
|
+
const grouped = {};
|
|
1697
|
+
for (const variableName of groupBy) {
|
|
1698
|
+
if (first[variableName]) {
|
|
1699
|
+
grouped[variableName] = first[variableName];
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
for (const aggregate of aggregates) {
|
|
1703
|
+
const term = this.aggregateLiteral(groupBindings, aggregate);
|
|
1704
|
+
if (term) {
|
|
1705
|
+
grouped[aggregate.as] = term;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return grouped;
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
aggregateLiteral(bindings, aggregate) {
|
|
1712
|
+
if (aggregate.type === 'count') {
|
|
1713
|
+
return countLiteral(this.countBindings(bindings, aggregate.variable, aggregate.distinct));
|
|
1714
|
+
}
|
|
1715
|
+
const values = this.numericAggregateValues(bindings, aggregate.variable, aggregate.distinct);
|
|
1716
|
+
if (values.length === 0) {
|
|
1717
|
+
return aggregate.type === 'sum' ? decimalLiteral(0) : undefined;
|
|
1718
|
+
}
|
|
1719
|
+
switch (aggregate.type) {
|
|
1720
|
+
case 'sum':
|
|
1721
|
+
return decimalLiteral(values.reduce((total, value) => total + value, 0));
|
|
1722
|
+
case 'avg':
|
|
1723
|
+
return decimalLiteral(values.reduce((total, value) => total + value, 0) / values.length);
|
|
1724
|
+
case 'min':
|
|
1725
|
+
return decimalLiteral(Math.min(...values));
|
|
1726
|
+
case 'max':
|
|
1727
|
+
return decimalLiteral(Math.max(...values));
|
|
1728
|
+
default: {
|
|
1729
|
+
const exhaustive = aggregate.type;
|
|
1730
|
+
throw new Error(`Unsupported RDF local aggregate type: ${exhaustive}`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
numericAggregateValues(bindings, variable, distinct) {
|
|
1735
|
+
if (!variable) {
|
|
1736
|
+
return [];
|
|
1737
|
+
}
|
|
1738
|
+
const values = [];
|
|
1739
|
+
const seen = new Set();
|
|
1740
|
+
for (const binding of bindings) {
|
|
1741
|
+
const term = binding[variable];
|
|
1742
|
+
if (!term || !isNumericTerm(term)) {
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
if (distinct) {
|
|
1746
|
+
const key = (0, n3_1.termToId)(term);
|
|
1747
|
+
if (seen.has(key)) {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
seen.add(key);
|
|
1751
|
+
}
|
|
1752
|
+
values.push((0, RdfTermSemantics_1.rdfNumericValue)(term.value));
|
|
1753
|
+
}
|
|
1754
|
+
return values;
|
|
1755
|
+
}
|
|
1756
|
+
compilePushdownFilter(variableName, filters) {
|
|
1757
|
+
return this.compilePushdownFilterWithIndexes(variableName, filters)?.pattern ?? null;
|
|
1758
|
+
}
|
|
1759
|
+
compilePushdownFilterWithIndexes(variableName, filters) {
|
|
1760
|
+
const pushable = filters
|
|
1761
|
+
.map((filter, index) => ({ filter, index }))
|
|
1762
|
+
.filter(({ filter }) => (filter.variable === variableName
|
|
1763
|
+
&& isPushdownFilter(filter)));
|
|
1764
|
+
if (pushable.length === 0) {
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
const operators = {};
|
|
1768
|
+
const filterIndexes = [];
|
|
1769
|
+
for (const { filter, index } of pushable) {
|
|
1770
|
+
switch (filter.operator) {
|
|
1771
|
+
case '$eq':
|
|
1772
|
+
case '$ne':
|
|
1773
|
+
if (filter.value === undefined)
|
|
1774
|
+
return null;
|
|
1775
|
+
if (!(0, types_1.isTerm)(filter.value))
|
|
1776
|
+
return null;
|
|
1777
|
+
operators[filter.operator] = filter.value;
|
|
1778
|
+
filterIndexes.push(index);
|
|
1779
|
+
break;
|
|
1780
|
+
case '$gt':
|
|
1781
|
+
case '$gte':
|
|
1782
|
+
case '$lt':
|
|
1783
|
+
case '$lte':
|
|
1784
|
+
if (filter.value === undefined)
|
|
1785
|
+
return null;
|
|
1786
|
+
operators[filter.operator] = filter.value;
|
|
1787
|
+
filterIndexes.push(index);
|
|
1788
|
+
break;
|
|
1789
|
+
case '$in':
|
|
1790
|
+
case '$notIn':
|
|
1791
|
+
if (!filter.values || filter.values.length === 0)
|
|
1792
|
+
return null;
|
|
1793
|
+
if (filter.values.some((value) => !(0, types_1.isTerm)(value)))
|
|
1794
|
+
return null;
|
|
1795
|
+
operators[filter.operator] = filter.values;
|
|
1796
|
+
filterIndexes.push(index);
|
|
1797
|
+
break;
|
|
1798
|
+
case '$sameTerm':
|
|
1799
|
+
if (filter.variable2 || filter.value === undefined)
|
|
1800
|
+
return null;
|
|
1801
|
+
if (!(0, types_1.isTerm)(filter.value))
|
|
1802
|
+
return null;
|
|
1803
|
+
operators.$eq = filter.value;
|
|
1804
|
+
filterIndexes.push(index);
|
|
1805
|
+
break;
|
|
1806
|
+
case '$termType':
|
|
1807
|
+
if (typeof filter.value !== 'string')
|
|
1808
|
+
return null;
|
|
1809
|
+
if (!['iri', 'blank', 'literal', 'numeric'].includes(filter.value))
|
|
1810
|
+
return null;
|
|
1811
|
+
operators.$termType = filter.value;
|
|
1812
|
+
filterIndexes.push(index);
|
|
1813
|
+
break;
|
|
1814
|
+
case '$lang':
|
|
1815
|
+
if (typeof filter.value !== 'string')
|
|
1816
|
+
return null;
|
|
1817
|
+
operators.$language = filter.value;
|
|
1818
|
+
filterIndexes.push(index);
|
|
1819
|
+
break;
|
|
1820
|
+
case '$notLang':
|
|
1821
|
+
if (typeof filter.value !== 'string')
|
|
1822
|
+
return null;
|
|
1823
|
+
operators.$notLanguage = filter.value;
|
|
1824
|
+
filterIndexes.push(index);
|
|
1825
|
+
break;
|
|
1826
|
+
case '$langMatches':
|
|
1827
|
+
if (typeof filter.value !== 'string')
|
|
1828
|
+
return null;
|
|
1829
|
+
operators.$langMatches = filter.value;
|
|
1830
|
+
filterIndexes.push(index);
|
|
1831
|
+
break;
|
|
1832
|
+
case '$datatype':
|
|
1833
|
+
if (filter.value === undefined || !(0, types_1.isTerm)(filter.value))
|
|
1834
|
+
return null;
|
|
1835
|
+
if (filter.value.termType !== 'NamedNode')
|
|
1836
|
+
return null;
|
|
1837
|
+
operators.$datatype = filter.value;
|
|
1838
|
+
filterIndexes.push(index);
|
|
1839
|
+
break;
|
|
1840
|
+
case '$notDatatype':
|
|
1841
|
+
if (filter.value === undefined || !(0, types_1.isTerm)(filter.value))
|
|
1842
|
+
return null;
|
|
1843
|
+
if (filter.value.termType !== 'NamedNode')
|
|
1844
|
+
return null;
|
|
1845
|
+
operators.$notDatatype = filter.value;
|
|
1846
|
+
filterIndexes.push(index);
|
|
1847
|
+
break;
|
|
1848
|
+
case '$startsWith':
|
|
1849
|
+
if (typeof filter.value !== 'string')
|
|
1850
|
+
return null;
|
|
1851
|
+
operators.$startsWith = filter.value;
|
|
1852
|
+
filterIndexes.push(index);
|
|
1853
|
+
break;
|
|
1854
|
+
case '$contains':
|
|
1855
|
+
case '$endsWith':
|
|
1856
|
+
if (typeof filter.value !== 'string')
|
|
1857
|
+
return null;
|
|
1858
|
+
operators[filter.operator] = filter.value;
|
|
1859
|
+
filterIndexes.push(index);
|
|
1860
|
+
break;
|
|
1861
|
+
case '$regex':
|
|
1862
|
+
if (typeof filter.value !== 'string')
|
|
1863
|
+
return null;
|
|
1864
|
+
if (filter.flags)
|
|
1865
|
+
return null;
|
|
1866
|
+
operators.$regex = filter.value;
|
|
1867
|
+
filterIndexes.push(index);
|
|
1868
|
+
break;
|
|
1869
|
+
default:
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return Object.keys(operators).length > 0
|
|
1874
|
+
? { pattern: operators, filterIndexes }
|
|
1875
|
+
: null;
|
|
1876
|
+
}
|
|
1877
|
+
matchesNewlyBoundFilters(binding, previousBinding, filters) {
|
|
1878
|
+
const newlyBound = filters.filter((filter) => {
|
|
1879
|
+
const variables = filterVariables(filter);
|
|
1880
|
+
return variables.every((variableName) => binding[variableName])
|
|
1881
|
+
&& variables.some((variableName) => !previousBinding[variableName]);
|
|
1882
|
+
});
|
|
1883
|
+
return this.matchesFilters(binding, newlyBound);
|
|
1884
|
+
}
|
|
1885
|
+
matchesFilters(binding, filters) {
|
|
1886
|
+
return filters.every((filter) => this.matchesFilter(binding, filter));
|
|
1887
|
+
}
|
|
1888
|
+
matchesFilter(binding, filter) {
|
|
1889
|
+
const value = binding[filter.variable];
|
|
1890
|
+
if (filter.operator === '$bound') {
|
|
1891
|
+
return Boolean(filter.value) ? Boolean(value) : !value;
|
|
1892
|
+
}
|
|
1893
|
+
if (!value) {
|
|
1894
|
+
return false;
|
|
1895
|
+
}
|
|
1896
|
+
const comparisonValue = filterOperandValue(value, filter.operand);
|
|
1897
|
+
switch (filter.operator) {
|
|
1898
|
+
case '$eq':
|
|
1899
|
+
if (filter.variable2)
|
|
1900
|
+
return this.compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison === 0);
|
|
1901
|
+
return filter.value !== undefined && sameTermOrLexical(comparisonValue, filter.value);
|
|
1902
|
+
case '$ne':
|
|
1903
|
+
if (filter.variable2)
|
|
1904
|
+
return this.compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison !== 0);
|
|
1905
|
+
return filter.value === undefined || !sameTermOrLexical(comparisonValue, filter.value);
|
|
1906
|
+
case '$gt':
|
|
1907
|
+
if (filter.variable2)
|
|
1908
|
+
return this.compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison > 0);
|
|
1909
|
+
return compareTermsForFilter(comparisonValue, filter.value) > 0;
|
|
1910
|
+
case '$gte':
|
|
1911
|
+
if (filter.variable2)
|
|
1912
|
+
return this.compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison >= 0);
|
|
1913
|
+
return compareTermsForFilter(comparisonValue, filter.value) >= 0;
|
|
1914
|
+
case '$lt':
|
|
1915
|
+
if (filter.variable2)
|
|
1916
|
+
return this.compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison < 0);
|
|
1917
|
+
return compareTermsForFilter(comparisonValue, filter.value) < 0;
|
|
1918
|
+
case '$lte':
|
|
1919
|
+
if (filter.variable2)
|
|
1920
|
+
return this.compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison <= 0);
|
|
1921
|
+
return compareTermsForFilter(comparisonValue, filter.value) <= 0;
|
|
1922
|
+
case '$in':
|
|
1923
|
+
return (filter.values ?? []).some((candidate) => sameTermOrLexical(comparisonValue, candidate));
|
|
1924
|
+
case '$notIn':
|
|
1925
|
+
return !(filter.values ?? []).some((candidate) => sameTermOrLexical(comparisonValue, candidate));
|
|
1926
|
+
case '$startsWith': {
|
|
1927
|
+
const text = filterStringValue(value, comparisonValue);
|
|
1928
|
+
return typeof filter.value === 'string' && text.startsWith(filter.value);
|
|
1929
|
+
}
|
|
1930
|
+
case '$contains': {
|
|
1931
|
+
const text = filterStringValue(value, comparisonValue);
|
|
1932
|
+
return typeof filter.value === 'string' && text.includes(filter.value);
|
|
1933
|
+
}
|
|
1934
|
+
case '$endsWith': {
|
|
1935
|
+
const text = filterStringValue(value, comparisonValue);
|
|
1936
|
+
return typeof filter.value === 'string' && text.endsWith(filter.value);
|
|
1937
|
+
}
|
|
1938
|
+
case '$regex': {
|
|
1939
|
+
const text = filterStringValue(value, comparisonValue);
|
|
1940
|
+
return typeof filter.value === 'string' && new RegExp(filter.value, filter.flags).test(text);
|
|
1941
|
+
}
|
|
1942
|
+
case '$termType':
|
|
1943
|
+
return typeof filter.value === 'string' && matchesTermType(value, filter.value);
|
|
1944
|
+
case '$sameTerm': {
|
|
1945
|
+
const right = filter.variable2 ? binding[filter.variable2] : filter.value;
|
|
1946
|
+
return Boolean(right && (0, types_1.isTerm)(right) && sameTerm(value, right));
|
|
1947
|
+
}
|
|
1948
|
+
case '$lang':
|
|
1949
|
+
return typeof filter.value === 'string'
|
|
1950
|
+
&& value.termType === 'Literal'
|
|
1951
|
+
&& value.language === filter.value;
|
|
1952
|
+
case '$notLang':
|
|
1953
|
+
return typeof filter.value === 'string'
|
|
1954
|
+
&& value.termType === 'Literal'
|
|
1955
|
+
&& value.language !== filter.value;
|
|
1956
|
+
case '$langMatches':
|
|
1957
|
+
return typeof filter.value === 'string'
|
|
1958
|
+
&& value.termType === 'Literal'
|
|
1959
|
+
&& langMatches(value.language, filter.value);
|
|
1960
|
+
case '$datatype':
|
|
1961
|
+
return filter.value !== undefined
|
|
1962
|
+
&& value.termType === 'Literal'
|
|
1963
|
+
&& sameTermOrLexical(value.datatype, filter.value);
|
|
1964
|
+
case '$notDatatype':
|
|
1965
|
+
return filter.value !== undefined
|
|
1966
|
+
&& value.termType === 'Literal'
|
|
1967
|
+
&& !sameTermOrLexical(value.datatype, filter.value);
|
|
1968
|
+
default: {
|
|
1969
|
+
const exhaustive = filter.operator;
|
|
1970
|
+
throw new Error(`Unsupported RDF local query filter operator: ${exhaustive}`);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
compareVariableFilter(binding, comparisonValue, filter, predicate) {
|
|
1975
|
+
if (!filter.variable2) {
|
|
1976
|
+
return false;
|
|
1977
|
+
}
|
|
1978
|
+
const right = binding[filter.variable2];
|
|
1979
|
+
if (!right) {
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
const rightValue = filterOperandValue(right, filter.operand);
|
|
1983
|
+
return predicate(compareFilterValues(comparisonValue, rightValue));
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
exports.RdfLocalQueryEngine = RdfLocalQueryEngine;
|
|
1987
|
+
function variable(variableName) {
|
|
1988
|
+
return { variable: variableName };
|
|
1989
|
+
}
|
|
1990
|
+
function isVariable(value) {
|
|
1991
|
+
return Boolean(value && typeof value === 'object' && 'variable' in value);
|
|
1992
|
+
}
|
|
1993
|
+
function variablesInPattern(pattern) {
|
|
1994
|
+
return TERM_KEYS
|
|
1995
|
+
.map((key) => pattern[key])
|
|
1996
|
+
.filter(isVariable)
|
|
1997
|
+
.map((value) => value.variable);
|
|
1998
|
+
}
|
|
1999
|
+
function patternHasRepeatedVariables(pattern) {
|
|
2000
|
+
const seen = new Set();
|
|
2001
|
+
for (const variableName of variablesInPattern(pattern)) {
|
|
2002
|
+
if (seen.has(variableName)) {
|
|
2003
|
+
return true;
|
|
2004
|
+
}
|
|
2005
|
+
seen.add(variableName);
|
|
2006
|
+
}
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
function variablesSharedAcrossPatterns(patterns) {
|
|
2010
|
+
const counts = new Map();
|
|
2011
|
+
for (const pattern of patterns) {
|
|
2012
|
+
for (const variableName of new Set(variablesInPattern(pattern))) {
|
|
2013
|
+
counts.set(variableName, (counts.get(variableName) ?? 0) + 1);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
return new Set([...counts.entries()]
|
|
2017
|
+
.filter(([, count]) => count > 1)
|
|
2018
|
+
.map(([variableName]) => variableName));
|
|
2019
|
+
}
|
|
2020
|
+
function hasSharedVariable(left, right) {
|
|
2021
|
+
for (const variableName of left) {
|
|
2022
|
+
if (right.has(variableName)) {
|
|
2023
|
+
return true;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
2028
|
+
function filterVariables(filter) {
|
|
2029
|
+
return filter.variable2
|
|
2030
|
+
? [filter.variable, filter.variable2]
|
|
2031
|
+
: [filter.variable];
|
|
2032
|
+
}
|
|
2033
|
+
function buildRequiredSources(patterns, query) {
|
|
2034
|
+
const patternSources = patterns.map((pattern, originalIndex) => ({
|
|
2035
|
+
kind: 'pattern',
|
|
2036
|
+
pattern,
|
|
2037
|
+
originalIndex,
|
|
2038
|
+
}));
|
|
2039
|
+
const remainingValues = [];
|
|
2040
|
+
for (const [originalIndex, source] of (query.values ?? []).entries()) {
|
|
2041
|
+
const patternSource = patternSources.find((candidate) => (!candidate.tupleValues && canAttachTupleValuesToPattern(source, candidate.pattern)));
|
|
2042
|
+
if (patternSource) {
|
|
2043
|
+
patternSource.tupleValues = source;
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
remainingValues.push({
|
|
2047
|
+
kind: 'values',
|
|
2048
|
+
source,
|
|
2049
|
+
originalIndex,
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
return [
|
|
2053
|
+
...remainingValues,
|
|
2054
|
+
...patternSources,
|
|
2055
|
+
...(query.textSearch ?? []).map((pattern, originalIndex) => ({
|
|
2056
|
+
kind: 'text',
|
|
2057
|
+
pattern,
|
|
2058
|
+
originalIndex,
|
|
2059
|
+
})),
|
|
2060
|
+
...(query.vectorSearch ?? []).map((pattern, originalIndex) => ({
|
|
2061
|
+
kind: 'vector',
|
|
2062
|
+
pattern,
|
|
2063
|
+
originalIndex,
|
|
2064
|
+
})),
|
|
2065
|
+
];
|
|
2066
|
+
}
|
|
2067
|
+
function variablesInRequiredSource(source) {
|
|
2068
|
+
if (source.kind === 'pattern') {
|
|
2069
|
+
return uniqueStrings([
|
|
2070
|
+
...variablesInPattern(source.pattern),
|
|
2071
|
+
...(source.tupleValues?.variables ?? []),
|
|
2072
|
+
]);
|
|
2073
|
+
}
|
|
2074
|
+
if (source.kind === 'values') {
|
|
2075
|
+
return source.source.variables;
|
|
2076
|
+
}
|
|
2077
|
+
return [
|
|
2078
|
+
source.pattern.source,
|
|
2079
|
+
source.pattern.chunk,
|
|
2080
|
+
source.pattern.content,
|
|
2081
|
+
source.pattern.heading,
|
|
2082
|
+
source.pattern.score,
|
|
2083
|
+
source.kind === 'vector' ? source.pattern.distance : undefined,
|
|
2084
|
+
source.pattern.workspace,
|
|
2085
|
+
source.pattern.localPath,
|
|
2086
|
+
source.pattern.contentType,
|
|
2087
|
+
source.pattern.ordinal,
|
|
2088
|
+
source.pattern.level,
|
|
2089
|
+
source.pattern.startOffset,
|
|
2090
|
+
source.pattern.endOffset,
|
|
2091
|
+
source.kind === 'vector' ? source.pattern.model : undefined,
|
|
2092
|
+
].filter((value) => Boolean(value));
|
|
2093
|
+
}
|
|
2094
|
+
function uniqueStrings(values) {
|
|
2095
|
+
return [...new Set(values)];
|
|
2096
|
+
}
|
|
2097
|
+
function filtersWithoutIndexes(filters, indexes) {
|
|
2098
|
+
if (indexes.length === 0) {
|
|
2099
|
+
return filters;
|
|
2100
|
+
}
|
|
2101
|
+
const pushedDown = new Set(indexes);
|
|
2102
|
+
return filters.filter((_filter, index) => !pushedDown.has(index));
|
|
2103
|
+
}
|
|
2104
|
+
function joinValuesSource(input, source) {
|
|
2105
|
+
const output = [];
|
|
2106
|
+
for (const binding of input) {
|
|
2107
|
+
for (const row of source.rows) {
|
|
2108
|
+
const next = mergeTupleValuesBinding(binding, source.variables, row);
|
|
2109
|
+
if (next) {
|
|
2110
|
+
output.push(next);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return output;
|
|
2115
|
+
}
|
|
2116
|
+
function mergeTupleValuesBinding(binding, variables, row) {
|
|
2117
|
+
const next = { ...binding };
|
|
2118
|
+
for (const variableName of variables) {
|
|
2119
|
+
const value = row[variableName];
|
|
2120
|
+
if (!value) {
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
const existing = next[variableName];
|
|
2124
|
+
if (existing && !sameTerm(existing, value)) {
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
next[variableName] = value;
|
|
2128
|
+
}
|
|
2129
|
+
return next;
|
|
2130
|
+
}
|
|
2131
|
+
function canAttachTupleValuesToPattern(source, pattern) {
|
|
2132
|
+
if (source.variables.length < 2 || source.rows.length === 0) {
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
const slots = new Set();
|
|
2136
|
+
for (const variableName of source.variables) {
|
|
2137
|
+
if (source.rows.some((row) => !row[variableName])) {
|
|
2138
|
+
return false;
|
|
2139
|
+
}
|
|
2140
|
+
const variableSlots = TERM_KEYS.filter((key) => {
|
|
2141
|
+
const value = pattern[key];
|
|
2142
|
+
return isVariable(value) && value.variable === variableName;
|
|
2143
|
+
});
|
|
2144
|
+
if (variableSlots.length !== 1) {
|
|
2145
|
+
return false;
|
|
2146
|
+
}
|
|
2147
|
+
slots.add(variableSlots[0]);
|
|
2148
|
+
}
|
|
2149
|
+
return slots.size === source.variables.length
|
|
2150
|
+
&& source.rows.every((row) => source.variables.every((variableName) => Boolean(row[variableName])));
|
|
2151
|
+
}
|
|
2152
|
+
function tupleConstraintSourceForPattern(source, pattern) {
|
|
2153
|
+
const columns = source.variables.map((variableName) => {
|
|
2154
|
+
const key = termKeyForVariable(pattern, variableName);
|
|
2155
|
+
if (!key) {
|
|
2156
|
+
throw new Error(`Tuple VALUES variable is not bound by pattern: ${variableName}`);
|
|
2157
|
+
}
|
|
2158
|
+
return key;
|
|
2159
|
+
});
|
|
2160
|
+
return {
|
|
2161
|
+
columns,
|
|
2162
|
+
rows: source.rows.map((row) => {
|
|
2163
|
+
const constraint = {};
|
|
2164
|
+
source.variables.forEach((variableName, index) => {
|
|
2165
|
+
constraint[columns[index]] = row[variableName];
|
|
2166
|
+
});
|
|
2167
|
+
return constraint;
|
|
2168
|
+
}),
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
function normalizeOptionalGroup(group) {
|
|
2172
|
+
return Array.isArray(group) ? { patterns: group } : group;
|
|
2173
|
+
}
|
|
2174
|
+
function compiledPatternKey(pattern) {
|
|
2175
|
+
return TERM_KEYS
|
|
2176
|
+
.map((key) => `${key}:${termMatchKey(pattern[key])}`)
|
|
2177
|
+
.join('|');
|
|
2178
|
+
}
|
|
2179
|
+
function termMatchKey(match) {
|
|
2180
|
+
if (!match) {
|
|
2181
|
+
return '*';
|
|
2182
|
+
}
|
|
2183
|
+
if ((0, types_1.isTerm)(match)) {
|
|
2184
|
+
return (0, n3_1.termToId)(match);
|
|
2185
|
+
}
|
|
2186
|
+
return JSON.stringify(match, (_key, value) => (0, types_1.isTerm)(value) ? (0, n3_1.termToId)(value) : value);
|
|
2187
|
+
}
|
|
2188
|
+
function termKeyForVariable(pattern, variableName) {
|
|
2189
|
+
return TERM_KEYS.find((key) => {
|
|
2190
|
+
const value = pattern[key];
|
|
2191
|
+
return isVariable(value) && value.variable === variableName;
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
function isConsistentPattern(pattern) {
|
|
2195
|
+
for (const key of TERM_KEYS) {
|
|
2196
|
+
const value = pattern[key];
|
|
2197
|
+
if (!value || (0, types_1.isTerm)(value)) {
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
return true;
|
|
2202
|
+
}
|
|
2203
|
+
function sameTerm(left, right) {
|
|
2204
|
+
return (0, n3_1.termToId)(left) === (0, n3_1.termToId)(right);
|
|
2205
|
+
}
|
|
2206
|
+
function filterOperandValue(value, operand) {
|
|
2207
|
+
switch (operand) {
|
|
2208
|
+
case 'stringLength':
|
|
2209
|
+
return value.value.length;
|
|
2210
|
+
case 'stringValue':
|
|
2211
|
+
return value.value;
|
|
2212
|
+
case 'lowerStringValue':
|
|
2213
|
+
return value.value.toLowerCase();
|
|
2214
|
+
case 'upperStringValue':
|
|
2215
|
+
return value.value.toUpperCase();
|
|
2216
|
+
default:
|
|
2217
|
+
return value;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function filterStringValue(value, comparisonValue) {
|
|
2221
|
+
return typeof comparisonValue === 'string' ? comparisonValue : value.value;
|
|
2222
|
+
}
|
|
2223
|
+
function sameTermOrLexical(left, right) {
|
|
2224
|
+
if (typeof left === 'number') {
|
|
2225
|
+
if (isNumericFilterValue(right)) {
|
|
2226
|
+
return left === (0, RdfTermSemantics_1.rdfNumericValue)((0, types_1.isTerm)(right) ? right.value : String(right));
|
|
2227
|
+
}
|
|
2228
|
+
return String(left) === String(right);
|
|
2229
|
+
}
|
|
2230
|
+
if (typeof left === 'string') {
|
|
2231
|
+
return left === ((0, types_1.isTerm)(right) ? right.value : String(right));
|
|
2232
|
+
}
|
|
2233
|
+
return (0, types_1.isTerm)(right) ? sameTerm(left, right) : left.value === String(right);
|
|
2234
|
+
}
|
|
2235
|
+
function compareTermsForFilter(left, right) {
|
|
2236
|
+
if (right === undefined) {
|
|
2237
|
+
return 1;
|
|
2238
|
+
}
|
|
2239
|
+
return compareFilterValues(left, (0, types_1.isTerm)(right) ? right : right);
|
|
2240
|
+
}
|
|
2241
|
+
function compareFilterValues(left, right) {
|
|
2242
|
+
if (typeof left === 'number') {
|
|
2243
|
+
if (isNumericFilterValue(right)) {
|
|
2244
|
+
return left - (0, RdfTermSemantics_1.rdfNumericValue)((0, types_1.isTerm)(right) ? right.value : String(right));
|
|
2245
|
+
}
|
|
2246
|
+
return String(left).localeCompare(String(right));
|
|
2247
|
+
}
|
|
2248
|
+
if (typeof left === 'string') {
|
|
2249
|
+
const rightValue = (0, types_1.isTerm)(right) ? right.value : String(right);
|
|
2250
|
+
return left.localeCompare(rightValue);
|
|
2251
|
+
}
|
|
2252
|
+
if (isNumericTerm(left) && isNumericFilterValue(right)) {
|
|
2253
|
+
return (0, RdfTermSemantics_1.rdfNumericValue)(left.value) - (0, RdfTermSemantics_1.rdfNumericValue)((0, types_1.isTerm)(right) ? right.value : String(right));
|
|
2254
|
+
}
|
|
2255
|
+
const rightValue = (0, types_1.isTerm)(right) ? right.value : String(right);
|
|
2256
|
+
return left.value.localeCompare(rightValue);
|
|
2257
|
+
}
|
|
2258
|
+
function matchesTermType(term, expected) {
|
|
2259
|
+
switch (expected) {
|
|
2260
|
+
case 'iri':
|
|
2261
|
+
return term.termType === 'NamedNode';
|
|
2262
|
+
case 'blank':
|
|
2263
|
+
return term.termType === 'BlankNode';
|
|
2264
|
+
case 'literal':
|
|
2265
|
+
return term.termType === 'Literal';
|
|
2266
|
+
case 'numeric':
|
|
2267
|
+
return isNumericTerm(term);
|
|
2268
|
+
default:
|
|
2269
|
+
return false;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function langMatches(languageTag, languageRange) {
|
|
2273
|
+
if (!languageTag) {
|
|
2274
|
+
return false;
|
|
2275
|
+
}
|
|
2276
|
+
if (languageRange === '*') {
|
|
2277
|
+
return true;
|
|
2278
|
+
}
|
|
2279
|
+
const normalizedTag = languageTag.toLowerCase();
|
|
2280
|
+
const normalizedRange = languageRange.toLowerCase();
|
|
2281
|
+
return normalizedTag === normalizedRange
|
|
2282
|
+
|| normalizedTag.startsWith(`${normalizedRange}-`);
|
|
2283
|
+
}
|
|
2284
|
+
function isNumericFilterValue(value) {
|
|
2285
|
+
return (0, types_1.isTerm)(value)
|
|
2286
|
+
? isNumericTerm(value)
|
|
2287
|
+
: (typeof value === 'number' || (typeof value === 'string' && (0, RdfTermSemantics_1.isFiniteNumericLexical)(value)));
|
|
2288
|
+
}
|
|
2289
|
+
function isNumericTerm(term) {
|
|
2290
|
+
return (0, RdfTermSemantics_1.isRdfNumericTerm)(term);
|
|
2291
|
+
}
|
|
2292
|
+
function isPushdownFilter(filter) {
|
|
2293
|
+
if (isNumericGuardFilter(filter)) {
|
|
2294
|
+
return true;
|
|
2295
|
+
}
|
|
2296
|
+
if (filter.operand === 'stringLength') {
|
|
2297
|
+
return false;
|
|
2298
|
+
}
|
|
2299
|
+
if (filter.operand === 'lowerStringValue' || filter.operand === 'upperStringValue') {
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
if (filter.operand === 'stringValue') {
|
|
2303
|
+
return filter.operator === '$startsWith'
|
|
2304
|
+
|| filter.operator === '$contains'
|
|
2305
|
+
|| filter.operator === '$endsWith'
|
|
2306
|
+
|| filter.operator === '$regex';
|
|
2307
|
+
}
|
|
2308
|
+
return filter.operator === '$eq'
|
|
2309
|
+
|| filter.operator === '$ne'
|
|
2310
|
+
|| filter.operator === '$gt'
|
|
2311
|
+
|| filter.operator === '$gte'
|
|
2312
|
+
|| filter.operator === '$lt'
|
|
2313
|
+
|| filter.operator === '$lte'
|
|
2314
|
+
|| filter.operator === '$in'
|
|
2315
|
+
|| filter.operator === '$notIn'
|
|
2316
|
+
|| filter.operator === '$startsWith'
|
|
2317
|
+
|| filter.operator === '$contains'
|
|
2318
|
+
|| filter.operator === '$endsWith'
|
|
2319
|
+
|| filter.operator === '$regex'
|
|
2320
|
+
|| filter.operator === '$sameTerm'
|
|
2321
|
+
|| filter.operator === '$termType'
|
|
2322
|
+
|| filter.operator === '$lang'
|
|
2323
|
+
|| filter.operator === '$notLang'
|
|
2324
|
+
|| filter.operator === '$langMatches'
|
|
2325
|
+
|| filter.operator === '$datatype'
|
|
2326
|
+
|| filter.operator === '$notDatatype';
|
|
2327
|
+
}
|
|
2328
|
+
function isNumericGuardFilter(filter) {
|
|
2329
|
+
return filter.operator === '$termType'
|
|
2330
|
+
&& filter.value === 'numeric'
|
|
2331
|
+
&& !filter.variable2
|
|
2332
|
+
&& !filter.operand;
|
|
2333
|
+
}
|
|
2334
|
+
function isGroupAggregateHavingOperator(operator) {
|
|
2335
|
+
return operator === '$eq'
|
|
2336
|
+
|| operator === '$ne'
|
|
2337
|
+
|| operator === '$gt'
|
|
2338
|
+
|| operator === '$gte'
|
|
2339
|
+
|| operator === '$lt'
|
|
2340
|
+
|| operator === '$lte';
|
|
2341
|
+
}
|
|
2342
|
+
function filterValueToNumber(value) {
|
|
2343
|
+
if (typeof value === 'number') {
|
|
2344
|
+
return Number.isFinite(value) ? value : undefined;
|
|
2345
|
+
}
|
|
2346
|
+
if (typeof value === 'string') {
|
|
2347
|
+
return (0, RdfTermSemantics_1.isFiniteNumericLexical)(value) ? (0, RdfTermSemantics_1.rdfNumericValue)(value) : undefined;
|
|
2348
|
+
}
|
|
2349
|
+
if ((0, types_1.isTerm)(value) && isNumericTerm(value)) {
|
|
2350
|
+
return (0, RdfTermSemantics_1.rdfNumericValue)(value.value);
|
|
2351
|
+
}
|
|
2352
|
+
return undefined;
|
|
2353
|
+
}
|
|
2354
|
+
function projectBinding(binding, select) {
|
|
2355
|
+
const projected = {};
|
|
2356
|
+
for (const variableName of select) {
|
|
2357
|
+
const value = binding[variableName];
|
|
2358
|
+
if (value) {
|
|
2359
|
+
projected[variableName] = value;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return projected;
|
|
2363
|
+
}
|
|
2364
|
+
function compareBindings(left, right, orderBy) {
|
|
2365
|
+
for (const order of orderBy) {
|
|
2366
|
+
const leftValue = left[order.variable] ? (0, n3_1.termToId)(left[order.variable]) : '';
|
|
2367
|
+
const rightValue = right[order.variable] ? (0, n3_1.termToId)(right[order.variable]) : '';
|
|
2368
|
+
const comparison = leftValue.localeCompare(rightValue);
|
|
2369
|
+
if (comparison !== 0) {
|
|
2370
|
+
return order.direction === 'desc' ? -comparison : comparison;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return 0;
|
|
2374
|
+
}
|
|
2375
|
+
function bindingKey(binding) {
|
|
2376
|
+
return Object.keys(binding)
|
|
2377
|
+
.sort()
|
|
2378
|
+
.map((key) => `${key}=${(0, n3_1.termToId)(binding[key])}`)
|
|
2379
|
+
.join('\u001f');
|
|
2380
|
+
}
|
|
2381
|
+
function distinctBindings(bindings) {
|
|
2382
|
+
const seen = new Set();
|
|
2383
|
+
const unique = [];
|
|
2384
|
+
for (const binding of bindings) {
|
|
2385
|
+
const key = bindingKey(binding);
|
|
2386
|
+
if (seen.has(key)) {
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
seen.add(key);
|
|
2390
|
+
unique.push(binding);
|
|
2391
|
+
}
|
|
2392
|
+
return unique;
|
|
2393
|
+
}
|
|
2394
|
+
function integerLiteral(value) {
|
|
2395
|
+
return n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode(XSD_INTEGER));
|
|
2396
|
+
}
|
|
2397
|
+
function countLiteral(count) {
|
|
2398
|
+
return integerLiteral(count);
|
|
2399
|
+
}
|
|
2400
|
+
function finiteBindNumber(term) {
|
|
2401
|
+
if (term.termType !== 'Literal') {
|
|
2402
|
+
return undefined;
|
|
2403
|
+
}
|
|
2404
|
+
const value = Number(term.value);
|
|
2405
|
+
return Number.isFinite(value) ? value : undefined;
|
|
2406
|
+
}
|
|
2407
|
+
function decimalLiteral(value) {
|
|
2408
|
+
return n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode(XSD_DECIMAL));
|
|
2409
|
+
}
|
|
2410
|
+
function describePattern(pattern) {
|
|
2411
|
+
return TERM_KEYS
|
|
2412
|
+
.filter((key) => pattern[key])
|
|
2413
|
+
.map((key) => `${key}:${describePatternValue(pattern[key])}`)
|
|
2414
|
+
.join(',');
|
|
2415
|
+
}
|
|
2416
|
+
function describePatternSource(source) {
|
|
2417
|
+
return TERM_KEYS
|
|
2418
|
+
.map((key) => {
|
|
2419
|
+
const variableName = source.variables[key];
|
|
2420
|
+
if (variableName) {
|
|
2421
|
+
return `${key}:?${variableName}`;
|
|
2422
|
+
}
|
|
2423
|
+
const value = source.pattern[key];
|
|
2424
|
+
return value ? `${key}:${termMatchKey(value)}` : undefined;
|
|
2425
|
+
})
|
|
2426
|
+
.filter(Boolean)
|
|
2427
|
+
.join(',');
|
|
2428
|
+
}
|
|
2429
|
+
function describePatternValue(value) {
|
|
2430
|
+
if (!value)
|
|
2431
|
+
return '*';
|
|
2432
|
+
if (isVariable(value))
|
|
2433
|
+
return `?${value.variable}`;
|
|
2434
|
+
if ((0, types_1.isTerm)(value))
|
|
2435
|
+
return (0, n3_1.termToId)(value);
|
|
2436
|
+
return 'op';
|
|
2437
|
+
}
|
|
2438
|
+
function describeFilter(filter) {
|
|
2439
|
+
return `?${filter.variable}${filter.operand ? `:${filter.operand}` : ''}${filter.operator}`;
|
|
2440
|
+
}
|
|
2441
|
+
function describeTextSearch(pattern) {
|
|
2442
|
+
const bindings = [
|
|
2443
|
+
pattern.source ? `source:?${pattern.source}` : undefined,
|
|
2444
|
+
pattern.chunk ? `chunk:?${pattern.chunk}` : undefined,
|
|
2445
|
+
pattern.content ? `content:?${pattern.content}` : undefined,
|
|
2446
|
+
pattern.heading ? `heading:?${pattern.heading}` : undefined,
|
|
2447
|
+
pattern.score ? `score:?${pattern.score}` : undefined,
|
|
2448
|
+
].filter(Boolean).join(',');
|
|
2449
|
+
const scope = pattern.scope?.workspace
|
|
2450
|
+
? `workspace:${pattern.scope.workspace}`
|
|
2451
|
+
: pattern.scope?.sourcePrefix
|
|
2452
|
+
? `prefix:${pattern.scope.sourcePrefix}`
|
|
2453
|
+
: '*';
|
|
2454
|
+
const window = describeSearchWindow(pattern.limit, pattern.offset);
|
|
2455
|
+
const order = describeSearchOrder(pattern.orderBy);
|
|
2456
|
+
return `${JSON.stringify(pattern.query)}@${scope}${bindings ? ` ${bindings}` : ''}${window}${order}`;
|
|
2457
|
+
}
|
|
2458
|
+
function describeVectorSearch(pattern) {
|
|
2459
|
+
const bindings = [
|
|
2460
|
+
pattern.source ? `source:?${pattern.source}` : undefined,
|
|
2461
|
+
pattern.chunk ? `chunk:?${pattern.chunk}` : undefined,
|
|
2462
|
+
pattern.content ? `content:?${pattern.content}` : undefined,
|
|
2463
|
+
pattern.heading ? `heading:?${pattern.heading}` : undefined,
|
|
2464
|
+
pattern.score ? `score:?${pattern.score}` : undefined,
|
|
2465
|
+
pattern.distance ? `distance:?${pattern.distance}` : undefined,
|
|
2466
|
+
].filter(Boolean).join(',');
|
|
2467
|
+
const scope = pattern.scope?.workspace
|
|
2468
|
+
? `workspace:${pattern.scope.workspace}`
|
|
2469
|
+
: pattern.scope?.sourcePrefix
|
|
2470
|
+
? `prefix:${pattern.scope.sourcePrefix}`
|
|
2471
|
+
: '*';
|
|
2472
|
+
const metric = pattern.metric ?? 'cosine';
|
|
2473
|
+
const window = describeSearchWindow(pattern.limit, pattern.offset);
|
|
2474
|
+
const order = describeSearchOrder(pattern.orderBy);
|
|
2475
|
+
return `${metric}:${pattern.embedding.length}d@${scope}${bindings ? ` ${bindings}` : ''}${window}${order}`;
|
|
2476
|
+
}
|
|
2477
|
+
function describeSearchWindow(limit, offset) {
|
|
2478
|
+
const parts = [
|
|
2479
|
+
limit !== undefined ? `limit:${Math.max(0, limit)}` : undefined,
|
|
2480
|
+
offset !== undefined ? `offset:${Math.max(0, offset)}` : undefined,
|
|
2481
|
+
].filter(Boolean);
|
|
2482
|
+
return parts.length > 0 ? ` ${parts.join(',')}` : '';
|
|
2483
|
+
}
|
|
2484
|
+
function hasSearchWindow(source) {
|
|
2485
|
+
return source.pattern.limit !== undefined || source.pattern.offset !== undefined;
|
|
2486
|
+
}
|
|
2487
|
+
function describeSearchOrder(orderBy) {
|
|
2488
|
+
if (!orderBy?.length) {
|
|
2489
|
+
return '';
|
|
2490
|
+
}
|
|
2491
|
+
return ` order:${orderBy.map((entry) => `${entry.field}:${entry.direction ?? 'asc'}`).join(',')}`;
|
|
2492
|
+
}
|
|
2493
|
+
function describeBind(bind) {
|
|
2494
|
+
return `?${bind.variable}:=${describeBindExpression(bind.expression)}`;
|
|
2495
|
+
}
|
|
2496
|
+
function describeBindExpression(expression) {
|
|
2497
|
+
switch (expression.type) {
|
|
2498
|
+
case 'term':
|
|
2499
|
+
if (expression.term.termType === 'Literal' && (0, RdfTermSemantics_1.isFiniteNumericLexical)(expression.term.value)) {
|
|
2500
|
+
return expression.term.value;
|
|
2501
|
+
}
|
|
2502
|
+
return (0, n3_1.termToId)(expression.term);
|
|
2503
|
+
case 'variable':
|
|
2504
|
+
return `?${expression.variable}`;
|
|
2505
|
+
case 'stringValue':
|
|
2506
|
+
return `STR(?${expression.variable})`;
|
|
2507
|
+
case 'stringLength':
|
|
2508
|
+
return `STRLEN(?${expression.variable})`;
|
|
2509
|
+
case 'lowerCase':
|
|
2510
|
+
return `LCASE(${describeBindExpression(expression.expression)})`;
|
|
2511
|
+
case 'upperCase':
|
|
2512
|
+
return `UCASE(${describeBindExpression(expression.expression)})`;
|
|
2513
|
+
case 'substring':
|
|
2514
|
+
return `SUBSTR(${[
|
|
2515
|
+
describeBindExpression(expression.expression),
|
|
2516
|
+
describeBindExpression(expression.start),
|
|
2517
|
+
expression.length === undefined ? undefined : describeBindExpression(expression.length),
|
|
2518
|
+
].filter(Boolean).join(',')})`;
|
|
2519
|
+
case 'concat':
|
|
2520
|
+
return `CONCAT(${expression.expressions.map(describeBindExpression).join(',')})`;
|
|
2521
|
+
case 'iri':
|
|
2522
|
+
return `IRI(${describeBindExpression(expression.expression)})`;
|
|
2523
|
+
default: {
|
|
2524
|
+
const exhaustive = expression;
|
|
2525
|
+
return JSON.stringify(exhaustive);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
function describeScanOrder(options) {
|
|
2530
|
+
const order = options.order ?? [];
|
|
2531
|
+
const directions = options.orderDirections ?? order.map(() => (options.reverse ? 'desc' : 'asc'));
|
|
2532
|
+
const firstDirection = directions[0] ?? 'asc';
|
|
2533
|
+
const sameDirection = directions.every((direction) => direction === firstDirection);
|
|
2534
|
+
if (sameDirection) {
|
|
2535
|
+
return `${firstDirection}:${order.join(',')}`;
|
|
2536
|
+
}
|
|
2537
|
+
return order.map((entry, index) => `${directions[index] ?? 'asc'}:${entry}`).join(',');
|
|
2538
|
+
}
|
|
2539
|
+
function describeQueryOrder(orderBy) {
|
|
2540
|
+
return orderBy.map((entry) => `${entry.direction ?? 'asc'}:${entry.variable}`).join(',');
|
|
2541
|
+
}
|
|
2542
|
+
function storagePlanMarkers(queryPlan) {
|
|
2543
|
+
return (queryPlan ?? []).filter((entry) => (entry.startsWith('TextSearch(')
|
|
2544
|
+
|| entry.startsWith('Rdf3x')
|
|
2545
|
+
|| entry === 'GraphMembershipFilter'
|
|
2546
|
+
|| entry === 'GraphPrefixMembershipFilter'
|
|
2547
|
+
|| entry.startsWith('LexicalRange(')
|
|
2548
|
+
|| entry.startsWith('NumericRange(')
|
|
2549
|
+
|| entry.startsWith('PrefixRange(')
|
|
2550
|
+
|| entry.startsWith('TermIn(')
|
|
2551
|
+
|| entry.startsWith('TermNotIn(')
|
|
2552
|
+
|| entry.startsWith('TermType(')
|
|
2553
|
+
|| entry.startsWith('Language(')
|
|
2554
|
+
|| entry.startsWith('Datatype(')
|
|
2555
|
+
|| entry.startsWith('TupleValuesJoin(')
|
|
2556
|
+
|| entry.startsWith('JoinBGP(')
|
|
2557
|
+
|| entry.startsWith('JoinOrder(')
|
|
2558
|
+
|| entry.startsWith('JoinDistinct(')
|
|
2559
|
+
|| entry.startsWith('JoinLimit')
|
|
2560
|
+
|| entry.startsWith('JoinGroupCountHaving(')
|
|
2561
|
+
|| entry.startsWith('JoinGroupAggregateHaving(')
|
|
2562
|
+
|| entry.startsWith('JoinGroupAggregateNumeric(')));
|
|
2563
|
+
}
|
|
2564
|
+
function isRdf3xCompatiblePattern(pattern) {
|
|
2565
|
+
return TERM_KEYS.every((key) => {
|
|
2566
|
+
const value = pattern[key];
|
|
2567
|
+
if (!value || (0, types_1.isTerm)(value)) {
|
|
2568
|
+
return true;
|
|
2569
|
+
}
|
|
2570
|
+
if (key === 'graph' && isGraphPrefixPattern(value)) {
|
|
2571
|
+
return true;
|
|
2572
|
+
}
|
|
2573
|
+
if (key === 'object' && isRdf3xNumericRangePattern(value)) {
|
|
2574
|
+
return true;
|
|
2575
|
+
}
|
|
2576
|
+
return false;
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
function isGraphPrefixPattern(value) {
|
|
2580
|
+
return value !== null
|
|
2581
|
+
&& typeof value === 'object'
|
|
2582
|
+
&& Object.keys(value).length === 1
|
|
2583
|
+
&& '$startsWith' in value
|
|
2584
|
+
&& typeof value.$startsWith === 'string';
|
|
2585
|
+
}
|
|
2586
|
+
function isRdf3xNumericRangePattern(value) {
|
|
2587
|
+
if (value === null || typeof value !== 'object' || 'termType' in value) {
|
|
2588
|
+
return false;
|
|
2589
|
+
}
|
|
2590
|
+
const operators = ['$gt', '$gte', '$lt', '$lte'];
|
|
2591
|
+
if (Object.keys(value).some((key) => !operators.includes(key))) {
|
|
2592
|
+
return false;
|
|
2593
|
+
}
|
|
2594
|
+
let hasRange = false;
|
|
2595
|
+
for (const operator of operators) {
|
|
2596
|
+
const rangeValue = value[operator];
|
|
2597
|
+
if (rangeValue === undefined) {
|
|
2598
|
+
continue;
|
|
2599
|
+
}
|
|
2600
|
+
hasRange = true;
|
|
2601
|
+
if (rdf3xNumericRangeValue(rangeValue) === undefined) {
|
|
2602
|
+
return false;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
return hasRange;
|
|
2606
|
+
}
|
|
2607
|
+
function rdf3xNumericRangeValue(value) {
|
|
2608
|
+
if (typeof value === 'number') {
|
|
2609
|
+
return Number.isFinite(value) ? value : undefined;
|
|
2610
|
+
}
|
|
2611
|
+
if (typeof value === 'string') {
|
|
2612
|
+
const parsed = Number(value);
|
|
2613
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2614
|
+
}
|
|
2615
|
+
if ((0, types_1.isTerm)(value)) {
|
|
2616
|
+
return (0, RdfTermSemantics_1.isRdfNumericTerm)(value) ? (0, RdfTermSemantics_1.rdfNumericValue)(value.value) : undefined;
|
|
2617
|
+
}
|
|
2618
|
+
return undefined;
|
|
2619
|
+
}
|
|
2620
|
+
function bindTextSearchResult(binding, pattern, result) {
|
|
2621
|
+
const next = { ...binding };
|
|
2622
|
+
const chunkResource = `${result.source}#chunk-${encodeURIComponent(result.chunkKey)}`;
|
|
2623
|
+
const candidates = [
|
|
2624
|
+
[pattern.source, n3_1.DataFactory.namedNode(result.source)],
|
|
2625
|
+
[pattern.chunk, n3_1.DataFactory.namedNode(chunkResource)],
|
|
2626
|
+
[pattern.content, n3_1.DataFactory.literal(result.content)],
|
|
2627
|
+
[pattern.heading, result.heading ? n3_1.DataFactory.literal(result.heading) : undefined],
|
|
2628
|
+
[pattern.score, decimalLiteral(result.score)],
|
|
2629
|
+
[pattern.workspace, n3_1.DataFactory.namedNode(result.workspace)],
|
|
2630
|
+
[pattern.localPath, result.localPath ? n3_1.DataFactory.literal(result.localPath) : undefined],
|
|
2631
|
+
[pattern.contentType, result.contentType ? n3_1.DataFactory.literal(result.contentType) : undefined],
|
|
2632
|
+
[pattern.ordinal, integerLiteral(result.ordinal)],
|
|
2633
|
+
[pattern.level, integerLiteral(result.level)],
|
|
2634
|
+
[pattern.startOffset, integerLiteral(result.startOffset)],
|
|
2635
|
+
[pattern.endOffset, integerLiteral(result.endOffset)],
|
|
2636
|
+
];
|
|
2637
|
+
for (const [variableName, term] of candidates) {
|
|
2638
|
+
if (!variableName || !term) {
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
const existing = next[variableName];
|
|
2642
|
+
if (existing && !sameTerm(existing, term)) {
|
|
2643
|
+
return null;
|
|
2644
|
+
}
|
|
2645
|
+
next[variableName] = term;
|
|
2646
|
+
}
|
|
2647
|
+
return next;
|
|
2648
|
+
}
|
|
2649
|
+
function bindVectorSearchResult(binding, pattern, result) {
|
|
2650
|
+
const next = { ...binding };
|
|
2651
|
+
const chunkResource = `${result.source}#chunk-${encodeURIComponent(result.chunkKey)}`;
|
|
2652
|
+
const candidates = [
|
|
2653
|
+
[pattern.source, n3_1.DataFactory.namedNode(result.source)],
|
|
2654
|
+
[pattern.chunk, n3_1.DataFactory.namedNode(chunkResource)],
|
|
2655
|
+
[pattern.content, n3_1.DataFactory.literal(result.content)],
|
|
2656
|
+
[pattern.heading, result.heading ? n3_1.DataFactory.literal(result.heading) : undefined],
|
|
2657
|
+
[pattern.score, decimalLiteral(result.score)],
|
|
2658
|
+
[pattern.distance, decimalLiteral(result.distance)],
|
|
2659
|
+
[pattern.workspace, n3_1.DataFactory.namedNode(result.workspace)],
|
|
2660
|
+
[pattern.localPath, result.localPath ? n3_1.DataFactory.literal(result.localPath) : undefined],
|
|
2661
|
+
[pattern.contentType, result.contentType ? n3_1.DataFactory.literal(result.contentType) : undefined],
|
|
2662
|
+
[pattern.ordinal, integerLiteral(result.ordinal)],
|
|
2663
|
+
[pattern.level, integerLiteral(result.level)],
|
|
2664
|
+
[pattern.startOffset, integerLiteral(result.startOffset)],
|
|
2665
|
+
[pattern.endOffset, integerLiteral(result.endOffset)],
|
|
2666
|
+
[pattern.model, result.model ? n3_1.DataFactory.literal(result.model) : undefined],
|
|
2667
|
+
];
|
|
2668
|
+
for (const [variableName, term] of candidates) {
|
|
2669
|
+
if (!variableName || !term) {
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
const existing = next[variableName];
|
|
2673
|
+
if (existing && !sameTerm(existing, term)) {
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
next[variableName] = term;
|
|
2677
|
+
}
|
|
2678
|
+
return next;
|
|
2679
|
+
}
|
|
2680
|
+
function queryAggregates(query) {
|
|
2681
|
+
return query.aggregates ?? (query.aggregate ? [query.aggregate] : []);
|
|
2682
|
+
}
|
|
2683
|
+
function aggregatePlan(aggregates, grouped) {
|
|
2684
|
+
if (aggregates.some((aggregate) => aggregate.type !== 'count')) {
|
|
2685
|
+
const prefix = grouped ? 'group-' : '';
|
|
2686
|
+
const suffix = aggregates.length > 1 ? '-multi' : '';
|
|
2687
|
+
return `Aggregate(${prefix}basic${suffix})`;
|
|
2688
|
+
}
|
|
2689
|
+
if (aggregates.length === 1) {
|
|
2690
|
+
const aggregate = aggregates[0];
|
|
2691
|
+
if (grouped) {
|
|
2692
|
+
return aggregate.distinct ? 'Aggregate(group-count-distinct)' : 'Aggregate(group-count)';
|
|
2693
|
+
}
|
|
2694
|
+
return aggregate.distinct ? 'Aggregate(count-distinct)' : 'Aggregate(count)';
|
|
2695
|
+
}
|
|
2696
|
+
if (grouped) {
|
|
2697
|
+
return aggregates.some((aggregate) => aggregate.distinct)
|
|
2698
|
+
? 'Aggregate(group-count-multi-distinct)'
|
|
2699
|
+
: 'Aggregate(group-count-multi)';
|
|
2700
|
+
}
|
|
2701
|
+
return aggregates.some((aggregate) => aggregate.distinct)
|
|
2702
|
+
? 'Aggregate(count-multi-distinct)'
|
|
2703
|
+
: 'Aggregate(count-multi)';
|
|
2704
|
+
}
|
|
2705
|
+
//# sourceMappingURL=RdfLocalQueryEngine.js.map
|