@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.
Files changed (99) hide show
  1. package/config/bun.json +57 -11
  2. package/config/cloud.json +14 -12
  3. package/config/local.json +16 -14
  4. package/config/xpod.json +47 -9
  5. package/dist/api/matrix/PodMatrixStore.d.ts +4 -7
  6. package/dist/api/matrix/PodMatrixStore.js +116 -148
  7. package/dist/api/matrix/PodMatrixStore.js.map +1 -1
  8. package/dist/api/matrix/types.d.ts +2 -0
  9. package/dist/api/matrix/types.js.map +1 -1
  10. package/dist/components/components.jsonld +3 -0
  11. package/dist/components/context.jsonld +71 -32
  12. package/dist/http/SubgraphSparqlHttpHandler.d.ts +1 -0
  13. package/dist/http/SubgraphSparqlHttpHandler.js +27 -4
  14. package/dist/http/SubgraphSparqlHttpHandler.js.map +1 -1
  15. package/dist/http/SubgraphSparqlHttpHandler.jsonld +4 -0
  16. package/dist/http/vector/VectorHttpHandler.d.ts +5 -1
  17. package/dist/http/vector/VectorHttpHandler.js +5 -5
  18. package/dist/http/vector/VectorHttpHandler.js.map +1 -1
  19. package/dist/http/vector/VectorHttpHandler.jsonld +40 -28
  20. package/dist/index.d.ts +5 -3
  21. package/dist/index.js +9 -6
  22. package/dist/index.js.map +1 -1
  23. package/dist/runtime/Proxy.d.ts +3 -0
  24. package/dist/runtime/Proxy.js +31 -7
  25. package/dist/runtime/Proxy.js.map +1 -1
  26. package/dist/storage/SparqlUpdateResourceStore.js +94 -33
  27. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  28. package/dist/storage/accessors/MixDataAccessor.d.ts +22 -5
  29. package/dist/storage/accessors/MixDataAccessor.js +376 -61
  30. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  31. package/dist/storage/accessors/MixDataAccessor.jsonld +73 -5
  32. package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js +32 -10
  33. package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js.map +1 -1
  34. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +28 -6
  35. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
  36. package/dist/storage/accessors/SolidRdfDataAccessor.d.ts +45 -0
  37. package/dist/storage/accessors/SolidRdfDataAccessor.js +277 -0
  38. package/dist/storage/accessors/SolidRdfDataAccessor.js.map +1 -0
  39. package/dist/storage/accessors/SolidRdfDataAccessor.jsonld +161 -0
  40. package/dist/storage/rdf/Rdf3xIndex.d.ts +122 -0
  41. package/dist/storage/rdf/Rdf3xIndex.js +2695 -0
  42. package/dist/storage/rdf/Rdf3xIndex.js.map +1 -0
  43. package/dist/storage/rdf/Rdf3xIndex.jsonld +528 -0
  44. package/dist/storage/rdf/Rdf3xSchema.d.ts +20 -0
  45. package/dist/storage/rdf/Rdf3xSchema.js +65 -0
  46. package/dist/storage/rdf/Rdf3xSchema.js.map +1 -0
  47. package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +10 -4
  48. package/dist/storage/rdf/RdfLocalQueryEngine.js +607 -127
  49. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -1
  50. package/dist/storage/rdf/RdfQuadIndex.d.ts +12 -1
  51. package/dist/storage/rdf/RdfQuadIndex.js +152 -22
  52. package/dist/storage/rdf/RdfQuadIndex.js.map +1 -1
  53. package/dist/storage/rdf/RdfQuadIndex.jsonld +36 -4
  54. package/dist/storage/rdf/RdfSparqlAdapter.d.ts +20 -2
  55. package/dist/storage/rdf/RdfSparqlAdapter.js +364 -40
  56. package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -1
  57. package/dist/storage/rdf/RdfSparqlAdapter.jsonld +60 -0
  58. package/dist/storage/rdf/RdfTermDictionary.d.ts +8 -0
  59. package/dist/storage/rdf/RdfTermDictionary.js +141 -70
  60. package/dist/storage/rdf/RdfTermDictionary.js.map +1 -1
  61. package/dist/storage/rdf/RdfTermDictionary.jsonld +24 -0
  62. package/dist/storage/rdf/RdfTextIndex.js +10 -3
  63. package/dist/storage/rdf/RdfTextIndex.js.map +1 -1
  64. package/dist/storage/rdf/SolidRdfEngine.d.ts +15 -6
  65. package/dist/storage/rdf/SolidRdfEngine.js +218 -25
  66. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  67. package/dist/storage/rdf/SolidRdfEngine.jsonld +70 -7
  68. package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +11 -7
  69. package/dist/storage/rdf/SolidRdfSparqlEngine.js +60 -47
  70. package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
  71. package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +9 -5
  72. package/dist/storage/rdf/index.d.ts +2 -2
  73. package/dist/storage/rdf/index.js +3 -3
  74. package/dist/storage/rdf/index.js.map +1 -1
  75. package/dist/storage/rdf/models-benchmark.d.ts +12 -1
  76. package/dist/storage/rdf/models-benchmark.js +549 -32
  77. package/dist/storage/rdf/models-benchmark.js.map +1 -1
  78. package/dist/storage/rdf/types.d.ts +81 -7
  79. package/dist/storage/rdf/types.js.map +1 -1
  80. package/dist/storage/sparql/CompatibilitySparqlEngine.d.ts +36 -0
  81. package/dist/storage/sparql/CompatibilitySparqlEngine.js +96 -0
  82. package/dist/storage/sparql/CompatibilitySparqlEngine.js.map +1 -0
  83. package/dist/storage/sparql/CompatibilitySparqlEngine.jsonld +123 -0
  84. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.d.ts +35 -0
  85. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js +112 -0
  86. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js.map +1 -0
  87. package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -36
  88. package/dist/storage/sparql/SubgraphQueryEngine.js +2 -115
  89. package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
  90. package/dist/storage/sparql/SubgraphQueryEngine.jsonld +1 -124
  91. package/dist/terminal/AclPermissionService.d.ts +2 -1
  92. package/dist/terminal/AclPermissionService.js +26 -3
  93. package/dist/terminal/AclPermissionService.js.map +1 -1
  94. package/dist/terminal/TerminalSessionManager.js +25 -3
  95. package/dist/terminal/TerminalSessionManager.js.map +1 -1
  96. package/package.json +1 -1
  97. package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +0 -55
  98. package/dist/storage/rdf/Rdf3xTripleIndex.js +0 -1235
  99. 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
- matched: diff.missingFromPrimary.length === 0 && diff.extraInPrimary.length === 0,
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.index.joinPatterns(joinShape.patterns, joinShape.options);
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.rdf3xIndex.joinPatterns(joinShape.patterns, joinShape.options);
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 = isSemanticallyOrderedJoin(joinShape.options)
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
- matched: diff.missingFromPrimary.length === 0 && diff.extraInPrimary.length === 0,
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 || query.filters?.length || query.having?.length) {
1077
- return 'RDF-3X join shadow does not support local query filters or BIND yet';
1207
+ if (query.binds?.length) {
1208
+ return 'RDF-3X join shadow does not support BIND yet';
1078
1209
  }
1079
- if (query.groupBy?.length || query.aggregate || query.aggregates?.length) {
1080
- return 'RDF-3X join shadow does not support aggregate queries yet';
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
- for (const pattern of query.patterns) {
1083
- const joinPattern = rdf3xJoinPatternFor(pattern);
1084
- const unsupportedPattern = unsupportedRdf3xPatternReason(joinPattern.pattern);
1085
- if (unsupportedPattern) {
1086
- return unsupportedPattern;
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
- patterns: query.patterns.map(rdf3xJoinPatternFor),
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 rdf3xJoinPatternFor(pattern) {
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 (key === 'object' && isSupportedRdf3xNumericObjectRangePattern(value)) {
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 (key === 'object' && isSupportedRdf3xNumericObjectRangePattern(value)) {
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 isSupportedRdf3xNumericObjectRangePattern(value) {
1535
+ function isSupportedRdf3xObjectOperatorPattern(value) {
1175
1536
  if (value === null || typeof value !== 'object' || 'termType' in value) {
1176
1537
  return false;
1177
1538
  }
1178
- let hasRange = false;
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
- hasRange = true;
1185
- if (numericRangeValue(rangeValue) === undefined) {
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 hasRange;
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 isSemanticallyOrderedJoin(options) {
1209
- return Boolean(options?.orderBy && options.orderBy.length > 0);
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 testCase.expectedPlan.filter((label) => !matchesExpectedPlanLabel(label, testCase, metrics));
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 missingExpectedLocalQueryPlan(testCase, metrics) {
1315
- return testCase.expectedPlan.filter((label) => !matchesExpectedLocalQueryPlanLabel(label, metrics));
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(') && !planText.includes('\nSort');
1801
+ return (planText.includes('IndexGroupCountOrder(')
1802
+ || planText.includes('IndexGroupAggregateOrder('))
1803
+ && !planText.includes('\nSort');
1327
1804
  case 'limit':
1328
- return planText.includes('IndexGroupCountLimit') && !planText.includes('\nLimit');
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()