Skip to content

Commit 29097d1

Browse files
committed
GH-5231: fix poor query performance for hasStatements() in FedX
The previous implementation of the FedXConnection was delegating "hasStatements()" to the implementation of "getStatements()", where the latter was actually fetching data from the federation members. For checks hasStatements() checks like {null, rdf:type, null} or even {null, null, null} the implementation is problematic as it would fetch all data matching the pattern from the federation members, only to answer if it actually exists. We now make use of "existence" check on the federation members, and can actually rely on the source selection cache for this. Unit test coverage has been added.
1 parent fbf6bea commit 29097d1

3 files changed

Lines changed: 118 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: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ public CloseableIteration<Statement> getStatements(QueryInfo queryInfo, Resource
562562
IRI pred, Value obj, Resource... contexts)
563563
throws RepositoryException, MalformedQueryException, QueryEvaluationException {
564564

565-
List<Endpoint> members = federationContext.getFederation().getMembers();
565+
List<Endpoint> members = getAccessibleFederationMembers(queryInfo);
566566

567567
// a bound query: if at least one fed member provides results
568568
// return the statement, otherwise empty result
@@ -605,6 +605,47 @@ public CloseableIteration<Statement> getStatements(QueryInfo queryInfo, Resource
605605
return union;
606606
}
607607

608+
/**
609+
* Returns true if the federation has statements
610+
*
611+
* @param queryInfo information about the query
612+
* @param subj the subject or <code>null</code>
613+
* @param pred the predicate or <code>null</code>
614+
* @param obj the object or <code>null</code>
615+
* @param contexts optional list of contexts
616+
* @return the statement iteration
617+
*
618+
* @throws RepositoryException
619+
* @throws MalformedQueryException
620+
* @throws QueryEvaluationException
621+
*/
622+
public boolean hasStatements(QueryInfo queryInfo, Resource subj,
623+
IRI pred, Value obj, Resource... contexts)
624+
throws RepositoryException, MalformedQueryException, QueryEvaluationException {
625+
626+
List<Endpoint> members = getAccessibleFederationMembers(queryInfo);
627+
628+
// form the union of results from relevant endpoints
629+
List<StatementSource> sources = CacheUtils.checkCacheForStatementSourcesUpdateCache(cache, members, subj, pred,
630+
obj, queryInfo, contexts);
631+
632+
if (sources.isEmpty()) {
633+
return false;
634+
}
635+
636+
return true;
637+
}
638+
639+
/**
640+
* Returns the accessible federation members in the context of the query. By default this is all federation members.
641+
*
642+
* @param queryInfo
643+
* @return
644+
*/
645+
protected List<Endpoint> getAccessibleFederationMembers(QueryInfo queryInfo) {
646+
return federationContext.getFederation().getMembers();
647+
}
648+
608649
public CloseableIteration<BindingSet> evaluateService(FedXService service,
609650
BindingSet bindings) throws QueryEvaluationException {
610651

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)