@undefineds.co/xpod 0.3.17 → 0.3.22
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/bun.json +57 -11
- package/config/cloud.json +14 -12
- package/config/local.json +16 -14
- package/config/xpod.json +47 -9
- package/dist/api/matrix/PodMatrixStore.d.ts +4 -7
- package/dist/api/matrix/PodMatrixStore.js +116 -148
- package/dist/api/matrix/PodMatrixStore.js.map +1 -1
- package/dist/api/matrix/types.d.ts +2 -0
- package/dist/api/matrix/types.js.map +1 -1
- package/dist/components/components.jsonld +3 -0
- package/dist/components/context.jsonld +71 -32
- package/dist/http/SubgraphSparqlHttpHandler.d.ts +1 -0
- package/dist/http/SubgraphSparqlHttpHandler.js +27 -4
- package/dist/http/SubgraphSparqlHttpHandler.js.map +1 -1
- package/dist/http/SubgraphSparqlHttpHandler.jsonld +4 -0
- package/dist/http/vector/VectorHttpHandler.d.ts +5 -1
- package/dist/http/vector/VectorHttpHandler.js +5 -5
- package/dist/http/vector/VectorHttpHandler.js.map +1 -1
- package/dist/http/vector/VectorHttpHandler.jsonld +40 -28
- package/dist/index.d.ts +5 -3
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/runtime/Proxy.d.ts +3 -0
- package/dist/runtime/Proxy.js +31 -7
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js +94 -33
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.d.ts +22 -5
- package/dist/storage/accessors/MixDataAccessor.js +376 -61
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +73 -5
- package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js +32 -10
- package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js.map +1 -1
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +28 -6
- package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
- package/dist/storage/accessors/SolidRdfDataAccessor.d.ts +45 -0
- package/dist/storage/accessors/SolidRdfDataAccessor.js +277 -0
- package/dist/storage/accessors/SolidRdfDataAccessor.js.map +1 -0
- package/dist/storage/accessors/SolidRdfDataAccessor.jsonld +161 -0
- package/dist/storage/rdf/Rdf3xIndex.d.ts +122 -0
- package/dist/storage/rdf/Rdf3xIndex.js +2695 -0
- package/dist/storage/rdf/Rdf3xIndex.js.map +1 -0
- package/dist/storage/rdf/Rdf3xIndex.jsonld +528 -0
- package/dist/storage/rdf/Rdf3xSchema.d.ts +20 -0
- package/dist/storage/rdf/Rdf3xSchema.js +65 -0
- package/dist/storage/rdf/Rdf3xSchema.js.map +1 -0
- package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +10 -4
- package/dist/storage/rdf/RdfLocalQueryEngine.js +607 -127
- package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -1
- package/dist/storage/rdf/RdfQuadIndex.d.ts +12 -1
- package/dist/storage/rdf/RdfQuadIndex.js +152 -22
- package/dist/storage/rdf/RdfQuadIndex.js.map +1 -1
- package/dist/storage/rdf/RdfQuadIndex.jsonld +36 -4
- package/dist/storage/rdf/RdfSparqlAdapter.d.ts +20 -2
- package/dist/storage/rdf/RdfSparqlAdapter.js +364 -40
- package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -1
- package/dist/storage/rdf/RdfSparqlAdapter.jsonld +60 -0
- package/dist/storage/rdf/RdfTermDictionary.d.ts +8 -0
- package/dist/storage/rdf/RdfTermDictionary.js +141 -70
- package/dist/storage/rdf/RdfTermDictionary.js.map +1 -1
- package/dist/storage/rdf/RdfTermDictionary.jsonld +24 -0
- package/dist/storage/rdf/RdfTextIndex.js +10 -3
- package/dist/storage/rdf/RdfTextIndex.js.map +1 -1
- package/dist/storage/rdf/SolidRdfEngine.d.ts +15 -6
- package/dist/storage/rdf/SolidRdfEngine.js +218 -25
- package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
- package/dist/storage/rdf/SolidRdfEngine.jsonld +70 -7
- package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +11 -7
- package/dist/storage/rdf/SolidRdfSparqlEngine.js +60 -47
- package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
- package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +9 -5
- package/dist/storage/rdf/index.d.ts +2 -2
- package/dist/storage/rdf/index.js +3 -3
- package/dist/storage/rdf/index.js.map +1 -1
- package/dist/storage/rdf/models-benchmark.d.ts +12 -1
- package/dist/storage/rdf/models-benchmark.js +549 -32
- package/dist/storage/rdf/models-benchmark.js.map +1 -1
- package/dist/storage/rdf/types.d.ts +81 -7
- package/dist/storage/rdf/types.js.map +1 -1
- package/dist/storage/sparql/CompatibilitySparqlEngine.d.ts +36 -0
- package/dist/storage/sparql/CompatibilitySparqlEngine.js +96 -0
- package/dist/storage/sparql/CompatibilitySparqlEngine.js.map +1 -0
- package/dist/storage/sparql/CompatibilitySparqlEngine.jsonld +123 -0
- package/dist/storage/sparql/CompatibilitySparqlEngineImpl.d.ts +35 -0
- package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js +112 -0
- package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js.map +1 -0
- package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -36
- package/dist/storage/sparql/SubgraphQueryEngine.js +2 -115
- package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.jsonld +1 -124
- package/dist/terminal/AclPermissionService.d.ts +2 -1
- package/dist/terminal/AclPermissionService.js +26 -3
- package/dist/terminal/AclPermissionService.js.map +1 -1
- package/dist/terminal/TerminalSessionManager.js +25 -3
- package/dist/terminal/TerminalSessionManager.js.map +1 -1
- package/package.json +1 -1
- package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +0 -55
- package/dist/storage/rdf/Rdf3xTripleIndex.js +0 -1235
- package/dist/storage/rdf/Rdf3xTripleIndex.js.map +0 -1
|
@@ -371,6 +371,7 @@ exports.rdfModelsLocalQueryBenchmarkCases = [
|
|
|
371
371
|
query: {
|
|
372
372
|
patterns: [
|
|
373
373
|
{
|
|
374
|
+
graph: { $startsWith: 'https://pod.example/alice/.data/chat/default/' },
|
|
374
375
|
subject: { variable: 'message' },
|
|
375
376
|
predicate: namedNode(SIOC_HAS_MEMBER),
|
|
376
377
|
object: namedNode('https://pod.example/alice/.data/chat/default/index.ttl#thread_1'),
|
|
@@ -513,6 +514,7 @@ exports.rdfModelsLocalQueryBenchmarkCases = [
|
|
|
513
514
|
query: {
|
|
514
515
|
patterns: [
|
|
515
516
|
{
|
|
517
|
+
graph: { $startsWith: 'https://pod.example/alice/.data/chat/default/' },
|
|
516
518
|
subject: { variable: 'message' },
|
|
517
519
|
predicate: namedNode(SIOC_HAS_MEMBER),
|
|
518
520
|
object: { variable: 'thread' },
|
|
@@ -542,6 +544,70 @@ exports.rdfModelsLocalQueryBenchmarkCases = [
|
|
|
542
544
|
},
|
|
543
545
|
expectedPlan: ['group-count-index', 'having-pushdown', 'order', 'limit'],
|
|
544
546
|
},
|
|
547
|
+
{
|
|
548
|
+
name: 'message score by thread numeric aggregate',
|
|
549
|
+
resource: 'message',
|
|
550
|
+
purpose: 'grouped numeric message score aggregate stays inside SQL/RDF-3X GROUP BY',
|
|
551
|
+
minScale: 'small',
|
|
552
|
+
minReturnedRows: 1,
|
|
553
|
+
query: {
|
|
554
|
+
patterns: [
|
|
555
|
+
{
|
|
556
|
+
graph: { $startsWith: 'https://pod.example/alice/.data/chat/default/' },
|
|
557
|
+
subject: { variable: 'message' },
|
|
558
|
+
predicate: namedNode(SIOC_HAS_MEMBER),
|
|
559
|
+
object: { variable: 'thread' },
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
graph: { $startsWith: 'https://pod.example/alice/.data/chat/default/' },
|
|
563
|
+
subject: { variable: 'message' },
|
|
564
|
+
predicate: namedNode(`${UDFS}score`),
|
|
565
|
+
object: { variable: 'score' },
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
filters: [
|
|
569
|
+
{
|
|
570
|
+
variable: 'score',
|
|
571
|
+
operator: '$termType',
|
|
572
|
+
value: 'numeric',
|
|
573
|
+
},
|
|
574
|
+
],
|
|
575
|
+
groupBy: ['thread'],
|
|
576
|
+
aggregates: [
|
|
577
|
+
{
|
|
578
|
+
type: 'count',
|
|
579
|
+
as: 'count',
|
|
580
|
+
variable: 'message',
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
type: 'sum',
|
|
584
|
+
as: 'scoreTotal',
|
|
585
|
+
variable: 'score',
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
type: 'avg',
|
|
589
|
+
as: 'scoreAvg',
|
|
590
|
+
variable: 'score',
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
having: [
|
|
594
|
+
{
|
|
595
|
+
variable: 'scoreTotal',
|
|
596
|
+
operator: '$gt',
|
|
597
|
+
value: literal('4', namedNode('http://www.w3.org/2001/XMLSchema#integer')),
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
select: ['thread', 'count', 'scoreTotal', 'scoreAvg'],
|
|
601
|
+
orderBy: [
|
|
602
|
+
{
|
|
603
|
+
variable: 'scoreTotal',
|
|
604
|
+
direction: 'desc',
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
limit: 1,
|
|
608
|
+
},
|
|
609
|
+
expectedPlan: ['group-aggregate-index', 'having-pushdown', 'order', 'limit'],
|
|
610
|
+
},
|
|
545
611
|
{
|
|
546
612
|
name: 'message join count distinct',
|
|
547
613
|
resource: 'message',
|
|
@@ -550,11 +616,13 @@ exports.rdfModelsLocalQueryBenchmarkCases = [
|
|
|
550
616
|
query: {
|
|
551
617
|
patterns: [
|
|
552
618
|
{
|
|
619
|
+
graph: { $startsWith: 'https://pod.example/alice/.data/chat/default/' },
|
|
553
620
|
subject: { variable: 'message' },
|
|
554
621
|
predicate: namedNode(RDF_TYPE),
|
|
555
622
|
object: namedNode(`${MEETING}Message`),
|
|
556
623
|
},
|
|
557
624
|
{
|
|
625
|
+
graph: { $startsWith: 'https://pod.example/alice/.data/chat/default/' },
|
|
558
626
|
subject: { variable: 'message' },
|
|
559
627
|
predicate: namedNode(SIOC_HAS_MEMBER),
|
|
560
628
|
object: { variable: 'thread' },
|
|
@@ -621,6 +689,7 @@ function runRdfModelsBenchmark(engine, options = {}) {
|
|
|
621
689
|
generatedAt: new Date().toISOString(),
|
|
622
690
|
planMatched: failedPlanCases.length === 0,
|
|
623
691
|
failedPlanCases,
|
|
692
|
+
storage: engine.storageStats(),
|
|
624
693
|
cases: results,
|
|
625
694
|
localQueryCases: localQueryResults,
|
|
626
695
|
};
|
|
@@ -674,6 +743,10 @@ function runRdfModelsRdf3xShadowBenchmark(engine, options = {}) {
|
|
|
674
743
|
const joinResults = localQueryCases.map((testCase) => runRdf3xShadowJoinBenchmarkCase(engine, testCase, iterations));
|
|
675
744
|
const supportedResults = results.filter((result) => result.supported);
|
|
676
745
|
const supportedJoinResults = joinResults.filter((result) => result.supported);
|
|
746
|
+
const failedPlanCases = [
|
|
747
|
+
...supportedResults.filter((result) => !result.planMatched).map((result) => result.name),
|
|
748
|
+
...supportedJoinResults.filter((result) => !result.planMatched).map((result) => result.name),
|
|
749
|
+
];
|
|
677
750
|
return {
|
|
678
751
|
engine: 'rdf3x-shadow',
|
|
679
752
|
primaryEngine: 'solid-rdf',
|
|
@@ -685,13 +758,16 @@ function runRdfModelsRdf3xShadowBenchmark(engine, options = {}) {
|
|
|
685
758
|
&& supportedJoinResults.every((result) => result.matched),
|
|
686
759
|
orderedMatched: supportedResults.every((result) => result.orderedMatch)
|
|
687
760
|
&& supportedJoinResults.every((result) => result.orderedMatch),
|
|
761
|
+
planMatched: failedPlanCases.length === 0,
|
|
688
762
|
skippedCases: results.filter((result) => !result.supported).map((result) => result.name),
|
|
689
763
|
skippedJoinCases: joinResults.filter((result) => !result.supported).map((result) => result.name),
|
|
690
764
|
failedCases: supportedResults.filter((result) => !result.matched || !result.orderedMatch).map((result) => result.name),
|
|
691
765
|
failedJoinCases: supportedJoinResults
|
|
692
766
|
.filter((result) => !result.matched || !result.orderedMatch)
|
|
693
767
|
.map((result) => result.name),
|
|
768
|
+
failedPlanCases,
|
|
694
769
|
rebuild,
|
|
770
|
+
storage: engine.storageStats(),
|
|
695
771
|
cases: results,
|
|
696
772
|
joinCases: joinResults,
|
|
697
773
|
};
|
|
@@ -761,7 +837,7 @@ function runLocalQueryBenchmarkCase(engine, testCase, iterations) {
|
|
|
761
837
|
filtersApplied: 0,
|
|
762
838
|
filtersPushedDown: 0,
|
|
763
839
|
};
|
|
764
|
-
const missingPlan = missingExpectedLocalQueryPlan(testCase, finalMetrics);
|
|
840
|
+
const missingPlan = missingExpectedLocalQueryPlan(testCase, finalMetrics, keys.length);
|
|
765
841
|
return {
|
|
766
842
|
name: testCase.name,
|
|
767
843
|
resource: testCase.resource,
|
|
@@ -793,6 +869,8 @@ function runRdf3xShadowBenchmarkCase(engine, testCase, iterations) {
|
|
|
793
869
|
...baseResult,
|
|
794
870
|
supported: false,
|
|
795
871
|
unsupportedReason,
|
|
872
|
+
planMatched: false,
|
|
873
|
+
missingPlan: [unsupportedReason],
|
|
796
874
|
matched: false,
|
|
797
875
|
orderedMatch: false,
|
|
798
876
|
diff: {
|
|
@@ -840,10 +918,14 @@ function runRdf3xShadowBenchmarkCase(engine, testCase, iterations) {
|
|
|
840
918
|
returnedRows: 0,
|
|
841
919
|
durationMs: 0,
|
|
842
920
|
};
|
|
921
|
+
const missingPlan = missingExpectedRdf3xPlan(testCase, finalRdf3xMetrics);
|
|
922
|
+
const planMatched = missingPlan.length === 0;
|
|
843
923
|
return {
|
|
844
924
|
...baseResult,
|
|
845
925
|
supported: true,
|
|
846
|
-
|
|
926
|
+
planMatched,
|
|
927
|
+
missingPlan,
|
|
928
|
+
matched: planMatched && diff.missingFromPrimary.length === 0 && diff.extraInPrimary.length === 0,
|
|
847
929
|
orderedMatch,
|
|
848
930
|
diff,
|
|
849
931
|
solidRdf: {
|
|
@@ -867,6 +949,8 @@ function runRdf3xShadowJoinBenchmarkCase(engine, testCase, iterations) {
|
|
|
867
949
|
...baseRdf3xShadowJoinBenchmarkResult(testCase),
|
|
868
950
|
supported: false,
|
|
869
951
|
unsupportedReason,
|
|
952
|
+
planMatched: false,
|
|
953
|
+
missingPlan: [unsupportedReason],
|
|
870
954
|
matched: false,
|
|
871
955
|
orderedMatch: false,
|
|
872
956
|
diff: {
|
|
@@ -885,12 +969,12 @@ function runRdf3xShadowJoinBenchmarkCase(engine, testCase, iterations) {
|
|
|
885
969
|
let rdf3xMetrics;
|
|
886
970
|
for (let i = 0; i < iterations; i += 1) {
|
|
887
971
|
let start = Date.now();
|
|
888
|
-
const solidRdfResult = engine
|
|
972
|
+
const solidRdfResult = runSolidRdfJoinShape(engine, joinShape);
|
|
889
973
|
solidRdfDurationsMs.push(Math.max(0, Date.now() - start));
|
|
890
974
|
solidRdfBindings = solidRdfResult.bindings;
|
|
891
975
|
solidRdfMetrics = solidRdfResult.metrics;
|
|
892
976
|
start = Date.now();
|
|
893
|
-
const rdf3xResult = engine
|
|
977
|
+
const rdf3xResult = runRdf3xJoinShape(engine, joinShape);
|
|
894
978
|
rdf3xDurationsMs.push(Math.max(0, Date.now() - start));
|
|
895
979
|
rdf3xBindings = rdf3xResult.bindings;
|
|
896
980
|
rdf3xMetrics = rdf3xResult.metrics;
|
|
@@ -898,7 +982,7 @@ function runRdf3xShadowJoinBenchmarkCase(engine, testCase, iterations) {
|
|
|
898
982
|
const solidRdfKeys = solidRdfBindings.map(bindingKey);
|
|
899
983
|
const rdf3xKeys = rdf3xBindings.map(bindingKey);
|
|
900
984
|
const diff = diffBindingKeys(solidRdfKeys, rdf3xKeys);
|
|
901
|
-
const orderedMatch =
|
|
985
|
+
const orderedMatch = isSemanticallyOrderedRdf3xJoinShape(joinShape)
|
|
902
986
|
? rdf3xKeys.join('\n') === solidRdfKeys.join('\n')
|
|
903
987
|
: true;
|
|
904
988
|
const finalSolidRdfMetrics = solidRdfMetrics ?? {
|
|
@@ -915,10 +999,19 @@ function runRdf3xShadowJoinBenchmarkCase(engine, testCase, iterations) {
|
|
|
915
999
|
returnedRows: 0,
|
|
916
1000
|
durationMs: 0,
|
|
917
1001
|
};
|
|
1002
|
+
const missingPlan = [
|
|
1003
|
+
...missingExpectedRdf3xJoinPlan(testCase, finalRdf3xMetrics, rdf3xKeys.length),
|
|
1004
|
+
...unresolvedPlanFailures(finalSolidRdfMetrics.queryPlan ?? []).map((label) => `solid-rdf:${label}`),
|
|
1005
|
+
];
|
|
1006
|
+
const planMatched = missingPlan.length === 0;
|
|
918
1007
|
return {
|
|
919
1008
|
...baseRdf3xShadowJoinBenchmarkResult(testCase),
|
|
920
1009
|
supported: true,
|
|
921
|
-
|
|
1010
|
+
planMatched,
|
|
1011
|
+
missingPlan,
|
|
1012
|
+
matched: planMatched
|
|
1013
|
+
&& diff.missingFromPrimary.length === 0
|
|
1014
|
+
&& diff.extraInPrimary.length === 0,
|
|
922
1015
|
orderedMatch,
|
|
923
1016
|
diff,
|
|
924
1017
|
solidRdf: {
|
|
@@ -935,6 +1028,42 @@ function runRdf3xShadowJoinBenchmarkCase(engine, testCase, iterations) {
|
|
|
935
1028
|
},
|
|
936
1029
|
};
|
|
937
1030
|
}
|
|
1031
|
+
function runSolidRdfJoinShape(engine, shape) {
|
|
1032
|
+
switch (shape.kind) {
|
|
1033
|
+
case 'join':
|
|
1034
|
+
return engine.index.joinPatterns(shape.patterns, shape.options);
|
|
1035
|
+
case 'join-count':
|
|
1036
|
+
return engine.index.countJoinPatterns(shape.patterns, shape.options);
|
|
1037
|
+
case 'join-aggregate':
|
|
1038
|
+
return engine.index.aggregateJoinPatterns(shape.patterns, shape.options);
|
|
1039
|
+
case 'group-count':
|
|
1040
|
+
return engine.index.groupCountJoinPatterns(shape.patterns, shape.options);
|
|
1041
|
+
case 'group-aggregate':
|
|
1042
|
+
return engine.index.groupAggregateJoinPatterns(shape.patterns, shape.options);
|
|
1043
|
+
default: {
|
|
1044
|
+
const exhaustive = shape;
|
|
1045
|
+
throw new Error(`Unsupported RDF-3X benchmark shape: ${JSON.stringify(exhaustive)}`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function runRdf3xJoinShape(engine, shape) {
|
|
1050
|
+
switch (shape.kind) {
|
|
1051
|
+
case 'join':
|
|
1052
|
+
return engine.rdf3xIndex.joinPatterns(shape.patterns, shape.options);
|
|
1053
|
+
case 'join-count':
|
|
1054
|
+
return engine.rdf3xIndex.countJoinPatterns(shape.patterns, shape.options);
|
|
1055
|
+
case 'join-aggregate':
|
|
1056
|
+
return engine.rdf3xIndex.aggregateJoinPatterns(shape.patterns, shape.options);
|
|
1057
|
+
case 'group-count':
|
|
1058
|
+
return engine.rdf3xIndex.groupCountJoinPatterns(shape.patterns, shape.options);
|
|
1059
|
+
case 'group-aggregate':
|
|
1060
|
+
return engine.rdf3xIndex.groupAggregateJoinPatterns(shape.patterns, shape.options);
|
|
1061
|
+
default: {
|
|
1062
|
+
const exhaustive = shape;
|
|
1063
|
+
throw new Error(`Unsupported RDF-3X benchmark shape: ${JSON.stringify(exhaustive)}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
938
1067
|
function baseRdf3xShadowBenchmarkResult(testCase) {
|
|
939
1068
|
return {
|
|
940
1069
|
name: testCase.name,
|
|
@@ -945,6 +1074,7 @@ function baseRdf3xShadowBenchmarkResult(testCase) {
|
|
|
945
1074
|
pattern: serializePattern(testCase.query.pattern),
|
|
946
1075
|
...(testCase.query.options ? { options: testCase.query.options } : {}),
|
|
947
1076
|
},
|
|
1077
|
+
expectedPlan: [...testCase.expectedPlan],
|
|
948
1078
|
};
|
|
949
1079
|
}
|
|
950
1080
|
function emptySolidRdfBenchmarkSide(engine) {
|
|
@@ -1058,6 +1188,7 @@ function baseRdf3xShadowJoinBenchmarkResult(testCase) {
|
|
|
1058
1188
|
purpose: testCase.purpose,
|
|
1059
1189
|
minScale: testCase.minScale,
|
|
1060
1190
|
query: serializeLocalQuery(testCase.query),
|
|
1191
|
+
expectedPlan: [...testCase.expectedPlan],
|
|
1061
1192
|
};
|
|
1062
1193
|
}
|
|
1063
1194
|
function unsupportedRdf3xJoinQueryReason(query) {
|
|
@@ -1073,24 +1204,64 @@ function unsupportedRdf3xJoinQueryReason(query) {
|
|
|
1073
1204
|
if (query.unions?.length || query.minus?.length || query.exists?.length || query.optional?.length) {
|
|
1074
1205
|
return 'RDF-3X join shadow only supports required BGP queries';
|
|
1075
1206
|
}
|
|
1076
|
-
if (query.binds?.length
|
|
1077
|
-
return 'RDF-3X join shadow does not support
|
|
1207
|
+
if (query.binds?.length) {
|
|
1208
|
+
return 'RDF-3X join shadow does not support BIND yet';
|
|
1078
1209
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1210
|
+
const aggregates = queryAggregates(query);
|
|
1211
|
+
const visibleVariables = new Set(query.patterns.flatMap((pattern) => variablesInLocalPattern(pattern)));
|
|
1212
|
+
const compiled = rdf3xJoinPatternsFor(query, aggregates);
|
|
1213
|
+
if (compiled.unsupportedReason) {
|
|
1214
|
+
return compiled.unsupportedReason;
|
|
1081
1215
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1216
|
+
if ((query.filters?.length ?? 0) > 0 && compiled.pushedFilterIndexes.size < (query.filters?.length ?? 0)) {
|
|
1217
|
+
return 'RDF-3X join shadow only supports filters that can be fully pushed into RDF-3X patterns';
|
|
1218
|
+
}
|
|
1219
|
+
if (aggregates.length > 0) {
|
|
1220
|
+
const aggregateReason = unsupportedRdf3xAggregateReason(query, aggregates, visibleVariables);
|
|
1221
|
+
if (aggregateReason) {
|
|
1222
|
+
return aggregateReason;
|
|
1087
1223
|
}
|
|
1088
1224
|
}
|
|
1225
|
+
if (aggregates.length === 0 && (query.groupBy?.length ?? 0) > 0) {
|
|
1226
|
+
return 'RDF-3X join shadow does not support GROUP BY without aggregates';
|
|
1227
|
+
}
|
|
1228
|
+
if (aggregates.length === 0 && (query.having?.length ?? 0) > 0) {
|
|
1229
|
+
return 'RDF-3X join shadow does not support HAVING without aggregates';
|
|
1230
|
+
}
|
|
1089
1231
|
return undefined;
|
|
1090
1232
|
}
|
|
1091
1233
|
function rdf3xJoinShapeFor(query) {
|
|
1234
|
+
const aggregates = queryAggregates(query);
|
|
1235
|
+
const compiled = rdf3xJoinPatternsFor(query, aggregates);
|
|
1236
|
+
if (!compiled.patterns) {
|
|
1237
|
+
throw new Error(compiled.unsupportedReason ?? 'RDF-3X join shadow cannot compile query shape');
|
|
1238
|
+
}
|
|
1239
|
+
if ((query.groupBy?.length ?? 0) > 0) {
|
|
1240
|
+
const aggregateAliases = new Set(aggregates.map((aggregate) => aggregate.as));
|
|
1241
|
+
const having = rdf3xGroupAggregateHaving(query.having ?? [], aggregateAliases);
|
|
1242
|
+
return {
|
|
1243
|
+
kind: aggregates.every((aggregate) => aggregate.type === 'count') ? 'group-count' : 'group-aggregate',
|
|
1244
|
+
patterns: compiled.patterns,
|
|
1245
|
+
options: {
|
|
1246
|
+
groupBy: query.groupBy ?? [],
|
|
1247
|
+
aggregates,
|
|
1248
|
+
...(having.length > 0 ? { having } : {}),
|
|
1249
|
+
...(query.orderBy ? { orderBy: query.orderBy } : {}),
|
|
1250
|
+
...(query.limit !== undefined ? { limit: query.limit } : {}),
|
|
1251
|
+
...(query.offset !== undefined ? { offset: query.offset } : {}),
|
|
1252
|
+
},
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
if (aggregates.length > 0) {
|
|
1256
|
+
return {
|
|
1257
|
+
kind: aggregates.every((aggregate) => aggregate.type === 'count') ? 'join-count' : 'join-aggregate',
|
|
1258
|
+
patterns: compiled.patterns,
|
|
1259
|
+
options: { aggregates },
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1092
1262
|
return {
|
|
1093
|
-
|
|
1263
|
+
kind: 'join',
|
|
1264
|
+
patterns: compiled.patterns,
|
|
1094
1265
|
options: {
|
|
1095
1266
|
...(query.select ? { project: query.select } : {}),
|
|
1096
1267
|
...(query.distinct ? { distinct: true } : {}),
|
|
@@ -1100,9 +1271,63 @@ function rdf3xJoinShapeFor(query) {
|
|
|
1100
1271
|
},
|
|
1101
1272
|
};
|
|
1102
1273
|
}
|
|
1103
|
-
function
|
|
1274
|
+
function unsupportedRdf3xAggregateReason(query, aggregates, visibleVariables) {
|
|
1275
|
+
if ((query.groupBy?.length ?? 0) > 0) {
|
|
1276
|
+
if ((query.groupBy ?? []).some((variableName) => !visibleVariables.has(variableName))) {
|
|
1277
|
+
return 'RDF-3X join shadow cannot group by variables outside required BGP';
|
|
1278
|
+
}
|
|
1279
|
+
const aggregateAliases = new Set(aggregates.map((aggregate) => aggregate.as));
|
|
1280
|
+
if ((query.orderBy ?? []).some((entry) => !(query.groupBy ?? []).includes(entry.variable) && !aggregateAliases.has(entry.variable))) {
|
|
1281
|
+
return 'RDF-3X join shadow cannot order grouped aggregates by unbound variables';
|
|
1282
|
+
}
|
|
1283
|
+
if (!canCompileRdf3xGroupAggregateHaving(query.having ?? [], aggregateAliases)) {
|
|
1284
|
+
return 'RDF-3X join shadow cannot push this grouped HAVING shape';
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
else if ((query.having?.length ?? 0) > 0) {
|
|
1288
|
+
return 'RDF-3X join shadow does not support non-grouped HAVING yet';
|
|
1289
|
+
}
|
|
1290
|
+
else if (query.orderBy?.length || query.limit !== undefined || query.offset !== undefined || query.distinct) {
|
|
1291
|
+
return 'RDF-3X join shadow does not support ORDER/LIMIT/DISTINCT around non-grouped aggregates yet';
|
|
1292
|
+
}
|
|
1293
|
+
for (const aggregate of aggregates) {
|
|
1294
|
+
if (aggregate.variable && !visibleVariables.has(aggregate.variable)) {
|
|
1295
|
+
return 'RDF-3X join shadow aggregate variable must be bound by required BGP';
|
|
1296
|
+
}
|
|
1297
|
+
if (aggregate.type !== 'count' && (!aggregate.variable || aggregate.distinct)) {
|
|
1298
|
+
return 'RDF-3X join shadow only supports non-distinct numeric aggregates over bound variables';
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return undefined;
|
|
1302
|
+
}
|
|
1303
|
+
function rdf3xJoinPatternsFor(query, aggregates) {
|
|
1304
|
+
const patterns = [];
|
|
1305
|
+
const pushedFilterIndexes = new Set();
|
|
1306
|
+
const numericAggregateVariables = new Set(aggregates
|
|
1307
|
+
.filter((aggregate) => aggregate.type !== 'count')
|
|
1308
|
+
.map((aggregate) => aggregate.variable)
|
|
1309
|
+
.filter((variableName) => Boolean(variableName)));
|
|
1310
|
+
for (const pattern of query.patterns) {
|
|
1311
|
+
const compiled = rdf3xJoinPatternFor(pattern, query.filters ?? [], numericAggregateVariables);
|
|
1312
|
+
const unsupportedPattern = unsupportedRdf3xPatternReason(compiled.pattern);
|
|
1313
|
+
if (unsupportedPattern) {
|
|
1314
|
+
return {
|
|
1315
|
+
pushedFilterIndexes,
|
|
1316
|
+
unsupportedReason: unsupportedPattern,
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
compiled.pushedFilterIndexes.forEach((index) => pushedFilterIndexes.add(index));
|
|
1320
|
+
patterns.push({
|
|
1321
|
+
pattern: compiled.pattern,
|
|
1322
|
+
variables: compiled.variables,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
return { patterns, pushedFilterIndexes };
|
|
1326
|
+
}
|
|
1327
|
+
function rdf3xJoinPatternFor(pattern, filters, numericAggregateVariables) {
|
|
1104
1328
|
const compiledPattern = {};
|
|
1105
1329
|
const variables = {};
|
|
1330
|
+
const pushedFilterIndexes = new Set();
|
|
1106
1331
|
for (const key of ['graph', 'subject', 'predicate', 'object']) {
|
|
1107
1332
|
const value = pattern[key];
|
|
1108
1333
|
if (!value) {
|
|
@@ -1110,6 +1335,13 @@ function rdf3xJoinPatternFor(pattern) {
|
|
|
1110
1335
|
}
|
|
1111
1336
|
if (isQueryVariable(value)) {
|
|
1112
1337
|
variables[key] = value.variable;
|
|
1338
|
+
const pushdown = rdf3xBenchmarkPushdownFilter(value.variable, filters, numericAggregateVariables);
|
|
1339
|
+
if (pushdown) {
|
|
1340
|
+
if (pushdown.pattern !== undefined) {
|
|
1341
|
+
compiledPattern[key] = pushdown.pattern;
|
|
1342
|
+
}
|
|
1343
|
+
pushdown.filterIndexes.forEach((index) => pushedFilterIndexes.add(index));
|
|
1344
|
+
}
|
|
1113
1345
|
}
|
|
1114
1346
|
else {
|
|
1115
1347
|
compiledPattern[key] = value;
|
|
@@ -1118,8 +1350,105 @@ function rdf3xJoinPatternFor(pattern) {
|
|
|
1118
1350
|
return {
|
|
1119
1351
|
pattern: compiledPattern,
|
|
1120
1352
|
variables,
|
|
1353
|
+
pushedFilterIndexes: [...pushedFilterIndexes],
|
|
1121
1354
|
};
|
|
1122
1355
|
}
|
|
1356
|
+
function rdf3xBenchmarkPushdownFilter(variableName, filters, numericAggregateVariables) {
|
|
1357
|
+
const operators = {};
|
|
1358
|
+
const filterIndexes = [];
|
|
1359
|
+
for (let index = 0; index < filters.length; index += 1) {
|
|
1360
|
+
const filter = filters[index];
|
|
1361
|
+
if (filter.variable !== variableName || filter.variable2 || filter.operand) {
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
switch (filter.operator) {
|
|
1365
|
+
case '$eq':
|
|
1366
|
+
case '$sameTerm':
|
|
1367
|
+
if (filter.value === undefined || !(0, types_1.isTerm)(filter.value)) {
|
|
1368
|
+
return undefined;
|
|
1369
|
+
}
|
|
1370
|
+
return { pattern: filter.value, filterIndexes: [index] };
|
|
1371
|
+
case '$in':
|
|
1372
|
+
if (!filter.values?.length || filter.values.some((value) => !(0, types_1.isTerm)(value))) {
|
|
1373
|
+
return undefined;
|
|
1374
|
+
}
|
|
1375
|
+
return { pattern: { $in: filter.values }, filterIndexes: [index] };
|
|
1376
|
+
case '$notIn':
|
|
1377
|
+
if (!filter.values?.length || filter.values.some((value) => !(0, types_1.isTerm)(value))) {
|
|
1378
|
+
return undefined;
|
|
1379
|
+
}
|
|
1380
|
+
return { pattern: { $notIn: filter.values }, filterIndexes: [index] };
|
|
1381
|
+
case '$gt':
|
|
1382
|
+
case '$gte':
|
|
1383
|
+
case '$lt':
|
|
1384
|
+
case '$lte':
|
|
1385
|
+
if (filter.value === undefined) {
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
operators[filter.operator] = filter.value;
|
|
1389
|
+
filterIndexes.push(index);
|
|
1390
|
+
break;
|
|
1391
|
+
case '$termType':
|
|
1392
|
+
if (filter.value !== 'numeric' || !numericAggregateVariables.has(variableName)) {
|
|
1393
|
+
return undefined;
|
|
1394
|
+
}
|
|
1395
|
+
filterIndexes.push(index);
|
|
1396
|
+
break;
|
|
1397
|
+
default:
|
|
1398
|
+
return undefined;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
if (Object.keys(operators).length > 0) {
|
|
1402
|
+
return { pattern: operators, filterIndexes };
|
|
1403
|
+
}
|
|
1404
|
+
return filterIndexes.length > 0
|
|
1405
|
+
? { filterIndexes }
|
|
1406
|
+
: undefined;
|
|
1407
|
+
}
|
|
1408
|
+
function queryAggregates(query) {
|
|
1409
|
+
return query.aggregates && query.aggregates.length > 0
|
|
1410
|
+
? query.aggregates
|
|
1411
|
+
: query.aggregate
|
|
1412
|
+
? [query.aggregate]
|
|
1413
|
+
: [];
|
|
1414
|
+
}
|
|
1415
|
+
function variablesInLocalPattern(pattern) {
|
|
1416
|
+
return ['graph', 'subject', 'predicate', 'object']
|
|
1417
|
+
.map((key) => pattern[key])
|
|
1418
|
+
.filter(isQueryVariable)
|
|
1419
|
+
.map((value) => value.variable);
|
|
1420
|
+
}
|
|
1421
|
+
function canCompileRdf3xGroupAggregateHaving(having, aggregateAliases) {
|
|
1422
|
+
return having.every((filter) => (aggregateAliases.has(filter.variable)
|
|
1423
|
+
&& !filter.operand
|
|
1424
|
+
&& !filter.variable2
|
|
1425
|
+
&& filter.value !== undefined
|
|
1426
|
+
&& isGroupAggregateHavingOperator(filter.operator)
|
|
1427
|
+
&& numericRangeValue(filter.value) !== undefined));
|
|
1428
|
+
}
|
|
1429
|
+
function rdf3xGroupAggregateHaving(having, aggregateAliases) {
|
|
1430
|
+
return having.map((filter) => {
|
|
1431
|
+
const value = filter.value === undefined ? undefined : numericRangeValue(filter.value);
|
|
1432
|
+
if (!aggregateAliases.has(filter.variable)
|
|
1433
|
+
|| !isGroupAggregateHavingOperator(filter.operator)
|
|
1434
|
+
|| value === undefined) {
|
|
1435
|
+
throw new Error('RDF-3X join shadow cannot compile grouped HAVING');
|
|
1436
|
+
}
|
|
1437
|
+
return {
|
|
1438
|
+
aggregate: filter.variable,
|
|
1439
|
+
operator: filter.operator,
|
|
1440
|
+
value,
|
|
1441
|
+
};
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
function isGroupAggregateHavingOperator(operator) {
|
|
1445
|
+
return operator === '$eq'
|
|
1446
|
+
|| operator === '$ne'
|
|
1447
|
+
|| operator === '$gt'
|
|
1448
|
+
|| operator === '$gte'
|
|
1449
|
+
|| operator === '$lt'
|
|
1450
|
+
|| operator === '$lte';
|
|
1451
|
+
}
|
|
1123
1452
|
function isQueryVariable(value) {
|
|
1124
1453
|
return value !== null
|
|
1125
1454
|
&& typeof value === 'object'
|
|
@@ -1136,7 +1465,13 @@ function unsupportedRdf3xPatternReason(pattern) {
|
|
|
1136
1465
|
if (key === 'graph' && isGraphPrefixPattern(value)) {
|
|
1137
1466
|
continue;
|
|
1138
1467
|
}
|
|
1139
|
-
if (
|
|
1468
|
+
if (isRdf3xTermInPattern(value)) {
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if (isRdf3xTermNotInPattern(value)) {
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
if (key === 'object' && isSupportedRdf3xObjectOperatorPattern(value)) {
|
|
1140
1475
|
continue;
|
|
1141
1476
|
}
|
|
1142
1477
|
return `unsupported ${key} pattern for RDF-3X shadow`;
|
|
@@ -1154,7 +1489,15 @@ function rdf3xPatternFor(pattern) {
|
|
|
1154
1489
|
result.graph = { $startsWith: value.$startsWith };
|
|
1155
1490
|
continue;
|
|
1156
1491
|
}
|
|
1157
|
-
if (
|
|
1492
|
+
if (isRdf3xTermInPattern(value)) {
|
|
1493
|
+
result[key] = value;
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
if (isRdf3xTermNotInPattern(value)) {
|
|
1497
|
+
result[key] = value;
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
if (key === 'object' && isSupportedRdf3xObjectOperatorPattern(value)) {
|
|
1158
1501
|
result.object = value;
|
|
1159
1502
|
continue;
|
|
1160
1503
|
}
|
|
@@ -1165,30 +1508,70 @@ function rdf3xPatternFor(pattern) {
|
|
|
1165
1508
|
}
|
|
1166
1509
|
return result;
|
|
1167
1510
|
}
|
|
1511
|
+
function isRdf3xTermInPattern(value) {
|
|
1512
|
+
return value !== null
|
|
1513
|
+
&& typeof value === 'object'
|
|
1514
|
+
&& !('termType' in value)
|
|
1515
|
+
&& Object.keys(value).length === 1
|
|
1516
|
+
&& Array.isArray(value.$in)
|
|
1517
|
+
&& (value.$in).length > 0
|
|
1518
|
+
&& (value.$in).every((entry) => (0, types_1.isTerm)(entry));
|
|
1519
|
+
}
|
|
1520
|
+
function isRdf3xTermNotInPattern(value) {
|
|
1521
|
+
return value !== null
|
|
1522
|
+
&& typeof value === 'object'
|
|
1523
|
+
&& !('termType' in value)
|
|
1524
|
+
&& Object.keys(value).length === 1
|
|
1525
|
+
&& Array.isArray(value.$notIn)
|
|
1526
|
+
&& (value.$notIn).length > 0
|
|
1527
|
+
&& (value.$notIn).every((entry) => (0, types_1.isTerm)(entry));
|
|
1528
|
+
}
|
|
1168
1529
|
function isGraphPrefixPattern(value) {
|
|
1169
1530
|
return value !== null
|
|
1170
1531
|
&& typeof value === 'object'
|
|
1171
1532
|
&& '$startsWith' in value
|
|
1172
1533
|
&& typeof value.$startsWith === 'string';
|
|
1173
1534
|
}
|
|
1174
|
-
function
|
|
1535
|
+
function isSupportedRdf3xObjectOperatorPattern(value) {
|
|
1175
1536
|
if (value === null || typeof value !== 'object' || 'termType' in value) {
|
|
1176
1537
|
return false;
|
|
1177
1538
|
}
|
|
1178
|
-
let
|
|
1539
|
+
let hasOperator = false;
|
|
1179
1540
|
for (const operator of ['$gt', '$gte', '$lt', '$lte']) {
|
|
1180
1541
|
const rangeValue = value[operator];
|
|
1181
1542
|
if (rangeValue === undefined) {
|
|
1182
1543
|
continue;
|
|
1183
1544
|
}
|
|
1184
|
-
|
|
1185
|
-
if (
|
|
1545
|
+
hasOperator = true;
|
|
1546
|
+
if (!isSupportedRdf3xObjectRangeValue(rangeValue)) {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
for (const operator of ['$contains', '$endsWith']) {
|
|
1551
|
+
const textValue = value[operator];
|
|
1552
|
+
if (textValue === undefined) {
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
hasOperator = true;
|
|
1556
|
+
if (typeof textValue !== 'string') {
|
|
1186
1557
|
return false;
|
|
1187
1558
|
}
|
|
1188
1559
|
}
|
|
1189
|
-
return
|
|
1560
|
+
return hasOperator;
|
|
1561
|
+
}
|
|
1562
|
+
function isSupportedRdf3xObjectRangeValue(value) {
|
|
1563
|
+
if (typeof value === 'number') {
|
|
1564
|
+
return Number.isFinite(value);
|
|
1565
|
+
}
|
|
1566
|
+
if (typeof value === 'string') {
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
return (0, types_1.isTerm)(value);
|
|
1190
1570
|
}
|
|
1191
1571
|
function numericRangeValue(value) {
|
|
1572
|
+
if (typeof value === 'boolean') {
|
|
1573
|
+
return undefined;
|
|
1574
|
+
}
|
|
1192
1575
|
if (typeof value === 'number') {
|
|
1193
1576
|
return Number.isFinite(value) ? value : undefined;
|
|
1194
1577
|
}
|
|
@@ -1205,8 +1588,20 @@ function numericRangeValue(value) {
|
|
|
1205
1588
|
function isSemanticallyOrdered(options) {
|
|
1206
1589
|
return Boolean(options?.order && options.order.length > 0);
|
|
1207
1590
|
}
|
|
1208
|
-
function
|
|
1209
|
-
|
|
1591
|
+
function isSemanticallyOrderedRdf3xJoinShape(shape) {
|
|
1592
|
+
switch (shape.kind) {
|
|
1593
|
+
case 'join':
|
|
1594
|
+
case 'group-count':
|
|
1595
|
+
case 'group-aggregate':
|
|
1596
|
+
return Boolean(shape.options?.orderBy && shape.options.orderBy.length > 0);
|
|
1597
|
+
case 'join-count':
|
|
1598
|
+
case 'join-aggregate':
|
|
1599
|
+
return false;
|
|
1600
|
+
default: {
|
|
1601
|
+
const exhaustive = shape;
|
|
1602
|
+
throw new Error(`Unsupported RDF-3X benchmark shape: ${JSON.stringify(exhaustive)}`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1210
1605
|
}
|
|
1211
1606
|
function benchmarkSide(keys, durationsMs) {
|
|
1212
1607
|
return {
|
|
@@ -1271,7 +1666,10 @@ function ratio(candidate, baseline) {
|
|
|
1271
1666
|
return candidate / baseline;
|
|
1272
1667
|
}
|
|
1273
1668
|
function missingExpectedPlan(testCase, metrics) {
|
|
1274
|
-
return
|
|
1669
|
+
return [
|
|
1670
|
+
...testCase.expectedPlan.filter((label) => !matchesExpectedPlanLabel(label, testCase, metrics)),
|
|
1671
|
+
...unresolvedPlanFailures(metrics.queryPlan ?? []),
|
|
1672
|
+
];
|
|
1275
1673
|
}
|
|
1276
1674
|
function matchesExpectedPlanLabel(label, testCase, metrics) {
|
|
1277
1675
|
const pattern = testCase.query.pattern;
|
|
@@ -1311,21 +1709,102 @@ function matchesExpectedPlanLabel(label, testCase, metrics) {
|
|
|
1311
1709
|
return false;
|
|
1312
1710
|
}
|
|
1313
1711
|
}
|
|
1314
|
-
function
|
|
1315
|
-
return
|
|
1712
|
+
function missingExpectedRdf3xPlan(testCase, metrics) {
|
|
1713
|
+
return [
|
|
1714
|
+
...testCase.expectedPlan.filter((label) => !matchesExpectedRdf3xPlanLabel(label, testCase, metrics)),
|
|
1715
|
+
...unresolvedPlanFailures(metrics.queryPlan ?? []),
|
|
1716
|
+
];
|
|
1717
|
+
}
|
|
1718
|
+
function matchesExpectedRdf3xPlanLabel(label, testCase, metrics) {
|
|
1719
|
+
const pattern = testCase.query.pattern;
|
|
1720
|
+
const planText = (metrics.queryPlan ?? []).join('\n');
|
|
1721
|
+
switch (label) {
|
|
1722
|
+
case 'graph-scope':
|
|
1723
|
+
return Boolean(pattern.graph)
|
|
1724
|
+
&& (metrics.indexChoice === 'source-membership'
|
|
1725
|
+
|| planText.includes('GraphPrefixMembershipFilter')
|
|
1726
|
+
|| planText.includes('GraphMembershipFilter'));
|
|
1727
|
+
case 'type-filter':
|
|
1728
|
+
return (0, types_1.isTerm)(pattern.predicate)
|
|
1729
|
+
&& (0, n3_2.termToId)(pattern.predicate) === RDF_TYPE
|
|
1730
|
+
&& Boolean(pattern.object)
|
|
1731
|
+
&& metrics.indexChoice !== 'none';
|
|
1732
|
+
case 'predicate-filter':
|
|
1733
|
+
return Boolean(pattern.predicate) && metrics.indexChoice !== 'none';
|
|
1734
|
+
case 'predicate-object-filter':
|
|
1735
|
+
return Boolean(pattern.predicate) && Boolean(pattern.object) && metrics.indexChoice !== 'none';
|
|
1736
|
+
case 'predicate-object-range-filter':
|
|
1737
|
+
return Boolean(pattern.predicate)
|
|
1738
|
+
&& (planText.includes('NumericRange(') || planText.includes('LexicalRange('));
|
|
1739
|
+
case 'limit':
|
|
1740
|
+
return testCase.query.options?.limit !== undefined
|
|
1741
|
+
&& (planText.includes('Pagination') || planText.includes('LIMIT'));
|
|
1742
|
+
case 'order':
|
|
1743
|
+
return Boolean(testCase.query.options?.order?.length)
|
|
1744
|
+
&& (planText.includes('ORDER BY') || planText.includes('Rdf3xJoinOrder('));
|
|
1745
|
+
case 'text-index':
|
|
1746
|
+
return planText.includes('TextSearch(');
|
|
1747
|
+
case 'rdf-subject-join':
|
|
1748
|
+
return planText.includes('TextSearch(')
|
|
1749
|
+
&& metrics.indexChoice !== 'none'
|
|
1750
|
+
&& metrics.matchedRows >= metrics.returnedRows;
|
|
1751
|
+
case 'SPOG':
|
|
1752
|
+
return matchesRdf3xPermutation(metrics, 'SPO');
|
|
1753
|
+
case 'POSG':
|
|
1754
|
+
return matchesRdf3xPermutation(metrics, 'POS');
|
|
1755
|
+
case 'OSPG':
|
|
1756
|
+
return matchesRdf3xPermutation(metrics, 'OSP');
|
|
1757
|
+
case 'GSPO':
|
|
1758
|
+
return matchesExpectedRdf3xPlanLabel('graph-scope', testCase, metrics)
|
|
1759
|
+
&& matchesRdf3xPermutation(metrics, 'SPO');
|
|
1760
|
+
case 'GPOS':
|
|
1761
|
+
return matchesExpectedRdf3xPlanLabel('graph-scope', testCase, metrics)
|
|
1762
|
+
&& matchesRdf3xPermutation(metrics, 'POS');
|
|
1763
|
+
default:
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
function matchesRdf3xPermutation(metrics, permutation) {
|
|
1768
|
+
const planText = (metrics.queryPlan ?? []).join('\n');
|
|
1769
|
+
return metrics.indexChoice === permutation || planText.includes(`Rdf3xPermutationScan(${permutation})`);
|
|
1770
|
+
}
|
|
1771
|
+
function missingExpectedLocalQueryPlan(testCase, metrics, returnedRows) {
|
|
1772
|
+
return [
|
|
1773
|
+
...testCase.expectedPlan.filter((label) => !matchesExpectedLocalQueryPlanLabel(label, metrics)),
|
|
1774
|
+
...unresolvedPlanFailures(metrics.plan),
|
|
1775
|
+
...minimumReturnedRowsFailures(testCase, returnedRows),
|
|
1776
|
+
];
|
|
1777
|
+
}
|
|
1778
|
+
function unresolvedPlanFailures(plan) {
|
|
1779
|
+
return hasUnresolvedPlan(plan) ? ['resolved-terms'] : [];
|
|
1780
|
+
}
|
|
1781
|
+
function hasUnresolvedPlan(plan) {
|
|
1782
|
+
return plan.some((entry) => /\bunresolved\b/i.test(entry));
|
|
1783
|
+
}
|
|
1784
|
+
function minimumReturnedRowsFailures(testCase, returnedRows) {
|
|
1785
|
+
const minimum = testCase.minReturnedRows ?? 0;
|
|
1786
|
+
return returnedRows >= minimum ? [] : [`min-rows:${minimum}`];
|
|
1316
1787
|
}
|
|
1317
1788
|
function matchesExpectedLocalQueryPlanLabel(label, metrics) {
|
|
1318
1789
|
const planText = metrics.plan.join('\n');
|
|
1319
1790
|
switch (label) {
|
|
1320
1791
|
case 'group-count-index':
|
|
1321
1792
|
return planText.includes('Aggregate(group-count-index)');
|
|
1793
|
+
case 'group-aggregate-index':
|
|
1794
|
+
return planText.includes('Aggregate(group-basic-multi-index)')
|
|
1795
|
+
|| planText.includes('Aggregate(group-basic-index)');
|
|
1322
1796
|
case 'having-pushdown':
|
|
1323
|
-
return planText.includes('IndexGroupCountHaving(')
|
|
1797
|
+
return (planText.includes('IndexGroupCountHaving(')
|
|
1798
|
+
|| planText.includes('IndexGroupAggregateHaving('))
|
|
1324
1799
|
&& !planText.includes('\nHaving(');
|
|
1325
1800
|
case 'order':
|
|
1326
|
-
return planText.includes('IndexGroupCountOrder(')
|
|
1801
|
+
return (planText.includes('IndexGroupCountOrder(')
|
|
1802
|
+
|| planText.includes('IndexGroupAggregateOrder('))
|
|
1803
|
+
&& !planText.includes('\nSort');
|
|
1327
1804
|
case 'limit':
|
|
1328
|
-
return planText.includes('IndexGroupCountLimit')
|
|
1805
|
+
return (planText.includes('IndexGroupCountLimit')
|
|
1806
|
+
|| planText.includes('IndexGroupAggregateLimit'))
|
|
1807
|
+
&& !planText.includes('\nLimit');
|
|
1329
1808
|
case 'join-index':
|
|
1330
1809
|
return planText.includes('IndexJoin(')
|
|
1331
1810
|
&& !planText.includes('\nIndexScan(');
|
|
@@ -1346,6 +1825,44 @@ function matchesExpectedLocalQueryPlanLabel(label, metrics) {
|
|
|
1346
1825
|
return false;
|
|
1347
1826
|
}
|
|
1348
1827
|
}
|
|
1828
|
+
function missingExpectedRdf3xJoinPlan(testCase, metrics, returnedRows) {
|
|
1829
|
+
return [
|
|
1830
|
+
...testCase.expectedPlan.filter((label) => !matchesExpectedRdf3xJoinPlanLabel(label, metrics)),
|
|
1831
|
+
...unresolvedPlanFailures(metrics.queryPlan ?? []),
|
|
1832
|
+
...minimumReturnedRowsFailures(testCase, returnedRows),
|
|
1833
|
+
];
|
|
1834
|
+
}
|
|
1835
|
+
function matchesExpectedRdf3xJoinPlanLabel(label, metrics) {
|
|
1836
|
+
const planText = (metrics.queryPlan ?? []).join('\n');
|
|
1837
|
+
switch (label) {
|
|
1838
|
+
case 'group-count-index':
|
|
1839
|
+
return planText.includes('Rdf3xJoinGroupCount(');
|
|
1840
|
+
case 'group-aggregate-index':
|
|
1841
|
+
return planText.includes('Rdf3xJoinGroupAggregate(')
|
|
1842
|
+
|| planText.includes('Rdf3xJoinGroupAggregateNumeric(');
|
|
1843
|
+
case 'having-pushdown':
|
|
1844
|
+
return planText.includes('Rdf3xJoinGroupCountHaving(')
|
|
1845
|
+
|| planText.includes('Rdf3xJoinGroupAggregateHaving(');
|
|
1846
|
+
case 'order':
|
|
1847
|
+
return planText.includes('Rdf3xJoinGroupCountOrder(')
|
|
1848
|
+
|| planText.includes('Rdf3xJoinGroupAggregateOrder(');
|
|
1849
|
+
case 'limit':
|
|
1850
|
+
return planText.includes('Rdf3xJoinGroupCountLimit')
|
|
1851
|
+
|| planText.includes('Rdf3xJoinGroupAggregateLimit');
|
|
1852
|
+
case 'join-index':
|
|
1853
|
+
return planText.includes('Rdf3xJoinBGP(');
|
|
1854
|
+
case 'join-order-pushdown':
|
|
1855
|
+
return planText.includes('Rdf3xJoinOrder(');
|
|
1856
|
+
case 'join-limit-pushdown':
|
|
1857
|
+
return planText.includes('Rdf3xJoinLimit');
|
|
1858
|
+
case 'range-filter-pushdown':
|
|
1859
|
+
return planText.includes('LexicalRange(') || planText.includes('NumericRange(');
|
|
1860
|
+
case 'join-count-index':
|
|
1861
|
+
return planText.includes('Rdf3xJoinCount(');
|
|
1862
|
+
default:
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1349
1866
|
function bindingKey(binding) {
|
|
1350
1867
|
return Object.keys(binding)
|
|
1351
1868
|
.sort()
|