Skip to content

Commit

Permalink
GH-5231: fix poor query performance for hasStatements() in FedX (#5232)
Browse files Browse the repository at this point in the history
  • Loading branch information
aschwarte10 authored Jan 24, 2025
2 parents 98d8944 + 1cc4ab8 commit dcacf74
Show file tree
Hide file tree
Showing 3 changed files with 125 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 @@ -17,6 +17,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import org.eclipse.rdf4j.common.annotation.Experimental;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.iteration.EmptyIteration;
import org.eclipse.rdf4j.common.iteration.SingletonIteration;
Expand Down Expand Up @@ -574,7 +575,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 @@ -617,6 +618,53 @@ 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.
* <p>
* Specialized implementations of the {@link FederationEvalStrategy} may override and define custom behavior (e.g.,
* to support resilience).
* </p>
*
*
* @param queryInfo
* @return
*/
@Experimental
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 dcacf74

Please sign in to comment.