Skip to content

Commit 1db80b5

Browse files
committed
GH-5229: fix left bind join in FedX for single binding input
This change fixes a situation that can incorrectly cause empty results. It happens when the input of the left argument is a single binding set and for special source selection situations (e.g. the right argument is marked as ExclusiveStatement while the endpoint does not provide data) To avoid the issue we also use the regular left join logic also for a single binding set input, which can handle the situation properly. Issue is covered with a unit test.
1 parent fbf6bea commit 1db80b5

2 files changed

Lines changed: 79 additions & 4 deletions

File tree

tools/federation/src/main/java/org/eclipse/rdf4j/federated/evaluation/FederationEvalStrategy.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -953,10 +953,6 @@ public abstract CloseableIteration<BindingSet> evaluateGroupedCheck(
953953
*/
954954
public CloseableIteration<BindingSet> evaluateLeftBoundJoinStatementPattern(
955955
StatementTupleExpr stmt, final List<BindingSet> bindings) throws QueryEvaluationException {
956-
// we can omit the bound join handling
957-
if (bindings.size() == 1) {
958-
return evaluate(stmt, bindings.get(0));
959-
}
960956

961957
FilterValueExpr filterExpr = null;
962958
if (stmt instanceof FilterTuple) {

tools/federation/src/test/java/org/eclipse/rdf4j/federated/BindLeftJoinTests.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import java.util.Set;
1515

1616
import org.eclipse.rdf4j.common.iteration.Iterations;
17+
import org.eclipse.rdf4j.federated.endpoint.Endpoint;
18+
import org.eclipse.rdf4j.federated.structures.SubQuery;
1719
import org.eclipse.rdf4j.model.util.Values;
1820
import org.eclipse.rdf4j.model.vocabulary.FOAF;
1921
import org.eclipse.rdf4j.model.vocabulary.OWL;
@@ -339,4 +341,81 @@ public void test_leftBindJoin_emptyOptional(boolean bindLeftJoinOptimizationEnab
339341
}
340342
}
341343

344+
@ParameterizedTest
345+
@ValueSource(booleans = { true, false })
346+
public void test_leftBindJoin_emptyLeftArgumentAsExclusiveGroup(boolean bindLeftJoinOptimizationEnabled)
347+
throws Exception {
348+
349+
var endpoints = prepareTest(
350+
Arrays.asList("/tests/basic/data_emptyStore.ttl", "/tests/basic/data_emptyStore.ttl"));
351+
352+
Repository repo1 = getRepository(1);
353+
Repository repo2 = getRepository(2);
354+
355+
Repository fedxRepo = fedxRule.getRepository();
356+
357+
fedxRule.setConfig(config -> {
358+
config.withBoundJoinBlockSize(10);
359+
config.withEnableOptionalAsBindJoin(bindLeftJoinOptimizationEnabled);
360+
});
361+
362+
// add a person
363+
try (RepositoryConnection conn = repo1.getConnection()) {
364+
var p = Values.iri("http://ex.com/p1");
365+
var otherP = Values.iri("http://other.com/p1");
366+
conn.add(p, OWL.SAMEAS, otherP);
367+
}
368+
369+
// add name for person 1
370+
try (RepositoryConnection conn = repo2.getConnection()) {
371+
var otherP = Values.iri("http://other.com/p1");
372+
conn.add(otherP, FOAF.NAME, Values.literal("Person 1"));
373+
}
374+
375+
// mark that repo2 for some reason has foaf:age statements (e.g. old cache entry)
376+
Endpoint repo2Endpoint = endpoints.get(1);
377+
federationContext().getSourceSelectionCache()
378+
.updateInformation(new SubQuery(null, FOAF.AGE, null), repo2Endpoint, true);
379+
380+
fedxRule.enableDebug();
381+
382+
try {
383+
// run query which joins results from multiple repos
384+
// the age does not exist for any person
385+
try (RepositoryConnection conn = fedxRepo.getConnection()) {
386+
String query = "PREFIX foaf: <http://xmlns.com/foaf/0.1/> " +
387+
"SELECT * WHERE { "
388+
+ " ?person owl:sameAs ?otherPerson . "
389+
+ " OPTIONAL { ?otherPerson foaf:age ?age . } " // age does not exist, however is marked as
390+
// ExclusiveStatement
391+
+ "}";
392+
393+
TupleQuery tupleQuery = conn.prepareTupleQuery(query);
394+
try (TupleQueryResult tqr = tupleQuery.evaluate()) {
395+
var bindings = Iterations.asList(tqr);
396+
397+
Assertions.assertEquals(1, bindings.size());
398+
399+
for (int i = 1; i <= 1; i++) {
400+
var p = Values.iri("http://ex.com/p" + i);
401+
var otherP = Values.iri("http://other.com/p" + i);
402+
403+
// find the bindingset for the person in the unordered result
404+
BindingSet bs = bindings.stream()
405+
.filter(b -> b.getValue("person").equals(p))
406+
.findFirst()
407+
.orElseThrow();
408+
409+
Assertions.assertEquals(otherP, bs.getValue("otherPerson"));
410+
411+
Assertions.assertEquals(otherP, bs.getValue("otherPerson"));
412+
Assertions.assertFalse(bs.hasBinding("age"));
413+
}
414+
}
415+
}
416+
417+
} finally {
418+
fedxRepo.shutDown();
419+
}
420+
}
342421
}

0 commit comments

Comments
 (0)