@undefineds.co/xpod 0.3.18 → 0.3.23

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 (115) 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/api/runs/PiAgentRuntimeDriver.d.ts +1 -0
  11. package/dist/api/runs/PiAgentRuntimeDriver.js +4 -1
  12. package/dist/api/runs/PiAgentRuntimeDriver.js.map +1 -1
  13. package/dist/components/components.jsonld +3 -0
  14. package/dist/components/context.jsonld +71 -32
  15. package/dist/http/SubgraphSparqlHttpHandler.d.ts +1 -0
  16. package/dist/http/SubgraphSparqlHttpHandler.js +27 -4
  17. package/dist/http/SubgraphSparqlHttpHandler.js.map +1 -1
  18. package/dist/http/SubgraphSparqlHttpHandler.jsonld +4 -0
  19. package/dist/http/vector/VectorHttpHandler.d.ts +5 -1
  20. package/dist/http/vector/VectorHttpHandler.js +5 -5
  21. package/dist/http/vector/VectorHttpHandler.js.map +1 -1
  22. package/dist/http/vector/VectorHttpHandler.jsonld +40 -28
  23. package/dist/index.d.ts +5 -2
  24. package/dist/index.js +9 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/runtime/Proxy.d.ts +3 -0
  27. package/dist/runtime/Proxy.js +31 -7
  28. package/dist/runtime/Proxy.js.map +1 -1
  29. package/dist/solidfs/LocalSolidFS.js +31 -124
  30. package/dist/solidfs/LocalSolidFS.js.map +1 -1
  31. package/dist/solidfs/SolidFsPathUtils.d.ts +13 -0
  32. package/dist/solidfs/SolidFsPathUtils.js +114 -0
  33. package/dist/solidfs/SolidFsPathUtils.js.map +1 -0
  34. package/dist/solidfs/SolidFsSyncJournal.d.ts +117 -0
  35. package/dist/solidfs/SolidFsSyncJournal.js +553 -0
  36. package/dist/solidfs/SolidFsSyncJournal.js.map +1 -0
  37. package/dist/solidfs/index.d.ts +1 -0
  38. package/dist/solidfs/index.js +1 -0
  39. package/dist/solidfs/index.js.map +1 -1
  40. package/dist/solidfs/types.d.ts +1 -0
  41. package/dist/solidfs/types.js.map +1 -1
  42. package/dist/storage/SparqlUpdateResourceStore.js +94 -33
  43. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  44. package/dist/storage/accessors/MixDataAccessor.d.ts +22 -5
  45. package/dist/storage/accessors/MixDataAccessor.js +376 -61
  46. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  47. package/dist/storage/accessors/MixDataAccessor.jsonld +73 -5
  48. package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js +32 -10
  49. package/dist/storage/accessors/QuadstoreSparqlDataAccessor.js.map +1 -1
  50. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js +28 -6
  51. package/dist/storage/accessors/QuintStoreSparqlDataAccessor.js.map +1 -1
  52. package/dist/storage/accessors/SolidRdfDataAccessor.d.ts +45 -0
  53. package/dist/storage/accessors/SolidRdfDataAccessor.js +277 -0
  54. package/dist/storage/accessors/SolidRdfDataAccessor.js.map +1 -0
  55. package/dist/storage/accessors/SolidRdfDataAccessor.jsonld +161 -0
  56. package/dist/storage/rdf/Rdf3xIndex.d.ts +122 -0
  57. package/dist/storage/rdf/Rdf3xIndex.js +2695 -0
  58. package/dist/storage/rdf/Rdf3xIndex.js.map +1 -0
  59. package/dist/storage/rdf/Rdf3xIndex.jsonld +528 -0
  60. package/dist/storage/rdf/Rdf3xSchema.d.ts +20 -0
  61. package/dist/storage/rdf/Rdf3xSchema.js +65 -0
  62. package/dist/storage/rdf/Rdf3xSchema.js.map +1 -0
  63. package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +10 -4
  64. package/dist/storage/rdf/RdfLocalQueryEngine.js +607 -127
  65. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -1
  66. package/dist/storage/rdf/RdfQuadIndex.d.ts +12 -1
  67. package/dist/storage/rdf/RdfQuadIndex.js +152 -22
  68. package/dist/storage/rdf/RdfQuadIndex.js.map +1 -1
  69. package/dist/storage/rdf/RdfQuadIndex.jsonld +36 -4
  70. package/dist/storage/rdf/RdfSparqlAdapter.d.ts +20 -2
  71. package/dist/storage/rdf/RdfSparqlAdapter.js +364 -40
  72. package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -1
  73. package/dist/storage/rdf/RdfSparqlAdapter.jsonld +60 -0
  74. package/dist/storage/rdf/RdfTermDictionary.d.ts +8 -0
  75. package/dist/storage/rdf/RdfTermDictionary.js +141 -70
  76. package/dist/storage/rdf/RdfTermDictionary.js.map +1 -1
  77. package/dist/storage/rdf/RdfTermDictionary.jsonld +24 -0
  78. package/dist/storage/rdf/RdfTextIndex.js +10 -3
  79. package/dist/storage/rdf/RdfTextIndex.js.map +1 -1
  80. package/dist/storage/rdf/SolidRdfEngine.d.ts +15 -6
  81. package/dist/storage/rdf/SolidRdfEngine.js +218 -25
  82. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  83. package/dist/storage/rdf/SolidRdfEngine.jsonld +70 -7
  84. package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +11 -7
  85. package/dist/storage/rdf/SolidRdfSparqlEngine.js +60 -47
  86. package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
  87. package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +9 -5
  88. package/dist/storage/rdf/index.d.ts +2 -2
  89. package/dist/storage/rdf/index.js +3 -3
  90. package/dist/storage/rdf/index.js.map +1 -1
  91. package/dist/storage/rdf/models-benchmark.d.ts +12 -1
  92. package/dist/storage/rdf/models-benchmark.js +549 -32
  93. package/dist/storage/rdf/models-benchmark.js.map +1 -1
  94. package/dist/storage/rdf/types.d.ts +81 -7
  95. package/dist/storage/rdf/types.js.map +1 -1
  96. package/dist/storage/sparql/CompatibilitySparqlEngine.d.ts +36 -0
  97. package/dist/storage/sparql/CompatibilitySparqlEngine.js +96 -0
  98. package/dist/storage/sparql/CompatibilitySparqlEngine.js.map +1 -0
  99. package/dist/storage/sparql/CompatibilitySparqlEngine.jsonld +123 -0
  100. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.d.ts +35 -0
  101. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js +112 -0
  102. package/dist/storage/sparql/CompatibilitySparqlEngineImpl.js.map +1 -0
  103. package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -36
  104. package/dist/storage/sparql/SubgraphQueryEngine.js +2 -115
  105. package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
  106. package/dist/storage/sparql/SubgraphQueryEngine.jsonld +1 -124
  107. package/dist/terminal/AclPermissionService.d.ts +2 -1
  108. package/dist/terminal/AclPermissionService.js +26 -3
  109. package/dist/terminal/AclPermissionService.js.map +1 -1
  110. package/dist/terminal/TerminalSessionManager.js +25 -3
  111. package/dist/terminal/TerminalSessionManager.js.map +1 -1
  112. package/package.json +1 -1
  113. package/dist/storage/rdf/Rdf3xTripleIndex.d.ts +0 -55
  114. package/dist/storage/rdf/Rdf3xTripleIndex.js +0 -1235
  115. package/dist/storage/rdf/Rdf3xTripleIndex.js.map +0 -1
@@ -50,19 +50,46 @@ class RdfLocalQueryEngine {
50
50
  const joinBasicAggregatePushdown = this.joinBasicAggregatePushdown(query, requiredPatterns, requiredFilters, aggregates);
51
51
  let groupedAggregatePushed = false;
52
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);
53
+ const distinctKeys = countPushdown.distinctKeys ?? [];
54
+ const distinctKey = distinctKeys.length === 1 ? distinctKeys[0] : undefined;
55
+ const distinctTupleKeys = distinctKeys.length > 1 ? distinctKeys : undefined;
56
+ const useRdf3xPrimary = this.canUseRdf3xPrimaryScan(countPushdown.pattern) && !distinctTupleKeys;
57
+ let rdf3xCount;
58
+ if (useRdf3xPrimary) {
59
+ const rdf3xPattern = toRdf3xTriplePattern(countPushdown.pattern);
60
+ if (distinctKey) {
61
+ rdf3xCount = this.rdf3xPrimaryIndex.countDistinct(rdf3xPattern, distinctKey);
62
+ }
63
+ else {
64
+ const rdf3xCountScan = this.rdf3xPrimaryIndex.scan(rdf3xPattern, { limit: 0 });
65
+ rdf3xCount = {
66
+ count: rdf3xCountScan.metrics.matchedRows,
67
+ metrics: rdf3xCountScan.metrics,
68
+ };
69
+ }
70
+ }
71
+ const countEstimate = !useRdf3xPrimary && distinctTupleKeys
72
+ ? this.index.countDistinctTuple(countPushdown.pattern, distinctTupleKeys)
73
+ : !useRdf3xPrimary && distinctKey
74
+ ? this.index.countDistinct(countPushdown.pattern, distinctKey)
75
+ : undefined;
76
+ const count = rdf3xCount?.count ?? countEstimate?.rows ?? this.index.count(countPushdown.pattern);
57
77
  const result = countLiteral(count);
58
78
  metrics.scannedRows = count;
59
79
  metrics.joinedRows = count;
60
80
  metrics.returnedRows = 1;
61
81
  metrics.durationMs = Date.now() - start;
62
- metrics.indexChoices.push('count');
82
+ metrics.indexChoices.push(useRdf3xPrimary ? rdf3xCount.metrics.indexChoice : 'count');
63
83
  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)');
84
+ if (rdf3xCount) {
85
+ metrics.plan.push(...storagePlanMarkers(rdf3xCount.metrics.queryPlan));
86
+ metrics.plan.push(`${distinctKey ? 'Rdf3xPrimaryCountDistinct' : 'Rdf3xPrimaryCount'}(${describePattern(requiredPatterns[0])})`);
87
+ metrics.plan.push(distinctKey ? 'Aggregate(count-distinct-rdf3x-primary)' : 'Aggregate(count-rdf3x-primary)');
88
+ }
89
+ else {
90
+ metrics.plan.push(`IndexCount(${describePattern(requiredPatterns[0])})`);
91
+ metrics.plan.push(distinctTupleKeys ? 'Aggregate(count-distinct-tuple-index)' : distinctKey ? 'Aggregate(count-distinct-index)' : 'Aggregate(count-index)');
92
+ }
66
93
  return {
67
94
  bindings: [{ [countPushdown.as]: result }],
68
95
  count,
@@ -70,9 +97,14 @@ class RdfLocalQueryEngine {
70
97
  };
71
98
  }
72
99
  if (joinCountPushdown) {
73
- const scan = this.index.countJoinPatterns(joinCountPushdown.patterns, {
74
- aggregates,
75
- });
100
+ const useRdf3xPrimary = this.canUseRdf3xPrimaryJoin(joinCountPushdown.patterns);
101
+ const scan = useRdf3xPrimary
102
+ ? this.rdf3xPrimaryIndex.countJoinPatterns(joinCountPushdown.patterns, {
103
+ aggregates,
104
+ })
105
+ : this.index.countJoinPatterns(joinCountPushdown.patterns, {
106
+ aggregates,
107
+ });
76
108
  const firstAggregate = aggregates[0];
77
109
  const firstCount = firstAggregate ? Number(scan.bindings[0]?.[firstAggregate.as]?.value ?? 0) : 0;
78
110
  metrics.scannedRows = scan.metrics.matchedRows;
@@ -81,11 +113,11 @@ class RdfLocalQueryEngine {
81
113
  metrics.durationMs = Date.now() - start;
82
114
  metrics.indexChoices.push(scan.metrics.indexChoice);
83
115
  metrics.filtersPushedDown += joinCountPushdown.pushedDownFilters;
84
- if (joinCountPushdown.reorderPlan) {
116
+ if (!useRdf3xPrimary && joinCountPushdown.reorderPlan) {
85
117
  metrics.plan.push(joinCountPushdown.reorderPlan);
86
118
  }
87
119
  metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
88
- metrics.plan.push(`IndexJoinCount(${joinCountPushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
120
+ metrics.plan.push(`${useRdf3xPrimary ? 'Rdf3xPrimaryJoinCount' : 'IndexJoinCount'}(${joinCountPushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
89
121
  metrics.plan.push(aggregatePlan(aggregates, false));
90
122
  metrics.plan.push(aggregates.some((aggregate) => aggregate.distinct)
91
123
  ? 'Aggregate(join-count-distinct-index)'
@@ -97,20 +129,25 @@ class RdfLocalQueryEngine {
97
129
  };
98
130
  }
99
131
  if (joinBasicAggregatePushdown) {
100
- const scan = this.index.aggregateJoinPatterns(joinBasicAggregatePushdown.patterns, {
101
- aggregates,
102
- });
132
+ const useRdf3xPrimary = this.canUseRdf3xPrimaryJoin(joinBasicAggregatePushdown.patterns);
133
+ const scan = useRdf3xPrimary
134
+ ? this.rdf3xPrimaryIndex.aggregateJoinPatterns(joinBasicAggregatePushdown.patterns, {
135
+ aggregates,
136
+ })
137
+ : this.index.aggregateJoinPatterns(joinBasicAggregatePushdown.patterns, {
138
+ aggregates,
139
+ });
103
140
  metrics.scannedRows = scan.metrics.matchedRows;
104
141
  metrics.joinedRows = scan.metrics.matchedRows;
105
142
  metrics.returnedRows = scan.bindings.length;
106
143
  metrics.durationMs = Date.now() - start;
107
144
  metrics.indexChoices.push(scan.metrics.indexChoice);
108
145
  metrics.filtersPushedDown += joinBasicAggregatePushdown.pushedDownFilters;
109
- if (joinBasicAggregatePushdown.reorderPlan) {
146
+ if (!useRdf3xPrimary && joinBasicAggregatePushdown.reorderPlan) {
110
147
  metrics.plan.push(joinBasicAggregatePushdown.reorderPlan);
111
148
  }
112
149
  metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
113
- metrics.plan.push(`IndexJoinAggregate(${joinBasicAggregatePushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
150
+ metrics.plan.push(`${useRdf3xPrimary ? 'Rdf3xPrimaryJoinAggregate' : 'IndexJoinAggregate'}(${joinBasicAggregatePushdown.patterns.map((source) => describePatternSource(source)).join('|')})`);
114
151
  metrics.plan.push(aggregatePlan(aggregates, false));
115
152
  metrics.plan.push(aggregates.length > 1
116
153
  ? 'Aggregate(join-basic-multi-index)'
@@ -123,31 +160,35 @@ class RdfLocalQueryEngine {
123
160
  const remainingSources = buildRequiredSources(requiredPatterns, query);
124
161
  const requiredBgpPushdown = this.requiredBgpPushdown(query, requiredPatterns, requiredFilters);
125
162
  if (groupAggregatePushdown) {
163
+ const rdf3xGroupAggregatePatterns = groupAggregatePushdown.countOnly
164
+ ? groupAggregatePushdown.patterns
165
+ : stripRdf3xNumericAggregateGuards(groupAggregatePushdown.patterns, aggregates);
166
+ const useRdf3xPrimary = this.canUseRdf3xPrimaryJoin(rdf3xGroupAggregatePatterns);
167
+ const groupOptions = {
168
+ groupBy: query.groupBy ?? [],
169
+ aggregates,
170
+ ...(groupAggregatePushdown.having ? { having: groupAggregatePushdown.having } : {}),
171
+ ...(groupAggregatePushdown.orderPushed ? { orderBy: query.orderBy } : {}),
172
+ ...(groupAggregatePushdown.paginationPushed && query.limit !== undefined ? { limit: Math.max(0, query.limit) } : {}),
173
+ ...(groupAggregatePushdown.paginationPushed && query.offset !== undefined ? { offset: Math.max(0, query.offset) } : {}),
174
+ };
126
175
  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';
176
+ ? useRdf3xPrimary
177
+ ? this.rdf3xPrimaryIndex.groupCountJoinPatterns(rdf3xGroupAggregatePatterns, groupOptions)
178
+ : this.index.groupCountJoinPatterns(groupAggregatePushdown.patterns, groupOptions)
179
+ : useRdf3xPrimary
180
+ ? this.rdf3xPrimaryIndex.groupAggregateJoinPatterns(rdf3xGroupAggregatePatterns, groupOptions)
181
+ : this.index.groupAggregateJoinPatterns(groupAggregatePushdown.patterns, groupOptions);
182
+ const groupPlanPrefix = useRdf3xPrimary
183
+ ? groupAggregatePushdown.countOnly ? 'Rdf3xPrimaryGroupCount' : 'Rdf3xPrimaryGroupAggregate'
184
+ : groupAggregatePushdown.countOnly ? 'IndexGroupCount' : 'IndexGroupAggregate';
144
185
  bindings = scan.bindings;
145
186
  metrics.scannedRows += scan.metrics.matchedRows;
146
187
  metrics.joinedRows = scan.metrics.matchedRows;
147
188
  metrics.indexChoices.push(scan.metrics.indexChoice);
148
189
  metrics.filtersPushedDown += groupAggregatePushdown.pushedDownFilters;
149
190
  metrics.filtersPushedDown += groupAggregatePushdown.pushedDownHaving;
150
- if (groupAggregatePushdown.reorderPlan) {
191
+ if (!useRdf3xPrimary && groupAggregatePushdown.reorderPlan) {
151
192
  metrics.plan.push(groupAggregatePushdown.reorderPlan);
152
193
  }
153
194
  metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
@@ -175,6 +216,7 @@ class RdfLocalQueryEngine {
175
216
  const scanOptions = {
176
217
  ...(requiredBgpPushdown.project ? { project: requiredBgpPushdown.project } : {}),
177
218
  ...(requiredBgpPushdown.distinctPushed ? { distinct: true } : {}),
219
+ ...(requiredBgpPushdown.values ? { values: requiredBgpPushdown.values } : {}),
178
220
  ...(requiredBgpPushdown.orderPushed ? { orderBy: query.orderBy } : {}),
179
221
  ...(requiredBgpPushdown.paginationPushed && query.limit !== undefined ? { limit: Math.max(0, query.limit) } : {}),
180
222
  ...(requiredBgpPushdown.paginationPushed && query.offset !== undefined ? { offset: Math.max(0, query.offset) } : {}),
@@ -343,14 +385,17 @@ class RdfLocalQueryEngine {
343
385
  const sourceVariables = variablesInRequiredSource(source);
344
386
  const connected = sourceVariables.length === 0
345
387
  || sourceVariables.some((variableName) => boundVariables.has(variableName));
388
+ const estimate = this.estimateSource(source, bindings, filters, metrics);
346
389
  return {
347
390
  index,
348
391
  disconnectedPenalty: hasBoundVariables && !connected ? 1 : 0,
349
- estimatedRows: this.estimateSourceRows(source, bindings, filters, metrics),
392
+ estimatedRows: estimate.rows,
393
+ estimatedCostRows: estimate.costRows,
350
394
  rank: this.sourceRank(source, sampleBinding),
351
395
  };
352
396
  });
353
397
  choices.sort((left, right) => (left.disconnectedPenalty - right.disconnectedPenalty
398
+ || left.estimatedCostRows - right.estimatedCostRows
354
399
  || left.estimatedRows - right.estimatedRows
355
400
  || left.rank - right.rank
356
401
  || left.index - right.index));
@@ -424,33 +469,33 @@ class RdfLocalQueryEngine {
424
469
  })
425
470
  .map((slot) => ({ key: slot.key, variable: slot.value.variable }));
426
471
  }
427
- estimateSourceRows(source, bindings, filters, metrics) {
472
+ estimateSource(source, bindings, filters, metrics) {
428
473
  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);
474
+ const rows = source.tupleValues
475
+ ? this.estimateTuplePatternRows(source, bindings, filters, metrics)
476
+ : this.estimatePatternRows(source.pattern, bindings, filters, metrics);
477
+ return { rows, costRows: rows };
433
478
  }
434
479
  if (source.kind === 'values') {
435
- return source.source.rows.length * Math.max(1, bindings.length);
480
+ const rows = source.source.rows.length * Math.max(1, bindings.length);
481
+ return { rows, costRows: rows };
436
482
  }
437
483
  const sample = (bindings.length > 0 ? bindings : [{}]).slice(0, PLANNER_SAMPLE_BINDINGS);
438
484
  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);
485
+ return this.estimateUnboundSearchSource(source, bindings, metrics);
444
486
  }
445
487
  const sourceVariable = source.pattern.source;
446
488
  const boundSourceEstimate = sourceVariable && this.canUseBoundSourceSearch(sample, source, sourceVariable)
447
489
  ? this.estimateSearchRowsByBoundSource(source, sample, metrics)
448
490
  : undefined;
449
491
  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;
492
+ const rows = bindings.length > sample.length && sample.length > 0
493
+ ? Math.ceil(boundSourceEstimate * (bindings.length / sample.length))
494
+ : boundSourceEstimate;
495
+ return { rows, costRows: rows };
496
+ }
497
+ if (hasSearchWindow(source)) {
498
+ return this.estimateUnboundSearchSource(source, bindings, metrics);
454
499
  }
455
500
  const results = source.kind === 'text'
456
501
  ? this.textSearchResults(source)
@@ -467,9 +512,30 @@ class RdfLocalQueryEngine {
467
512
  }
468
513
  }
469
514
  if (bindings.length > sample.length && sample.length > 0) {
470
- return Math.ceil(rows * (bindings.length / sample.length));
471
- }
472
- return rows;
515
+ const estimatedRows = Math.ceil(rows * (bindings.length / sample.length));
516
+ return { rows: estimatedRows, costRows: estimatedRows };
517
+ }
518
+ return { rows, costRows: rows };
519
+ }
520
+ estimateUnboundSearchSource(source, bindings, metrics) {
521
+ metrics.searchCardinalityEstimates = (metrics.searchCardinalityEstimates ?? 0) + 1;
522
+ const rows = source.kind === 'text'
523
+ ? this.estimateTextSearchRows(source)
524
+ : this.estimateVectorSearchRows(source);
525
+ const bindingCount = Math.max(1, bindings.length);
526
+ if (!hasSearchWindow(source)) {
527
+ const estimatedRows = rows * bindingCount;
528
+ return { rows: estimatedRows, costRows: estimatedRows };
529
+ }
530
+ metrics.searchCardinalityEstimates = (metrics.searchCardinalityEstimates ?? 0) + 1;
531
+ const candidateRows = source.kind === 'text'
532
+ ? this.estimateTextSearchRows(source, undefined, false)
533
+ : this.estimateVectorSearchRows(source, undefined, false);
534
+ const estimatedRows = rows * bindingCount;
535
+ return {
536
+ rows: estimatedRows,
537
+ costRows: candidateRows + estimatedRows,
538
+ };
473
539
  }
474
540
  estimateTuplePatternRows(source, bindings, filters, metrics) {
475
541
  const sample = (bindings.length > 0 ? bindings : [{}]).slice(0, PLANNER_SAMPLE_BINDINGS);
@@ -530,18 +596,18 @@ class RdfLocalQueryEngine {
530
596
  }
531
597
  return sawBoundSource ? rows : undefined;
532
598
  }
533
- estimateTextSearchRows(source, exactSource) {
599
+ estimateTextSearchRows(source, exactSource, includeWindow = true) {
534
600
  if (!this.textIndex) {
535
601
  throw new Error('RdfLocalQuery textSearch requires a configured RdfTextIndex');
536
602
  }
537
- const options = this.textSearchOptions(source.pattern, exactSource);
603
+ const options = this.textSearchOptions(source.pattern, exactSource, includeWindow);
538
604
  return options ? this.textIndex.estimateSearchCardinality(options).rows : 0;
539
605
  }
540
- estimateVectorSearchRows(source, exactSource) {
606
+ estimateVectorSearchRows(source, exactSource, includeWindow = true) {
541
607
  if (!this.vectorIndex) {
542
608
  throw new Error('RdfLocalQuery vectorSearch requires a configured RdfVectorIndex');
543
609
  }
544
- const options = this.vectorSearchOptions(source.pattern, exactSource);
610
+ const options = this.vectorSearchOptions(source.pattern, exactSource, includeWindow);
545
611
  return options ? this.vectorIndex.estimateSearchCardinality(options).rows : 0;
546
612
  }
547
613
  boundVariables(bindings) {
@@ -589,15 +655,16 @@ class RdfLocalQueryEngine {
589
655
  joinRequiredSource(input, source, filters, metrics, singleScanPushdown) {
590
656
  switch (source.kind) {
591
657
  case 'pattern': {
592
- const bindings = this.joinPattern(input, source, filters, metrics, false, singleScanPushdown?.options);
593
- metrics.plan.push(`IndexScan(${describePattern(source.pattern)})`);
658
+ const result = this.joinPattern(input, source, filters, metrics, false, singleScanPushdown?.options);
659
+ const scanPlan = requiredSourceScanPlan(result.scanBackend);
660
+ metrics.plan.push(`${scanPlan}(${describePattern(source.pattern)})`);
594
661
  if (singleScanPushdown?.orderPushed) {
595
- metrics.plan.push(`IndexOrder(${describeScanOrder(singleScanPushdown.options)})`);
662
+ metrics.plan.push(`${scanPlanOrder(scanPlan)}(${describeScanOrder(singleScanPushdown.options)})`);
596
663
  }
597
664
  if (singleScanPushdown?.paginationPushed) {
598
- metrics.plan.push('IndexLimit');
665
+ metrics.plan.push(scanPlanLimit(scanPlan));
599
666
  }
600
- return bindings;
667
+ return result.bindings;
601
668
  }
602
669
  case 'text': {
603
670
  const bindings = this.joinTextSearch(input, source, metrics);
@@ -636,10 +703,18 @@ class RdfLocalQueryEngine {
636
703
  return [binding];
637
704
  }
638
705
  }
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];
706
+ const grouped = (optionalGroup.values?.length ?? 0) === 0
707
+ ? this.joinPatternGroupRdf3x(matches, optionalGroup.patterns, optionalFilters, metrics, true)
708
+ : undefined;
709
+ if (grouped) {
710
+ matches = grouped.bindings;
711
+ }
712
+ else {
713
+ for (const pattern of optionalGroup.patterns) {
714
+ matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, optionalFilters, metrics, true).bindings;
715
+ if (matches.length === 0) {
716
+ return [binding];
717
+ }
643
718
  }
644
719
  }
645
720
  for (const unionGroup of optionalGroup.unions ?? []) {
@@ -702,8 +777,26 @@ class RdfLocalQueryEngine {
702
777
  if (matches.length === 0) {
703
778
  continue;
704
779
  }
705
- for (const pattern of branch.patterns) {
706
- matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, branchFilters, metrics, false);
780
+ const grouped = (branch.values?.length ?? 0) === 0
781
+ ? this.joinPatternGroupRdf3x(matches, branch.patterns, branchFilters, metrics, false)
782
+ : undefined;
783
+ if (grouped) {
784
+ matches = grouped.bindings;
785
+ }
786
+ else {
787
+ for (const pattern of branch.patterns) {
788
+ matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, branchFilters, metrics, false).bindings;
789
+ if (matches.length === 0) {
790
+ break;
791
+ }
792
+ }
793
+ }
794
+ if (matches.length === 0) {
795
+ continue;
796
+ }
797
+ for (const unionGroup of branch.unions ?? []) {
798
+ matches = this.joinUnionGroup(matches, unionGroup.branches, branchFilters, metrics);
799
+ metrics.plan.push(`UnionNested(${unionGroup.branches.map((nestedBranch) => nestedBranch.patterns.map(describePattern).join(',')).join('|')})`);
707
800
  if (matches.length === 0) {
708
801
  break;
709
802
  }
@@ -757,10 +850,18 @@ class RdfLocalQueryEngine {
757
850
  for (const binding of input) {
758
851
  let matches = [binding];
759
852
  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;
853
+ const grouped = (minusGroup.values?.length ?? 0) === 0
854
+ ? this.joinPatternGroupRdf3x(matches, minusGroup.patterns, filters, metrics, false)
855
+ : undefined;
856
+ if (grouped) {
857
+ matches = grouped.bindings;
858
+ }
859
+ else {
860
+ for (const pattern of minusGroup.patterns) {
861
+ matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, filters, metrics, false).bindings;
862
+ if (matches.length === 0) {
863
+ break;
864
+ }
764
865
  }
765
866
  }
766
867
  if (matches.length > 0) {
@@ -798,10 +899,18 @@ class RdfLocalQueryEngine {
798
899
  for (const binding of input) {
799
900
  let matches = [binding];
800
901
  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;
902
+ const grouped = (existsGroup.values?.length ?? 0) === 0
903
+ ? this.joinPatternGroupRdf3x(matches, existsGroup.patterns, filters, metrics, false)
904
+ : undefined;
905
+ if (grouped) {
906
+ matches = grouped.bindings;
907
+ }
908
+ else {
909
+ for (const pattern of existsGroup.patterns) {
910
+ matches = this.joinPattern(matches, { kind: 'pattern', pattern, originalIndex: -1 }, filters, metrics, false).bindings;
911
+ if (matches.length === 0) {
912
+ break;
913
+ }
805
914
  }
806
915
  }
807
916
  if (matches.length > 0) {
@@ -833,8 +942,60 @@ class RdfLocalQueryEngine {
833
942
  }
834
943
  return output;
835
944
  }
945
+ joinPatternGroupRdf3x(input, patterns, filters, metrics, optional) {
946
+ if (!this.rdf3xPrimaryIndex || patterns.length < 2) {
947
+ return undefined;
948
+ }
949
+ const compiledByBinding = input.map((binding) => {
950
+ const compiled = patterns.map((pattern) => this.compileJoinPatternForBinding(pattern, binding, filters));
951
+ return {
952
+ binding,
953
+ patterns: compiled,
954
+ };
955
+ });
956
+ if (compiledByBinding.some((entry) => (entry.patterns.some((pattern) => !pattern)
957
+ || !this.canUseRdf3xPrimaryJoin(entry.patterns)))) {
958
+ return undefined;
959
+ }
960
+ const output = [];
961
+ for (const entry of compiledByBinding) {
962
+ const compiled = entry.patterns;
963
+ const scan = this.rdf3xPrimaryIndex.joinPatterns(compiled);
964
+ metrics.scannedRows += scan.metrics.matchedRows;
965
+ metrics.indexChoices.push(scan.metrics.indexChoice);
966
+ metrics.filtersPushedDown += uniqueNumbers(compiled.flatMap((pattern) => pattern.pattern.pushedDownFilterIndexes)).length;
967
+ metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
968
+ const remainingFilters = filtersWithoutIndexes(filters, uniqueNumbers(compiled.flatMap((pattern) => pattern.pattern.pushedDownFilterIndexes)));
969
+ const before = output.length;
970
+ for (const row of scan.bindings) {
971
+ const next = mergeBindingRows(entry.binding, row);
972
+ if (next && this.matchesNewlyBoundFilters(next, entry.binding, remainingFilters)) {
973
+ output.push(next);
974
+ }
975
+ }
976
+ if (optional && output.length === before) {
977
+ output.push(entry.binding);
978
+ }
979
+ }
980
+ return {
981
+ bindings: output,
982
+ scanBackend: 'rdf3x',
983
+ };
984
+ }
985
+ compileJoinPatternForBinding(pattern, binding, filters) {
986
+ const variables = {};
987
+ for (const key of TERM_KEYS) {
988
+ const value = pattern[key];
989
+ if (isVariable(value) && !binding[value.variable]) {
990
+ variables[key] = value.variable;
991
+ }
992
+ }
993
+ const compiled = this.compilePattern(pattern, binding, filters);
994
+ return compiled ? { pattern: compiled, variables } : undefined;
995
+ }
836
996
  joinPattern(input, source, filters, metrics, optional, scanOptions) {
837
997
  const output = [];
998
+ const backends = new Set();
838
999
  const { pattern } = source;
839
1000
  for (const binding of input) {
840
1001
  const compiled = this.compilePattern(pattern, binding, filters);
@@ -845,9 +1006,8 @@ class RdfLocalQueryEngine {
845
1006
  continue;
846
1007
  }
847
1008
  const tupleValues = this.tupleValuesForBinding(source, binding);
848
- const scan = tupleValues
849
- ? this.index.scanWithTupleConstraints(compiled, tupleValues, scanOptions)
850
- : this.index.scan(compiled, scanOptions);
1009
+ const scan = this.scanCompiledPattern(compiled, tupleValues, scanOptions);
1010
+ backends.add(scan.metrics.engine === 'solid-rdf3x' ? 'rdf3x' : 'index');
851
1011
  metrics.scannedRows += scan.metrics.matchedRows;
852
1012
  metrics.indexChoices.push(scan.metrics.indexChoice);
853
1013
  metrics.filtersPushedDown += compiled.pushedDownFilters;
@@ -860,11 +1020,32 @@ class RdfLocalQueryEngine {
860
1020
  }
861
1021
  }
862
1022
  }
863
- return output;
1023
+ return {
1024
+ bindings: output,
1025
+ scanBackend: backends.size === 0
1026
+ ? 'none'
1027
+ : backends.size === 1
1028
+ ? [...backends][0]
1029
+ : 'mixed',
1030
+ };
1031
+ }
1032
+ scanCompiledPattern(compiled, tupleValues, scanOptions) {
1033
+ if (this.canUseRdf3xPrimaryScan(compiled, scanOptions)) {
1034
+ const rdf3xPattern = toRdf3xTriplePattern(compiled);
1035
+ return tupleValues
1036
+ ? this.rdf3xPrimaryIndex.scanWithTupleConstraints(rdf3xPattern, tupleValues, toRdf3xScanOptions(scanOptions))
1037
+ : this.rdf3xPrimaryIndex.scan(rdf3xPattern, toRdf3xScanOptions(scanOptions));
1038
+ }
1039
+ return tupleValues
1040
+ ? this.index.scanWithTupleConstraints(compiled, tupleValues, scanOptions)
1041
+ : this.index.scan(compiled, scanOptions);
1042
+ }
1043
+ canUseRdf3xPrimaryScan(pattern, scanOptions) {
1044
+ return Boolean(this.rdf3xPrimaryIndex)
1045
+ && isRdf3xCompatiblePattern(pattern);
864
1046
  }
865
1047
  requiredBgpPushdown(query, requiredPatterns, filters) {
866
1048
  if (requiredPatterns.length < 1
867
- || (query.values?.length ?? 0) > 0
868
1049
  || (query.textSearch?.length ?? 0) > 0
869
1050
  || (query.vectorSearch?.length ?? 0) > 0
870
1051
  || (query.unions?.length ?? 0) > 0
@@ -881,6 +1062,12 @@ class RdfLocalQueryEngine {
881
1062
  if (!this.canPushRequiredBgpFilters(requiredPatterns, filters)) {
882
1063
  return undefined;
883
1064
  }
1065
+ const values = query.values?.length
1066
+ ? this.requiredBgpValuesPushdown(query.values, requiredPatterns)
1067
+ : undefined;
1068
+ if ((query.values?.length ?? 0) > 0 && !values) {
1069
+ return undefined;
1070
+ }
884
1071
  const distinctProject = query.distinct
885
1072
  ? this.requiredBgpDistinctProject(query, requiredPatterns, filters)
886
1073
  : undefined;
@@ -899,9 +1086,13 @@ class RdfLocalQueryEngine {
899
1086
  if (compiled.some((entry) => !entry)) {
900
1087
  return undefined;
901
1088
  }
1089
+ if (values && !this.canUseRdf3xPrimaryJoin(compiled)) {
1090
+ return undefined;
1091
+ }
902
1092
  const reordered = this.reorderJoinPatterns(requiredPatterns, compiled, filters);
903
1093
  return {
904
1094
  patterns: reordered.patterns,
1095
+ ...(values ? { values } : {}),
905
1096
  ...(reordered.reorderPlan ? { reorderPlan: reordered.reorderPlan } : {}),
906
1097
  orderPushed,
907
1098
  paginationPushed: query.limit !== undefined || query.offset !== undefined,
@@ -910,6 +1101,21 @@ class RdfLocalQueryEngine {
910
1101
  pushedDownFilters: filters.length,
911
1102
  };
912
1103
  }
1104
+ requiredBgpValuesPushdown(values, requiredPatterns) {
1105
+ const visibleVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
1106
+ for (const source of values) {
1107
+ if (source.variables.length === 0 || new Set(source.variables).size !== source.variables.length) {
1108
+ return undefined;
1109
+ }
1110
+ if (source.variables.some((variableName) => !visibleVariables.has(variableName))) {
1111
+ return undefined;
1112
+ }
1113
+ if (source.rows.some((row) => source.variables.some((variableName) => !row[variableName]))) {
1114
+ return undefined;
1115
+ }
1116
+ }
1117
+ return values;
1118
+ }
913
1119
  requiredBgpDistinctProject(query, requiredPatterns, filters) {
914
1120
  const visibleVariables = new Set(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
915
1121
  const projectedVariables = uniqueStrings(query.select && query.select.length > 0
@@ -1170,18 +1376,18 @@ class RdfLocalQueryEngine {
1170
1376
  }
1171
1377
  return results;
1172
1378
  }
1173
- textSearchOptions(pattern, exactSource) {
1379
+ textSearchOptions(pattern, exactSource, includeWindow = true) {
1174
1380
  return {
1175
1381
  query: pattern.query,
1176
1382
  source: exactSource,
1177
1383
  workspace: pattern.scope?.workspace,
1178
1384
  sourcePrefix: pattern.scope?.sourcePrefix,
1179
- limit: pattern.limit,
1180
- offset: pattern.offset,
1385
+ limit: includeWindow ? pattern.limit : undefined,
1386
+ offset: includeWindow ? pattern.offset : undefined,
1181
1387
  orderBy: pattern.orderBy,
1182
1388
  };
1183
1389
  }
1184
- vectorSearchOptions(pattern, exactSource) {
1390
+ vectorSearchOptions(pattern, exactSource, includeWindow = true) {
1185
1391
  return {
1186
1392
  embedding: pattern.embedding,
1187
1393
  metric: pattern.metric,
@@ -1189,8 +1395,8 @@ class RdfLocalQueryEngine {
1189
1395
  source: exactSource,
1190
1396
  workspace: pattern.scope?.workspace,
1191
1397
  sourcePrefix: pattern.scope?.sourcePrefix,
1192
- limit: pattern.limit,
1193
- offset: pattern.offset,
1398
+ limit: includeWindow ? pattern.limit : undefined,
1399
+ offset: includeWindow ? pattern.offset : undefined,
1194
1400
  threshold: pattern.threshold,
1195
1401
  orderBy: pattern.orderBy,
1196
1402
  };
@@ -1230,6 +1436,19 @@ class RdfLocalQueryEngine {
1230
1436
  const value = this.evaluateBindExpression(expression.expression, binding);
1231
1437
  return value ? n3_1.DataFactory.literal(value.value.toLocaleUpperCase('en-US')) : undefined;
1232
1438
  }
1439
+ case 'coalesce': {
1440
+ for (const item of expression.expressions) {
1441
+ const value = this.evaluateBindExpression(item, binding);
1442
+ if (value) {
1443
+ return value;
1444
+ }
1445
+ }
1446
+ return undefined;
1447
+ }
1448
+ case 'if':
1449
+ return this.matchesFilters(binding, expression.condition)
1450
+ ? this.evaluateBindExpression(expression.then, binding)
1451
+ : this.evaluateBindExpression(expression.else, binding);
1233
1452
  case 'substring': {
1234
1453
  const value = this.evaluateBindExpression(expression.expression, binding);
1235
1454
  const startTerm = this.evaluateBindExpression(expression.start, binding);
@@ -1261,6 +1480,22 @@ class RdfLocalQueryEngine {
1261
1480
  return undefined;
1262
1481
  }
1263
1482
  }
1483
+ case 'strdt': {
1484
+ const lexical = this.evaluateBindExpression(expression.lexical, binding);
1485
+ const datatype = this.evaluateBindExpression(expression.datatype, binding);
1486
+ if (!lexical || !datatype || datatype.termType !== 'NamedNode') {
1487
+ return undefined;
1488
+ }
1489
+ return n3_1.DataFactory.literal(lexical.value, n3_1.DataFactory.namedNode(datatype.value));
1490
+ }
1491
+ case 'strlang': {
1492
+ const lexical = this.evaluateBindExpression(expression.lexical, binding);
1493
+ const language = this.evaluateBindExpression(expression.language, binding);
1494
+ if (!lexical || !language) {
1495
+ return undefined;
1496
+ }
1497
+ return n3_1.DataFactory.literal(lexical.value, language.value);
1498
+ }
1264
1499
  default: {
1265
1500
  const exhaustive = expression;
1266
1501
  throw new Error(`Unsupported RDF local BIND expression: ${JSON.stringify(exhaustive)}`);
@@ -1329,10 +1564,10 @@ class RdfLocalQueryEngine {
1329
1564
  if (aggregate.variable && !variablesInPattern(pattern).includes(aggregate.variable)) {
1330
1565
  return undefined;
1331
1566
  }
1332
- const distinctKey = aggregate.distinct
1333
- ? this.distinctCountKey(pattern, aggregate.variable)
1567
+ const distinctKeys = aggregate.distinct
1568
+ ? this.distinctCountKeys(pattern, aggregate.variable, aggregate.distinctVariables)
1334
1569
  : undefined;
1335
- if (aggregate.distinct && !distinctKey) {
1570
+ if (aggregate.distinct && (!distinctKeys || distinctKeys.length === 0)) {
1336
1571
  return undefined;
1337
1572
  }
1338
1573
  if (!this.canPushAllFiltersForPattern(pattern, filters)) {
@@ -1343,7 +1578,7 @@ class RdfLocalQueryEngine {
1343
1578
  ? {
1344
1579
  as: aggregate.as,
1345
1580
  pattern: compiled,
1346
- distinctKey,
1581
+ distinctKeys,
1347
1582
  pushedDownFilters: compiled.pushedDownFilters,
1348
1583
  }
1349
1584
  : undefined;
@@ -1540,15 +1775,30 @@ class RdfLocalQueryEngine {
1540
1775
  }
1541
1776
  return compiled;
1542
1777
  }
1543
- distinctCountKey(pattern, variableName) {
1544
- if (!variableName) {
1545
- return undefined;
1778
+ distinctCountKeys(pattern, variableName, distinctVariables) {
1779
+ if (variableName) {
1780
+ const keys = TERM_KEYS.filter((key) => {
1781
+ const value = pattern[key];
1782
+ return isVariable(value) && value.variable === variableName;
1783
+ });
1784
+ return keys.length === 1 ? keys : undefined;
1785
+ }
1786
+ if (distinctVariables) {
1787
+ const keys = [];
1788
+ for (const distinctVariable of distinctVariables) {
1789
+ const key = termKeyForVariable(pattern, distinctVariable);
1790
+ if (!key) {
1791
+ return undefined;
1792
+ }
1793
+ keys.push(key);
1794
+ }
1795
+ return uniquePatternKeys(keys);
1546
1796
  }
1547
1797
  const keys = TERM_KEYS.filter((key) => {
1548
1798
  const value = pattern[key];
1549
- return isVariable(value) && value.variable === variableName;
1799
+ return isVariable(value);
1550
1800
  });
1551
- return keys.length === 1 ? keys[0] : undefined;
1801
+ return uniquePatternKeys(keys);
1552
1802
  }
1553
1803
  scanOrderForPattern(pattern, orderBy) {
1554
1804
  if (orderBy.length === 0) {
@@ -1647,12 +1897,12 @@ class RdfLocalQueryEngine {
1647
1897
  }
1648
1898
  return next;
1649
1899
  }
1650
- countBindings(bindings, variable, distinct) {
1900
+ countBindings(bindings, variable, distinct, distinctVariables) {
1651
1901
  if (!distinct) {
1652
1902
  return variable ? bindings.filter((binding) => binding[variable]).length : bindings.length;
1653
1903
  }
1654
1904
  if (!variable) {
1655
- return new Set(bindings.map((binding) => bindingKey(binding))).size;
1905
+ return new Set(bindings.map((binding) => bindingKey(binding, distinctVariables))).size;
1656
1906
  }
1657
1907
  return new Set(bindings
1658
1908
  .map((binding) => binding[variable])
@@ -1664,7 +1914,7 @@ class RdfLocalQueryEngine {
1664
1914
  let firstCount = 0;
1665
1915
  aggregates.forEach((aggregate, index) => {
1666
1916
  const count = aggregate.type === 'count'
1667
- ? this.countBindings(bindings, aggregate.variable, aggregate.distinct)
1917
+ ? this.countBindings(bindings, aggregate.variable, aggregate.distinct, aggregate.distinctVariables)
1668
1918
  : 0;
1669
1919
  if (index === 0) {
1670
1920
  firstCount = count;
@@ -1710,7 +1960,7 @@ class RdfLocalQueryEngine {
1710
1960
  }
1711
1961
  aggregateLiteral(bindings, aggregate) {
1712
1962
  if (aggregate.type === 'count') {
1713
- return countLiteral(this.countBindings(bindings, aggregate.variable, aggregate.distinct));
1963
+ return countLiteral(this.countBindings(bindings, aggregate.variable, aggregate.distinct, aggregate.distinctVariables));
1714
1964
  }
1715
1965
  const values = this.numericAggregateValues(bindings, aggregate.variable, aggregate.distinct);
1716
1966
  if (values.length === 0) {
@@ -1927,24 +2177,46 @@ class RdfLocalQueryEngine {
1927
2177
  const text = filterStringValue(value, comparisonValue);
1928
2178
  return typeof filter.value === 'string' && text.startsWith(filter.value);
1929
2179
  }
2180
+ case '$notStartsWith': {
2181
+ const text = filterStringValue(value, comparisonValue);
2182
+ return typeof filter.value === 'string' && !text.startsWith(filter.value);
2183
+ }
1930
2184
  case '$contains': {
1931
2185
  const text = filterStringValue(value, comparisonValue);
1932
2186
  return typeof filter.value === 'string' && text.includes(filter.value);
1933
2187
  }
2188
+ case '$notContains': {
2189
+ const text = filterStringValue(value, comparisonValue);
2190
+ return typeof filter.value === 'string' && !text.includes(filter.value);
2191
+ }
1934
2192
  case '$endsWith': {
1935
2193
  const text = filterStringValue(value, comparisonValue);
1936
2194
  return typeof filter.value === 'string' && text.endsWith(filter.value);
1937
2195
  }
2196
+ case '$notEndsWith': {
2197
+ const text = filterStringValue(value, comparisonValue);
2198
+ return typeof filter.value === 'string' && !text.endsWith(filter.value);
2199
+ }
1938
2200
  case '$regex': {
1939
2201
  const text = filterStringValue(value, comparisonValue);
1940
2202
  return typeof filter.value === 'string' && new RegExp(filter.value, filter.flags).test(text);
1941
2203
  }
2204
+ case '$notRegex': {
2205
+ const text = filterStringValue(value, comparisonValue);
2206
+ return typeof filter.value === 'string' && !new RegExp(filter.value, filter.flags).test(text);
2207
+ }
1942
2208
  case '$termType':
1943
2209
  return typeof filter.value === 'string' && matchesTermType(value, filter.value);
2210
+ case '$notTermType':
2211
+ return typeof filter.value === 'string' && !matchesTermType(value, filter.value);
1944
2212
  case '$sameTerm': {
1945
2213
  const right = filter.variable2 ? binding[filter.variable2] : filter.value;
1946
2214
  return Boolean(right && (0, types_1.isTerm)(right) && sameTerm(value, right));
1947
2215
  }
2216
+ case '$notSameTerm': {
2217
+ const right = filter.variable2 ? binding[filter.variable2] : filter.value;
2218
+ return Boolean(right && (0, types_1.isTerm)(right) && !sameTerm(value, right));
2219
+ }
1948
2220
  case '$lang':
1949
2221
  return typeof filter.value === 'string'
1950
2222
  && value.termType === 'Literal'
@@ -1953,10 +2225,20 @@ class RdfLocalQueryEngine {
1953
2225
  return typeof filter.value === 'string'
1954
2226
  && value.termType === 'Literal'
1955
2227
  && value.language !== filter.value;
2228
+ case '$langIn':
2229
+ return value.termType === 'Literal'
2230
+ && (filter.values ?? []).some((candidate) => typeof candidate === 'string' && value.language === candidate);
2231
+ case '$notLangIn':
2232
+ return value.termType === 'Literal'
2233
+ && !(filter.values ?? []).some((candidate) => typeof candidate === 'string' && value.language === candidate);
1956
2234
  case '$langMatches':
1957
2235
  return typeof filter.value === 'string'
1958
2236
  && value.termType === 'Literal'
1959
2237
  && langMatches(value.language, filter.value);
2238
+ case '$notLangMatches':
2239
+ return typeof filter.value === 'string'
2240
+ && value.termType === 'Literal'
2241
+ && !langMatches(value.language, filter.value);
1960
2242
  case '$datatype':
1961
2243
  return filter.value !== undefined
1962
2244
  && value.termType === 'Literal'
@@ -1965,6 +2247,12 @@ class RdfLocalQueryEngine {
1965
2247
  return filter.value !== undefined
1966
2248
  && value.termType === 'Literal'
1967
2249
  && !sameTermOrLexical(value.datatype, filter.value);
2250
+ case '$datatypeIn':
2251
+ return value.termType === 'Literal'
2252
+ && (filter.values ?? []).some((candidate) => sameTermOrLexical(value.datatype, candidate));
2253
+ case '$notDatatypeIn':
2254
+ return value.termType === 'Literal'
2255
+ && !(filter.values ?? []).some((candidate) => sameTermOrLexical(value.datatype, candidate));
1968
2256
  default: {
1969
2257
  const exhaustive = filter.operator;
1970
2258
  throw new Error(`Unsupported RDF local query filter operator: ${exhaustive}`);
@@ -2094,6 +2382,9 @@ function variablesInRequiredSource(source) {
2094
2382
  function uniqueStrings(values) {
2095
2383
  return [...new Set(values)];
2096
2384
  }
2385
+ function uniqueNumbers(values) {
2386
+ return [...new Set(values)];
2387
+ }
2097
2388
  function filtersWithoutIndexes(filters, indexes) {
2098
2389
  if (indexes.length === 0) {
2099
2390
  return filters;
@@ -2113,6 +2404,17 @@ function joinValuesSource(input, source) {
2113
2404
  }
2114
2405
  return output;
2115
2406
  }
2407
+ function mergeBindingRows(left, right) {
2408
+ const next = { ...left };
2409
+ for (const [variableName, term] of Object.entries(right)) {
2410
+ const existing = next[variableName];
2411
+ if (existing && !sameTerm(existing, term)) {
2412
+ return null;
2413
+ }
2414
+ next[variableName] = term;
2415
+ }
2416
+ return next;
2417
+ }
2116
2418
  function mergeTupleValuesBinding(binding, variables, row) {
2117
2419
  const next = { ...binding };
2118
2420
  for (const variableName of variables) {
@@ -2372,10 +2674,13 @@ function compareBindings(left, right, orderBy) {
2372
2674
  }
2373
2675
  return 0;
2374
2676
  }
2375
- function bindingKey(binding) {
2376
- return Object.keys(binding)
2677
+ function bindingKey(binding, variables) {
2678
+ return [...(variables ?? Object.keys(binding))]
2377
2679
  .sort()
2378
- .map((key) => `${key}=${(0, n3_1.termToId)(binding[key])}`)
2680
+ .map((key) => {
2681
+ const term = binding[key];
2682
+ return `${key}=${term ? (0, n3_1.termToId)(term) : '__UNBOUND__'}`;
2683
+ })
2379
2684
  .join('\u001f');
2380
2685
  }
2381
2686
  function distinctBindings(bindings) {
@@ -2510,6 +2815,10 @@ function describeBindExpression(expression) {
2510
2815
  return `LCASE(${describeBindExpression(expression.expression)})`;
2511
2816
  case 'upperCase':
2512
2817
  return `UCASE(${describeBindExpression(expression.expression)})`;
2818
+ case 'coalesce':
2819
+ return `COALESCE(${expression.expressions.map(describeBindExpression).join(',')})`;
2820
+ case 'if':
2821
+ return `IF(${expression.condition.map(describeFilter).join('&')},${describeBindExpression(expression.then)},${describeBindExpression(expression.else)})`;
2513
2822
  case 'substring':
2514
2823
  return `SUBSTR(${[
2515
2824
  describeBindExpression(expression.expression),
@@ -2520,6 +2829,10 @@ function describeBindExpression(expression) {
2520
2829
  return `CONCAT(${expression.expressions.map(describeBindExpression).join(',')})`;
2521
2830
  case 'iri':
2522
2831
  return `IRI(${describeBindExpression(expression.expression)})`;
2832
+ case 'strdt':
2833
+ return `STRDT(${describeBindExpression(expression.lexical)},${describeBindExpression(expression.datatype)})`;
2834
+ case 'strlang':
2835
+ return `STRLANG(${describeBindExpression(expression.lexical)},${describeBindExpression(expression.language)})`;
2523
2836
  default: {
2524
2837
  const exhaustive = expression;
2525
2838
  return JSON.stringify(exhaustive);
@@ -2561,61 +2874,225 @@ function storagePlanMarkers(queryPlan) {
2561
2874
  || entry.startsWith('JoinGroupAggregateHaving(')
2562
2875
  || entry.startsWith('JoinGroupAggregateNumeric(')));
2563
2876
  }
2877
+ function requiredSourceScanPlan(backend) {
2878
+ switch (backend) {
2879
+ case 'rdf3x':
2880
+ return 'Rdf3xPrimaryScan';
2881
+ case 'mixed':
2882
+ return 'MixedScan';
2883
+ case 'index':
2884
+ case 'none':
2885
+ return 'IndexScan';
2886
+ default: {
2887
+ const exhaustive = backend;
2888
+ throw new Error(`Unsupported RDF required source scan backend: ${exhaustive}`);
2889
+ }
2890
+ }
2891
+ }
2892
+ function scanPlanOrder(plan) {
2893
+ switch (plan) {
2894
+ case 'Rdf3xPrimaryScan':
2895
+ return 'Rdf3xPrimaryOrder';
2896
+ case 'MixedScan':
2897
+ return 'MixedOrder';
2898
+ case 'IndexScan':
2899
+ return 'IndexOrder';
2900
+ default: {
2901
+ const exhaustive = plan;
2902
+ throw new Error(`Unsupported RDF scan plan for order marker: ${exhaustive}`);
2903
+ }
2904
+ }
2905
+ }
2906
+ function scanPlanLimit(plan) {
2907
+ switch (plan) {
2908
+ case 'Rdf3xPrimaryScan':
2909
+ return 'Rdf3xPrimaryLimit';
2910
+ case 'MixedScan':
2911
+ return 'MixedLimit';
2912
+ case 'IndexScan':
2913
+ return 'IndexLimit';
2914
+ default: {
2915
+ const exhaustive = plan;
2916
+ throw new Error(`Unsupported RDF scan plan for limit marker: ${exhaustive}`);
2917
+ }
2918
+ }
2919
+ }
2920
+ function toRdf3xScanOptions(options) {
2921
+ if (!options) {
2922
+ return undefined;
2923
+ }
2924
+ return {
2925
+ ...(options.order ? { order: options.order } : {}),
2926
+ ...(options.orderDirections ? { orderDirections: options.orderDirections } : {}),
2927
+ ...(options.reverse ? { reverse: true } : {}),
2928
+ ...(options.limit !== undefined ? { limit: options.limit } : {}),
2929
+ ...(options.offset !== undefined ? { offset: options.offset } : {}),
2930
+ };
2931
+ }
2932
+ function toRdf3xTriplePattern(pattern) {
2933
+ const result = {};
2934
+ for (const key of TERM_KEYS) {
2935
+ const value = pattern[key];
2936
+ if (!value) {
2937
+ continue;
2938
+ }
2939
+ if ((0, types_1.isTerm)(value)) {
2940
+ result[key] = value;
2941
+ continue;
2942
+ }
2943
+ if (isRdf3xTermInPattern(value)) {
2944
+ result[key] = value;
2945
+ continue;
2946
+ }
2947
+ if (isRdf3xTermNotInPattern(value)) {
2948
+ result[key] = value;
2949
+ continue;
2950
+ }
2951
+ if (isRdf3xCompatibleOperatorPattern(key, value)) {
2952
+ result[key] = value;
2953
+ continue;
2954
+ }
2955
+ throw new Error(`RDF-3X primary scan cannot compile unsupported ${key} pattern`);
2956
+ }
2957
+ return result;
2958
+ }
2959
+ function stripRdf3xNumericAggregateGuards(patterns, aggregates) {
2960
+ const numericVariables = new Set(aggregates
2961
+ .filter((aggregate) => aggregate.type !== 'count')
2962
+ .map((aggregate) => aggregate.variable)
2963
+ .filter((variableName) => Boolean(variableName)));
2964
+ if (numericVariables.size === 0) {
2965
+ return patterns;
2966
+ }
2967
+ return patterns.map((entry) => {
2968
+ let changed = false;
2969
+ const pattern = {
2970
+ ...entry.pattern,
2971
+ pushedDownFilters: entry.pattern.pushedDownFilters,
2972
+ pushedDownFilterIndexes: [...entry.pattern.pushedDownFilterIndexes],
2973
+ };
2974
+ for (const key of TERM_KEYS) {
2975
+ const variableName = entry.variables[key];
2976
+ if (!variableName || !numericVariables.has(variableName)) {
2977
+ continue;
2978
+ }
2979
+ const value = pattern[key];
2980
+ if (!value || (0, types_1.isTerm)(value) || typeof value !== 'object') {
2981
+ continue;
2982
+ }
2983
+ if (value.$termType !== 'numeric') {
2984
+ continue;
2985
+ }
2986
+ const stripped = { ...value };
2987
+ delete stripped.$termType;
2988
+ if (Object.keys(stripped).length === 0) {
2989
+ delete pattern[key];
2990
+ }
2991
+ else {
2992
+ pattern[key] = stripped;
2993
+ }
2994
+ changed = true;
2995
+ }
2996
+ return changed ? { pattern, variables: entry.variables } : entry;
2997
+ });
2998
+ }
2564
2999
  function isRdf3xCompatiblePattern(pattern) {
2565
3000
  return TERM_KEYS.every((key) => {
2566
3001
  const value = pattern[key];
2567
3002
  if (!value || (0, types_1.isTerm)(value)) {
2568
3003
  return true;
2569
3004
  }
2570
- if (key === 'graph' && isGraphPrefixPattern(value)) {
3005
+ if (isRdf3xTermInPattern(value)) {
2571
3006
  return true;
2572
3007
  }
2573
- if (key === 'object' && isRdf3xNumericRangePattern(value)) {
3008
+ if (isRdf3xTermNotInPattern(value)) {
3009
+ return true;
3010
+ }
3011
+ if (isRdf3xCompatibleOperatorPattern(key, value)) {
2574
3012
  return true;
2575
3013
  }
2576
3014
  return false;
2577
3015
  });
2578
3016
  }
2579
- function isGraphPrefixPattern(value) {
3017
+ function isRdf3xTermInPattern(value) {
2580
3018
  return value !== null
2581
3019
  && typeof value === 'object'
3020
+ && !('termType' in value)
2582
3021
  && Object.keys(value).length === 1
2583
- && '$startsWith' in value
2584
- && typeof value.$startsWith === 'string';
3022
+ && Array.isArray(value.$in)
3023
+ && (value.$in).length > 0
3024
+ && (value.$in).every((entry) => (0, types_1.isTerm)(entry));
2585
3025
  }
2586
- function isRdf3xNumericRangePattern(value) {
3026
+ function isRdf3xTermNotInPattern(value) {
3027
+ return value !== null
3028
+ && typeof value === 'object'
3029
+ && !('termType' in value)
3030
+ && Object.keys(value).length === 1
3031
+ && Array.isArray(value.$notIn)
3032
+ && (value.$notIn).length > 0
3033
+ && (value.$notIn).every((entry) => (0, types_1.isTerm)(entry));
3034
+ }
3035
+ function isRdf3xCompatibleOperatorPattern(key, value) {
2587
3036
  if (value === null || typeof value !== 'object' || 'termType' in value) {
2588
3037
  return false;
2589
3038
  }
2590
- const operators = ['$gt', '$gte', '$lt', '$lte'];
2591
- if (Object.keys(value).some((key) => !operators.includes(key))) {
3039
+ const allowed = new Set([
3040
+ '$in',
3041
+ '$notIn',
3042
+ '$termType',
3043
+ '$language',
3044
+ '$notLanguage',
3045
+ '$langMatches',
3046
+ '$datatype',
3047
+ '$notDatatype',
3048
+ ...(key === 'graph' ? ['$startsWith'] : []),
3049
+ ...(key === 'object' ? ['$gt', '$gte', '$lt', '$lte', '$contains', '$endsWith'] : []),
3050
+ ]);
3051
+ if (Object.keys(value).length === 0 || Object.keys(value).some((operator) => !allowed.has(operator))) {
2592
3052
  return false;
2593
3053
  }
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) {
3054
+ const operators = value;
3055
+ if (operators.$in !== undefined && !isRdf3xTermInPattern({ $in: operators.$in }))
3056
+ return false;
3057
+ if (operators.$notIn !== undefined && !isRdf3xTermNotInPattern({ $notIn: operators.$notIn }))
3058
+ return false;
3059
+ if (operators.$startsWith !== undefined && typeof operators.$startsWith !== 'string')
3060
+ return false;
3061
+ if (operators.$termType !== undefined && !['iri', 'blank', 'literal', 'numeric'].includes(operators.$termType))
3062
+ return false;
3063
+ for (const languageOperator of ['$language', '$notLanguage', '$langMatches']) {
3064
+ if (operators[languageOperator] !== undefined && typeof operators[languageOperator] !== 'string')
3065
+ return false;
3066
+ }
3067
+ for (const datatypeOperator of ['$datatype', '$notDatatype']) {
3068
+ const datatype = operators[datatypeOperator];
3069
+ if (datatype !== undefined && (!(0, types_1.isTerm)(datatype) || datatype.termType !== 'NamedNode'))
2602
3070
  return false;
3071
+ }
3072
+ if (key === 'object') {
3073
+ for (const rangeOperator of ['$gt', '$gte', '$lt', '$lte']) {
3074
+ const rangeValue = operators[rangeOperator];
3075
+ if (rangeValue !== undefined && !isRdf3xObjectRangeValue(rangeValue))
3076
+ return false;
3077
+ }
3078
+ for (const textOperator of ['$contains', '$endsWith']) {
3079
+ if (operators[textOperator] !== undefined && typeof operators[textOperator] !== 'string')
3080
+ return false;
2603
3081
  }
2604
3082
  }
2605
- return hasRange;
3083
+ return true;
2606
3084
  }
2607
- function rdf3xNumericRangeValue(value) {
3085
+ function isRdf3xObjectRangeValue(value) {
2608
3086
  if (typeof value === 'number') {
2609
- return Number.isFinite(value) ? value : undefined;
3087
+ return Number.isFinite(value);
2610
3088
  }
2611
3089
  if (typeof value === 'string') {
2612
- const parsed = Number(value);
2613
- return Number.isFinite(parsed) ? parsed : undefined;
3090
+ return true;
2614
3091
  }
2615
3092
  if ((0, types_1.isTerm)(value)) {
2616
- return (0, RdfTermSemantics_1.isRdfNumericTerm)(value) ? (0, RdfTermSemantics_1.rdfNumericValue)(value.value) : undefined;
3093
+ return true;
2617
3094
  }
2618
- return undefined;
3095
+ return false;
2619
3096
  }
2620
3097
  function bindTextSearchResult(binding, pattern, result) {
2621
3098
  const next = { ...binding };
@@ -2702,4 +3179,7 @@ function aggregatePlan(aggregates, grouped) {
2702
3179
  ? 'Aggregate(count-multi-distinct)'
2703
3180
  : 'Aggregate(count-multi)';
2704
3181
  }
3182
+ function uniquePatternKeys(values) {
3183
+ return TERM_KEYS.filter((key) => values.includes(key));
3184
+ }
2705
3185
  //# sourceMappingURL=RdfLocalQueryEngine.js.map