Skip to content

Commit

Permalink
GH-5231: fix poor query performance for hasStatements() in FedX
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aschwarte10 committed Jan 15, 2025
1 parent fbf6bea commit 29097d1
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,27 @@ protected SailException convert(RuntimeException e) {
}
}

@Override
protected boolean hasStatementInternal(Resource subj, IRI pred, Value obj, boolean includeInferred,
Resource[] contexts) {
try {
Dataset dataset = new SimpleDataset();
FederationEvalStrategy strategy = federationContext.createStrategy(dataset);
QueryInfo queryInfo = new QueryInfo(subj, pred, obj, 0, includeInferred, federationContext, strategy,
dataset);
federationContext.getMonitoringService().monitorQuery(queryInfo);
return strategy.hasStatements(queryInfo, subj, pred, obj, contexts);

} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
throw new SailException(e);
}
}

@Override
protected void addStatementInternal(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ public CloseableIteration<Statement> getStatements(QueryInfo queryInfo, Resource
IRI pred, Value obj, Resource... contexts)
throws RepositoryException, MalformedQueryException, QueryEvaluationException {

List<Endpoint> members = federationContext.getFederation().getMembers();
List<Endpoint> members = getAccessibleFederationMembers(queryInfo);

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

/**
* Returns true if the federation has statements
*
* @param queryInfo information about the query
* @param subj the subject or <code>null</code>
* @param pred the predicate or <code>null</code>
* @param obj the object or <code>null</code>
* @param contexts optional list of contexts
* @return the statement iteration
*
* @throws RepositoryException
* @throws MalformedQueryException
* @throws QueryEvaluationException
*/
public boolean hasStatements(QueryInfo queryInfo, Resource subj,
IRI pred, Value obj, Resource... contexts)
throws RepositoryException, MalformedQueryException, QueryEvaluationException {

List<Endpoint> members = getAccessibleFederationMembers(queryInfo);

// form the union of results from relevant endpoints
List<StatementSource> sources = CacheUtils.checkCacheForStatementSourcesUpdateCache(cache, members, subj, pred,
obj, queryInfo, contexts);

if (sources.isEmpty()) {
return false;
}

return true;
}

/**
* Returns the accessible federation members in the context of the query. By default this is all federation members.
*
* @param queryInfo
* @return
*/
protected List<Endpoint> getAccessibleFederationMembers(QueryInfo queryInfo) {
return federationContext.getFederation().getMembers();
}

public CloseableIteration<BindingSet> evaluateService(FedXService service,
BindingSet bindings) throws QueryEvaluationException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,61 @@ public void testBindClause() throws Exception {
execute("/tests/basic/query_bind.rq", "/tests/basic/query_bind.srx", false, true);
}

@Test
public void testRepositoryConnectionApi() throws Exception {

prepareTest(
Arrays.asList("/tests/basic/data_emptyStore.ttl", "/tests/basic/data_emptyStore.ttl"));

Repository repo1 = getRepository(1);
Repository repo2 = getRepository(2);

IRI bob = Values.iri("http://example.org/bob");
IRI alice = Values.iri("http://example.org/alice");
IRI graph1 = Values.iri("http://example.org/graph1");
IRI graph2 = Values.iri("http://example.org/graph2");

try (RepositoryConnection conn = repo1.getConnection()) {
conn.add(bob, RDF.TYPE, FOAF.PERSON, graph1);
conn.add(bob, FOAF.NAME, Values.literal("Bob"), graph1);
}

try (RepositoryConnection conn = repo2.getConnection()) {
conn.add(alice, RDF.TYPE, FOAF.PERSON, graph2);
conn.add(alice, FOAF.NAME, Values.literal("Alice"), graph2);
}

var fedxRepo = fedxRule.getRepository();

try (var conn = fedxRepo.getConnection()) {

// hasStatement which exist
Assertions.assertTrue(conn.hasStatement(bob, RDF.TYPE, FOAF.PERSON, false));
Assertions.assertTrue(conn.hasStatement(bob, RDF.TYPE, FOAF.PERSON, false, graph1));
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, FOAF.PERSON, false));
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, FOAF.PERSON, false, graph1));
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, null, false));
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, null, false, graph1));
Assertions.assertTrue(conn.hasStatement(null, RDF.TYPE, null, false, graph2));
Assertions.assertTrue(conn.hasStatement(null, null, null, false));
Assertions.assertTrue(conn.hasStatement(null, null, null, false, graph1));

// hasStatement which do not exist
Assertions.assertFalse(conn.hasStatement(bob, RDF.TYPE, FOAF.ORGANIZATION, false));
Assertions.assertFalse(conn.hasStatement(bob, RDF.TYPE, FOAF.PERSON, false, graph2));

// getStatements
Assertions.assertEquals(Set.of(bob, alice),
QueryResults.asModel(conn.getStatements(null, RDF.TYPE, FOAF.PERSON, false)).subjects());
Assertions.assertEquals(Set.of(bob),
QueryResults.asModel(conn.getStatements(null, RDF.TYPE, FOAF.PERSON, false, graph1)).subjects());
Assertions.assertEquals(Set.of(bob, alice),
QueryResults.asModel(conn.getStatements(null, null, null, false)).subjects());
Assertions.assertEquals(Set.of(bob),
QueryResults.asModel(conn.getStatements(null, null, null, false, graph1)).subjects());
}
}

@Test
public void testFederationSubSetQuery() throws Exception {
String ns1 = "http://namespace1.org/";
Expand Down

0 comments on commit 29097d1

Please sign in to comment.