2222import java .util .Optional ;
2323import java .util .Set ;
2424
25+ import org .eclipse .rdf4j .model .vocabulary .RDF ;
2526import org .eclipse .rdf4j .query .algebra .Compare ;
2627import org .eclipse .rdf4j .query .algebra .Filter ;
2728import org .eclipse .rdf4j .query .algebra .Not ;
2829import org .eclipse .rdf4j .query .algebra .StatementPattern ;
2930import org .eclipse .rdf4j .query .algebra .TupleExpr ;
3031import org .eclipse .rdf4j .query .algebra .ValueExpr ;
32+ import org .eclipse .rdf4j .query .algebra .Var ;
3133import org .eclipse .rdf4j .query .algebra .evaluation .optimizer .JoinOrderPlanner ;
3234import org .eclipse .rdf4j .query .algebra .helpers .AbstractSimpleQueryModelVisitor ;
35+ import org .eclipse .rdf4j .query .algebra .helpers .collectors .VarNameCollector ;
36+ import org .eclipse .rdf4j .query .explanation .TelemetryMetricNames ;
3337
3438final class LmdbDeferredFilterPlacer {
3539
@@ -295,17 +299,25 @@ private boolean canApplyDeferredFilterToBindingPrefix(DeferredFilter deferredFil
295299 if (LmdbJoinPlanSupport .containsExists (deferredFilter .condition )) {
296300 return false ;
297301 }
298- return !deferredFilter .requiredVars .isEmpty ()
302+ Set <String > conditionBindingNames = conditionBindingNames (deferredFilter );
303+ return !conditionBindingNames .isEmpty ()
299304 && (deferredFilter .conditionCost > JoinOrderPlanner .FILTER_COST_CHEAP
300- || !Collections .disjoint (prefixBindingNames , deferredFilter . requiredVars ))
301- && !prefixBindingNames .containsAll (deferredFilter . requiredVars )
302- && availableNames .containsAll (deferredFilter . requiredVars )
303- && !Collections .disjoint (assignmentBindingNames , deferredFilter . requiredVars )
304- && canApplySplitPrefixFilter (deferredFilter , assignmentBindingNames );
305+ || !Collections .disjoint (prefixBindingNames , conditionBindingNames ))
306+ && !prefixBindingNames .containsAll (conditionBindingNames )
307+ && availableNames .containsAll (conditionBindingNames )
308+ && !Collections .disjoint (assignmentBindingNames , conditionBindingNames )
309+ && canApplySplitPrefixFilter (deferredFilter , assignmentBindingNames , conditionBindingNames );
305310 }
306311
307- private boolean canApplySplitPrefixFilter (DeferredFilter deferredFilter , Set <String > assignmentBindingNames ) {
308- return assignmentBindingNames .containsAll (deferredFilter .requiredVars )
312+ private Set <String > conditionBindingNames (DeferredFilter deferredFilter ) {
313+ Set <String > bindingNames = new HashSet <>(VarNameCollector .process (deferredFilter .condition ));
314+ bindingNames .removeIf (bindingName -> bindingName == null || bindingName .startsWith ("_const_" ));
315+ return bindingNames ;
316+ }
317+
318+ private boolean canApplySplitPrefixFilter (DeferredFilter deferredFilter , Set <String > assignmentBindingNames ,
319+ Set <String > conditionBindingNames ) {
320+ return assignmentBindingNames .containsAll (conditionBindingNames )
309321 || !containsNotEquals (deferredFilter .condition );
310322 }
311323
@@ -382,6 +394,9 @@ private boolean groupDeferredFilterOnSmallestWindow(List<SegmentFactor> factors,
382394 }
383395 if (window == null && isCorrelatedNotExistsFilter (filter )) {
384396 window = smallestBindingCoveringWindow (factors , filter .requiredVars , boundBeforeSegment );
397+ if (window != null ) {
398+ window = expandCorrelatedNotExistsWindowOverCheapGuards (factors , filter , window );
399+ }
385400 }
386401 if (window == null ) {
387402 if (filter .originPatterns .isEmpty ()) {
@@ -399,6 +414,66 @@ private boolean groupDeferredFilterOnSmallestWindow(List<SegmentFactor> factors,
399414 return groupDeferredFilterOnWindow (factors , filter , window );
400415 }
401416
417+ private int [] expandCorrelatedNotExistsWindowOverCheapGuards (List <SegmentFactor > factors , DeferredFilter filter ,
418+ int [] window ) {
419+ int end = window [1 ];
420+ while (end + 1 < factors .size () && isCheapGuardForCorrelatedFilter (factors .get (end + 1 ), filter )) {
421+ end ++;
422+ }
423+ return end == window [1 ] ? window : new int [] { window [0 ], end };
424+ }
425+
426+ private boolean isCheapGuardForCorrelatedFilter (SegmentFactor factor , DeferredFilter filter ) {
427+ Set <String > bindingNames = plannerBindingNames (factor .bindingNames );
428+ if (bindingNames .isEmpty () || !filter .requiredVars .containsAll (bindingNames )
429+ || factor .containedPatterns .size () != 1 ) {
430+ return false ;
431+ }
432+ StatementPattern pattern = factor .containedPatterns .iterator ()
433+ .next ();
434+ return isSubjectTypeGuardForRequiredBinding (pattern , filter .requiredVars ) || isExactDirectLookup (pattern );
435+ }
436+
437+ private Set <String > plannerBindingNames (Set <String > bindingNames ) {
438+ if (bindingNames == null || bindingNames .isEmpty ()) {
439+ return Set .of ();
440+ }
441+ Set <String > plannerNames = new HashSet <>();
442+ for (String bindingName : bindingNames ) {
443+ if (bindingName != null && !bindingName .startsWith ("_const_" )) {
444+ plannerNames .add (bindingName );
445+ }
446+ }
447+ return plannerNames ;
448+ }
449+
450+ private boolean isSubjectTypeGuardForRequiredBinding (StatementPattern statementPattern , Set <String > requiredVars ) {
451+ Var subject = statementPattern .getSubjectVar ();
452+ Var predicate = statementPattern .getPredicateVar ();
453+ Var object = statementPattern .getObjectVar ();
454+ return subject != null
455+ && !subject .hasValue ()
456+ && requiredVars .contains (subject .getName ())
457+ && predicate != null
458+ && predicate .hasValue ()
459+ && RDF .TYPE .equals (predicate .getValue ())
460+ && object != null
461+ && object .hasValue ();
462+ }
463+
464+ private boolean isExactDirectLookup (StatementPattern statementPattern ) {
465+ if (!"directLookup" .equals (
466+ statementPattern .getStringMetricPlanned (TelemetryMetricNames .PLANNED_INDEX_ACCESS_MODE ))) {
467+ return false ;
468+ }
469+ String lookupComponents = statementPattern
470+ .getStringMetricPlanned (TelemetryMetricNames .PLANNED_LOOKUP_COMPONENTS );
471+ return lookupComponents != null
472+ && lookupComponents .contains ("S" )
473+ && lookupComponents .contains ("P" )
474+ && lookupComponents .contains ("O" );
475+ }
476+
402477 private boolean groupDeferredFilterOnPlannerWindow (List <SegmentFactor > factors , DeferredFilter filter ,
403478 Set <String > boundBeforeSegment , Integer targetStep ) {
404479 if (targetStep == null || filter .conditionCost == JoinOrderPlanner .FILTER_COST_CHEAP ) {
0 commit comments