Skip to content

Commit dcacf74

Browse files
authored
GH-5231: fix poor query performance for hasStatements() in FedX (#5232)
2 parents 98d8944 + 1cc4ab8 commit dcacf74

3 files changed

Lines changed: 125 additions & 1 deletion

File tree

tools/federation/src/main/java/org/eclipse/rdf4j/federated/FedXConnection.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,27 @@ protected SailException convert(RuntimeException e) {
337337
}
338338
}
339339

340+
@Override
341+
protected boolean hasStatementInternal(Resource subj, IRI pred, Value obj, boolean includeInferred,
342+
Resource[] contexts) {
343+
try {
344+
Dataset dataset = new SimpleDataset();
345+
FederationEvalStrategy strategy = federationContext.createStrategy(dataset);
346+
QueryInfo queryInfo = new QueryInfo(subj, pred, obj, 0, includeInferred, federationContext, strategy,
347+
dataset);
348+
federationContext.getMonitoringService().monitorQuery(queryInfo);
349+
return strategy.hasStatements(queryInfo, subj, pred, obj, contexts);
350+
351+
} catch (RuntimeException e) {
352+
throw e;
353+
} catch (Exception e) {
354+
if (e instanceof InterruptedException) {
355+
Thread.currentThread().interrupt();
356+
}
357+
throw new SailException(e);
358+
}
359+
}
360+
340361
@Override
341362
protected void addStatementInternal(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException {
342363
try {

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.concurrent.atomic.AtomicBoolean;
1818
import java.util.stream.Collectors;
1919

20+
import org.eclipse.rdf4j.common.annotation.Experimental;
2021
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
2122
import org.eclipse.rdf4j.common.iteration.EmptyIteration;
2223
import org.eclipse.rdf4j.common.iteration.SingletonIteration;
@@ -574,7 +575,7 @@ public CloseableIteration<Statement> getStatements(QueryInfo queryInfo, Resource
574575
IRI pred, Value obj, Resource... contexts)
575576
throws RepositoryException, MalformedQueryException, QueryEvaluationException {
576577

577-
List<Endpoint> members = federationContext.getFederation().getMembers();
578+
List<Endpoint> members = getAccessibleFederationMembers(queryInfo);
578579

579580
// a bound query: if at least one fed member provides results
580581
// return the statement, otherwise empty result
@@ -617,6 +618,53 @@ public CloseableIteration<Statement> getStatements(QueryInfo queryInfo, Resource
617618
return union;
618619
}
619620

621+
/**
622+
* Returns true if the federation has statements
623+
*
624+
* @param queryInfo information about the query
625+
* @param subj the subject or <code>null</code>
626+
* @param pred the predicate or <code>null</code>
627+
* @param obj the object or <code>null</code>
628+
* @param contexts optional list of contexts
629+
* @return the statement iteration
630+
*
631+
* @throws RepositoryException
632+
* @throws MalformedQueryException
633+
* @throws QueryEvaluationException
634+
*/
635+
public boolean hasStatements(QueryInfo queryInfo, Resource subj,
636+
IRI pred, Value obj, Resource... contexts)
637+
throws RepositoryException, MalformedQueryException, QueryEvaluationException {
638+
639+
List<Endpoint> members = getAccessibleFederationMembers(queryInfo);
640+
641+
// form the union of results from relevant endpoints
642+
List<StatementSource> sources = CacheUtils.checkCacheForStatementSourcesUpdateCache(cache, members, subj, pred,
643+
obj, queryInfo, contexts);
644+
645+
if (sources.isEmpty()) {
646+
return false;
647+
}
648+
649+
return true;
650+
}
651+
652+
/**
653+
* Returns the accessible federation members in the context of the query. By default this is all federation members.
654+
* <p>
655+
* Specialized implementations of the {@link FederationEvalStrategy} may override and define custom behavior (e.g.,
656+
* to support resilience).
657+
* </p>
658+
*
659+
*
660+
* @param queryInfo
661+
* @return
662+
*/
663+
@Experimental
664+
protected List<Endpoint> getAccessibleFederationMembers(QueryInfo queryInfo) {
665+
return federationContext.getFederation().getMembers();
666+
}
667+
620668
public CloseableIteration<BindingSet> evaluateService(FedXService service,
621669
BindingSet bindings) throws QueryEvaluationException {
622670

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,61 @@ public void testBindClause() throws Exception {
139139
execute("/tests/basic/query_bind.rq", "/tests/basic/query_bind.srx", false, true);
140140
}
141141

142+
@Test
143+
public void testRepositoryConnectionApi() throws Exception {
144+
145+
prepareTest(
146+
Arrays.asList("/tests/basic/data_emptyStore.ttl", "/tests/basic/data_emptyStore.ttl"));
147+
148+
Repository repo1 = getRepository(1);
149+
Repository repo2 = getRepository(2);
150+
151+
IRI bob = Values.iri("http://example.org/bob");
152+
IRI alice = Values.iri("http://example.org/alice");
153+
IRI graph1 = Values.iri("http://example.org/graph1");
154+
IRI graph2 = Values.iri("http://example.org/graph2");
155+
156+
try (RepositoryConnection conn = repo1.getConnection()) {
157+
conn.add(bob, RDF.TYPE, FOAF.PERSON, graph1);
158+
conn.add(bob, FOAF.NAME, Values.literal("Bob"), graph1);
159+
}
160+
161+
try (RepositoryConnection conn = repo2.getConnection()) {
162+
conn.add(alice, RDF.TYPE, FOAF.PERSON, graph2);
163+
conn.add(alice, FOAF.NAME, Values.literal("Alice"), graph2);
164+
}
165+
166+
var fedxRepo = fedxRule.getRepository();
167+
168+
try (var conn = fedxRepo.getConnection()) {
169+
170+
// hasStatement which exist
171+
Assertions.assertTrue(conn.hasStatement(bob, RDF.TYPE, FOAF.PERSON, false));
172+
Assertions.assertTrue(conn.hasStatement(bob, RDF.TYPE, FOAF.PERSON, false, graph1));
173+
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, FOAF.PERSON, false));
174+
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, FOAF.PERSON, false, graph1));
175+
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, null, false));
176+
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, null, false, graph1));
177+
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, null, false, graph2));
178+
Assertions.assertTrue(conn.hasStatement(null, null, null, false));
179+
Assertions.assertTrue(conn.hasStatement(null, null, null, false, graph1));
180+
181+
// hasStatement which do not exist
182+
Assertions.assertFalse(conn.hasStatement(bob, RDF.TYPE, FOAF.ORGANIZATION, false));
183+
Assertions.assertFalse(conn.hasStatement(bob, RDF.TYPE, FOAF.PERSON, false, graph2));
184+
185+
// getStatements
186+
Assertions.assertEquals(Set.of(bob, alice),
187+
QueryResults.asModel(conn.getStatements(null, RDF.TYPE, FOAF.PERSON, false)).subjects());
188+
Assertions.assertEquals(Set.of(bob),
189+
QueryResults.asModel(conn.getStatements(null, RDF.TYPE, FOAF.PERSON, false, graph1)).subjects());
190+
Assertions.assertEquals(Set.of(bob, alice),
191+
QueryResults.asModel(conn.getStatements(null, null, null, false)).subjects());
192+
Assertions.assertEquals(Set.of(bob),
193+
QueryResults.asModel(conn.getStatements(null, null, null, false, graph1)).subjects());
194+
}
195+
}
196+
142197
@Test
143198
public void testFederationSubSetQuery() throws Exception {
144199
String ns1 = "http://namespace1.org/";

0 commit comments

Comments
 (0)