diff --git a/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDown.java b/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDown.java index 950e24a67..131e2bda3 100644 --- a/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDown.java +++ b/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDown.java @@ -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; @@ -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 ) { @@ -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 varsInFilter = ExprVars.getVarsMentioned( filterOp.getFilterExpressions() ); + final List 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}. diff --git a/hefquin-engine/src/test/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDownTest.java b/hefquin-engine/src/test/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDownTest.java index 6b181be08..31ab30037 100644 --- a/hefquin-engine/src/test/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDownTest.java +++ b/hefquin-engine/src/test/java/se/liu/ida/hefquin/engine/queryproc/impl/loptimizer/heuristics/FilterPushDownTest.java @@ -6,6 +6,7 @@ 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; @@ -13,6 +14,7 @@ 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; @@ -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; @@ -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 ); + } + }