From 979a89e0a555bafebfb1eb5c600d64f0dc1675e0 Mon Sep 17 00:00:00 2001 From: Olaf Hartig Date: Sun, 11 Oct 2026 18:10:48 +0200 Subject: [PATCH 1/3] adds a 'contains' method to 'ExpectedVariables' --- .../ida/hefquin/base/query/ExpectedVariables.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hefquin-base/src/main/java/se/liu/ida/hefquin/base/query/ExpectedVariables.java b/hefquin-base/src/main/java/se/liu/ida/hefquin/base/query/ExpectedVariables.java index c6bf5ae51b..d7999053df 100644 --- a/hefquin-base/src/main/java/se/liu/ida/hefquin/base/query/ExpectedVariables.java +++ b/hefquin-base/src/main/java/se/liu/ida/hefquin/base/query/ExpectedVariables.java @@ -25,4 +25,18 @@ public interface ExpectedVariables * every such solution mapping. */ Set getPossibleVariables(); + + /** + * Returns {@code true} if the given variable is one of certain + * variables or one of the possible variables. + * + * @param v - the variable for which we want to check + * @return {@code true} if {@code v} is in the set returned by + * {@link #getCertainVariables()} or in the set returned + * by {@link #getPossibleVariables()} + */ + default boolean contains( final Var v ) { + return getCertainVariables().contains(v) + || getPossibleVariables().contains(v); + } } From 4a86f5ec98c1e4f3b5fe0a01902fe7684e241ce6 Mon Sep 17 00:00:00 2001 From: Olaf Hartig Date: Sun, 11 Oct 2026 18:11:24 +0200 Subject: [PATCH 2/3] adds a second constructor to LogicalOpBind (as a convenience) --- .../hefquin/engine/queryplan/logical/impl/LogicalOpBind.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/logical/impl/LogicalOpBind.java b/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/logical/impl/LogicalOpBind.java index 05e7bab25a..ff3361f587 100644 --- a/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/logical/impl/LogicalOpBind.java +++ b/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/logical/impl/LogicalOpBind.java @@ -24,6 +24,10 @@ public LogicalOpBind( final VarExprList bindExpressions, final boolean mayReduce this.bindExpressions = bindExpressions; } + public LogicalOpBind( final Var var, final Expr expr, final boolean mayReduce ) { + this( new VarExprList(var, expr), mayReduce ); + } + @Override public ExpectedVariables getExpectedVariables( final ExpectedVariables... inputVars ) { assert inputVars.length == 1; From 4ebd4c370e8b51a3eb4993e59c1d17e656390e2c Mon Sep 17 00:00:00 2001 From: Olaf Hartig Date: Sun, 11 Oct 2026 18:13:30 +0200 Subject: [PATCH 3/3] fix to the solution of PR #579 to account for the fact that an mreq operator also adds bindings for its service variable --- .../queryplan/utils/PhysicalPlanFactory.java | 82 +++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/utils/PhysicalPlanFactory.java b/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/utils/PhysicalPlanFactory.java index 7c61b2605a..e049f6f378 100644 --- a/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/utils/PhysicalPlanFactory.java +++ b/hefquin-engine/src/main/java/se/liu/ida/hefquin/engine/queryplan/utils/PhysicalPlanFactory.java @@ -5,6 +5,14 @@ import java.util.List; import java.util.Set; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.expr.E_Equals; +import org.apache.jena.sparql.expr.Expr; +import org.apache.jena.sparql.expr.ExprVar; +import org.apache.jena.sparql.expr.NodeValue; + import se.liu.ida.hefquin.base.data.VocabularyMapping; import se.liu.ida.hefquin.base.query.ExpectedVariables; import se.liu.ida.hefquin.base.query.SPARQLGraphPattern; @@ -546,14 +554,76 @@ public static PhysicalPlan createPlanWithDefaultUnaryOpIfPossible( final Physica if ( rootOp instanceof PhysicalOpMultiRequest mreq ) { final LogicalOpMultiRequest lop = mreq.getLogicalOperator(); final SPARQLGraphPattern pattern = lop.getRequest().getQueryPattern(); - final Set fms = lop.getFederationMembers() ; - + final Set fms = lop.getFederationMembers(); + + // Given that the mreq operator also adds bindings for its + // service variable, and that this variable may already be + // bound within the request pattern, it may be necessary to + // add a filter condition that ensures the compatibility of + // these bindings. Let's check whether it is needed: + final Var serviceVar = lop.getServiceVariable(); + final boolean filterNeeded = pattern.getExpectedVariables().contains(serviceVar); + + // Now we iterate over the federation members of the mreq + // operator and create a separate subplan for each of them + // (which, after the iteration, will be combined under a + // union operator). final List subPlans = new ArrayList<>( fms.size() ); - for ( final FederationMember fm : fms ) { - final LogicalOpGPAdd gpAdd = new LogicalOpGPAdd( fm, pattern, null, lop.mayReduce() ); - final PhysicalPlan gpAddPlan = PhysicalPlanFactory.createPlan(gpAdd, null, lop2pop, inputPlan); - subPlans.add(gpAddPlan); + final Node serviceURI = NodeFactory.createURI( fm.getServiceURI() ); + final NodeValue serviceURIAsValue = NodeValue.makeNode(serviceURI); + + final PhysicalPlan gpAddPlan; + if ( filterNeeded ) { + final Expr expr = new E_Equals( new ExprVar(serviceVar), + serviceURIAsValue ); + + // Check whether the federation member can handle the + // pattern with the filter included. + final SPARQLGraphPattern patternWithFilter = pattern.mergeWith(expr); + if ( fm.isSupportedPattern(patternWithFilter) ) { + final LogicalOpGPAdd gpAdd = new LogicalOpGPAdd( + fm, + patternWithFilter, + null, + lop.mayReduce() ); + gpAddPlan = createPlan(gpAdd, null, lop2pop, inputPlan); + } + else { + // If the federation member cannot handle the pattern + // with the filter, then we need to create the gpAdd + // operator for the federation member without the + // filter and ... + final LogicalOpGPAdd gpAdd = new LogicalOpGPAdd( + fm, + pattern, // without the filter! + null, + lop.mayReduce() ); + + // ... add the filter as a separate operator on top. + gpAddPlan = createPlan( + new LogicalOpFilter( expr, lop.mayReduce() ), + null, + lop2pop, + createPlan(gpAdd, null, lop2pop, inputPlan) ); + } + } + else { + final LogicalOpGPAdd gpAdd = new LogicalOpGPAdd( fm, + pattern, + null, + lop.mayReduce() ); + gpAddPlan = createPlan(gpAdd, null, lop2pop, inputPlan); + } + + // To complete the subplan for the current federation member + // we still need to add a bind operator that binds the service + // variable to the service URI of the federation member. + final LogicalOpBind bindOp = new LogicalOpBind( serviceVar, + serviceURIAsValue, + lop.mayReduce() ); + final PhysicalPlan subplan = createPlan(bindOp, null, lop2pop, gpAddPlan); + subPlans.add(subplan); } return createPlan( LogicalOpMultiwayUnion.getInstance(), qpInfo, lop2pop, subPlans );