Skip to content

Commit

Permalink
Merge pull request #384 from LiUSemWeb/Issue383
Browse files Browse the repository at this point in the history
Extends FilterPushDown to be able to push filters under LogicalOpBind
  • Loading branch information
hartig authored Jan 30, 2025
2 parents 4befbb0 + 410c1f0 commit 0b149bd
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package se.liu.ida.hefquin.engine.queryproc.impl.loptimizer.heuristics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -76,18 +77,25 @@ protected LogicalPlan applyToPlanWithFilterAsRootOperator( final LogicalOpFilter
final LogicalPlan subPlanUnderFilter,
final LogicalPlan inputPlan ) {
final LogicalOperator childOpUnderFilter = subPlanUnderFilter.getRootOperator();
if ( childOpUnderFilter instanceof LogicalOpRequest )
if ( childOpUnderFilter instanceof LogicalOpRequest requestOpUnderFilter )
{
return createPlanForRequestUnderFilter( filterOp,
(LogicalOpRequest<?,?>) childOpUnderFilter,
requestOpUnderFilter,
inputPlan );
}
else if ( childOpUnderFilter instanceof LogicalOpFilter )
else if ( childOpUnderFilter instanceof LogicalOpFilter filterOpUnderFilter )
{
return createPlanForFilterUnderFilter( filterOp,
(LogicalOpFilter) childOpUnderFilter,
filterOpUnderFilter,
subPlanUnderFilter.getSubPlan(0) );
}
else if ( childOpUnderFilter instanceof LogicalOpBind bindOpUnderFilter )
{
return createPlanForBindUnderFilter( filterOp,
bindOpUnderFilter,
subPlanUnderFilter.getSubPlan(0),
inputPlan );
}
else if ( childOpUnderFilter instanceof LogicalOpLocalToGlobal
|| childOpUnderFilter instanceof LogicalOpGlobalToLocal )
{
Expand Down Expand Up @@ -189,6 +197,28 @@ protected LogicalPlan createPlanForUnionUnderFilter( final LogicalOpFilter filte
return LogicalPlanUtils.createPlanWithSubPlans(unionOp, newSubPlans);
}

protected LogicalPlan createPlanForBindUnderFilter( final LogicalOpFilter filterOp,
final LogicalOpBind bindOp,
final LogicalPlan subPlanUnderBind,
final LogicalPlan inputPlan ) {
// Check whether the filter can be pushed under the given bind operator,
// which is possible only if none of the variables assigned by the bind
// operator is used in the filter condition.
final Set<Var> varsInFilter = ExprVars.getVarsMentioned( filterOp.getFilterExpressions() );
final List<Var> varsInBind = bindOp.getBindExpressions().getVars();
if ( ! Collections.disjoint(varsInFilter, varsInBind) )
return inputPlan;

// The filter can be pushed. In this case, create a new subplan with
// the filter as root operator on top of the subplan that was under
// the bind, and apply this heuristic recursively to this new subplan.
final LogicalPlan newSubPlan1 = LogicalPlanUtils.createPlanWithSubPlans(filterOp, subPlanUnderBind);
final LogicalPlan newSubPlan2 = apply(newSubPlan1);

// Finally, put together the new plan with the bind operator as root.
return LogicalPlanUtils.createPlanWithSubPlans(bindOp, newSubPlan2);
}

/**
* Assumes that the given child operator is either a {@link LogicalOpLocalToGlobal}
* or a {@link LogicalOpGlobalToLocal}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import java.util.Arrays;

import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.core.VarExprList;
import org.apache.jena.sparql.expr.E_Bound;
import org.apache.jena.sparql.expr.E_Equals;
import org.apache.jena.sparql.expr.E_IsIRI;
import org.apache.jena.sparql.expr.E_LogicalNot;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprList;
import org.apache.jena.sparql.expr.ExprVar;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.sparql.syntax.Element;
import org.apache.jena.sparql.syntax.ElementFilter;
import org.apache.jena.sparql.syntax.ElementGroup;
Expand All @@ -31,6 +33,7 @@
import se.liu.ida.hefquin.engine.queryplan.logical.LogicalPlan;
import se.liu.ida.hefquin.engine.queryplan.logical.LogicalPlanUtils;
import se.liu.ida.hefquin.engine.queryplan.logical.UnaryLogicalOp;
import se.liu.ida.hefquin.engine.queryplan.logical.impl.LogicalOpBind;
import se.liu.ida.hefquin.engine.queryplan.logical.impl.LogicalOpFilter;
import se.liu.ida.hefquin.engine.queryplan.logical.impl.LogicalOpJoin;
import se.liu.ida.hefquin.engine.queryplan.logical.impl.LogicalOpMultiwayUnion;
Expand Down Expand Up @@ -246,4 +249,128 @@ public void pushFilterOverFilterUnderUnion() {
assertTrue( subResult2.getSubPlan(0).getRootOperator().equals(reqOp2) );
}

@Test
public void pushFilterUnderBindImpossible() {
// a filter on top of a bind with a TPF request underneath, where the
// filter refers to the variable assigned by the bind;
// hence, the filter can *not* be pushed under the bind

// set up
// - request operator
final FederationMember fm = new TPFServerForTest();
final Var v1 = Var.alloc("x");
final TriplePattern tp = new TriplePatternImpl(v1, v1, v1);
final LogicalOpRequest<?,?> reqOp = new LogicalOpRequest<>( fm, new TriplePatternRequestImpl(tp) );

// - bind operator
final Var v2 = Var.alloc("y");
final Expr bindExpr = NodeValue.makeInteger(42);
final VarExprList bindExpressions = new VarExprList(v2, bindExpr);
final LogicalOpBind bindOp = new LogicalOpBind(bindExpressions);

// - filter operator
final Expr filterExpr = new E_IsIRI( new ExprVar(v2) ); // v2 !!!
final LogicalOpFilter filterOp = new LogicalOpFilter(filterExpr);

// - plan
final LogicalPlan reqPlan = new LogicalPlanWithNullaryRootImpl(reqOp);
final LogicalPlan bindPlan = new LogicalPlanWithUnaryRootImpl(bindOp, reqPlan);
final LogicalPlan filterPlan = new LogicalPlanWithUnaryRootImpl(filterOp, bindPlan);

// test
final LogicalPlan result = new FilterPushDown().apply(filterPlan);

// check
assertTrue( result.getRootOperator() instanceof LogicalOpFilter );

final LogicalPlan subResult1 = result.getSubPlan(0);
assertTrue( subResult1.getRootOperator() instanceof LogicalOpBind );
}

@Test
public void pushFilterUnderOneBind() {
// a filter on top of a bind with a TPF request underneath, where the
// filter refers to the variable assigned by the request;
// hence, the filter can be pushed under the bind but not into the request

// set up
// - request operator
final FederationMember fm = new TPFServerForTest();
final Var v1 = Var.alloc("x");
final TriplePattern tp = new TriplePatternImpl(v1, v1, v1);
final LogicalOpRequest<?,?> reqOp = new LogicalOpRequest<>( fm, new TriplePatternRequestImpl(tp) );

// - bind operator
final Var v2 = Var.alloc("y");
final Expr bindExpr = NodeValue.makeInteger(42);
final VarExprList bindExpressions = new VarExprList(v2, bindExpr);
final LogicalOpBind bindOp = new LogicalOpBind(bindExpressions);

// - filter operator
final Expr filterExpr = new E_IsIRI( new ExprVar(v1) );
final LogicalOpFilter filterOp = new LogicalOpFilter(filterExpr);

// - plan
final LogicalPlan reqPlan = new LogicalPlanWithNullaryRootImpl(reqOp);
final LogicalPlan bindPlan = new LogicalPlanWithUnaryRootImpl(bindOp, reqPlan);
final LogicalPlan filterPlan = new LogicalPlanWithUnaryRootImpl(filterOp, bindPlan);

// test
final LogicalPlan result = new FilterPushDown().apply(filterPlan);

// check
assertTrue( result.getRootOperator() instanceof LogicalOpBind );

final LogicalPlan subResult1 = result.getSubPlan(0);
assertTrue( subResult1.getRootOperator() instanceof LogicalOpFilter );
}

@Test
public void pushFilterUnderTwoBinds() {
// a filter on top of two bind with a TPF request underneath, where the
// filter refers to the variable assigned by the request; hence, the
// filter can be pushed under both binds but not into the request

// set up
// - request operator
final FederationMember fm = new TPFServerForTest();
final Var v1 = Var.alloc("x");
final TriplePattern tp = new TriplePatternImpl(v1, v1, v1);
final LogicalOpRequest<?,?> reqOp = new LogicalOpRequest<>( fm, new TriplePatternRequestImpl(tp) );

// - 1st bind operator
final Var v2 = Var.alloc("y");
final Expr bind1Expr = NodeValue.makeInteger(42);
final VarExprList bind1Expressions = new VarExprList(v2, bind1Expr);
final LogicalOpBind bind1Op = new LogicalOpBind(bind1Expressions);

// - 2nd bind operator
final Var v3 = Var.alloc("z");
final Expr bind2Expr = NodeValue.makeInteger(42);
final VarExprList bind2Expressions = new VarExprList(v3, bind2Expr);
final LogicalOpBind bind2Op = new LogicalOpBind(bind2Expressions);

// - filter operator
final Expr filterExpr = new E_IsIRI( new ExprVar(v1) );
final LogicalOpFilter filterOp = new LogicalOpFilter(filterExpr);

// - plan
final LogicalPlan reqPlan = new LogicalPlanWithNullaryRootImpl(reqOp);
final LogicalPlan bind1Plan = new LogicalPlanWithUnaryRootImpl(bind1Op, reqPlan);
final LogicalPlan bind2Plan = new LogicalPlanWithUnaryRootImpl(bind2Op, bind1Plan);
final LogicalPlan filterPlan = new LogicalPlanWithUnaryRootImpl(filterOp, bind2Plan);

// test
final LogicalPlan result = new FilterPushDown().apply(filterPlan);

// check
assertTrue( result.getRootOperator() instanceof LogicalOpBind );

final LogicalPlan subResult1 = result.getSubPlan(0);
assertTrue( subResult1.getRootOperator() instanceof LogicalOpBind );

final LogicalPlan subResult2 = subResult1.getSubPlan(0);
assertTrue( subResult2.getRootOperator() instanceof LogicalOpFilter );
}

}

0 comments on commit 0b149bd

Please sign in to comment.