Skip to content

Commit 208459f

Browse files
committed
GH-5030: fix FedXDataset support for queries with FROM clauses
FedX supports interpret the Dataset being defined on the query: if it is of type FedXDataset, the federation is reduced to the members defined by FedXDataset#getEndpoints(). This is a nice means to evaluate a query on a subset of the federation and is generally working fine. When the query contains a FROM clause, however, the externally passed Dataset is wrapped into a FallbackDataset (which internally keeps the FedXDataset as primary one. This change now inspects the FallbackDataset and unwraps it to reduce the members. Note that for execution still the "FallbackDataset" is used.
1 parent 516bf0a commit 208459f

3 files changed

Lines changed: 120 additions & 12 deletions

File tree

core/query/src/main/java/org/eclipse/rdf4j/query/impl/FallbackDataset.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,12 @@ private void appendURI(StringBuilder sb, IRI uri) {
113113
}
114114
}
115115

116+
public Dataset getPrimary() {
117+
return primary;
118+
}
119+
120+
public Dataset getFallback() {
121+
return fallback;
122+
}
123+
116124
}

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

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
import org.eclipse.rdf4j.query.algebra.helpers.TupleExprs;
122122
import org.eclipse.rdf4j.query.algebra.helpers.collectors.VarNameCollector;
123123
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
124+
import org.eclipse.rdf4j.query.impl.FallbackDataset;
124125
import org.eclipse.rdf4j.repository.RepositoryException;
125126
import org.eclipse.rdf4j.repository.sparql.federation.CollectionIteration;
126127
import org.eclipse.rdf4j.repository.sparql.federation.RepositoryFederatedService;
@@ -179,19 +180,8 @@ public TupleExpr optimize(TupleExpr expr, EvaluationStatistics evaluationStatist
179180

180181
FederationEvaluationStatistics stats = (FederationEvaluationStatistics) evaluationStatistics;
181182
QueryInfo queryInfo = stats.getQueryInfo();
182-
Dataset dataset = stats.getDataset();
183183

184-
FederationContext federationContext = queryInfo.getFederationContext();
185-
List<Endpoint> members;
186-
if (dataset instanceof FedXDataset) {
187-
// run the query against a selected set of endpoints
188-
FedXDataset ds = (FedXDataset) dataset;
189-
members = federationContext.getEndpointManager().getEndpoints(ds.getEndpoints());
190-
} else {
191-
// evaluate against entire federation
192-
FedX fed = federationContext.getFederation();
193-
members = fed.getMembers();
194-
}
184+
List<Endpoint> members = getMembersFromContext(stats);
195185

196186
// Clone the tuple expression to allow for more aggressive optimizations
197187
TupleExpr clone = expr.clone();
@@ -270,6 +260,34 @@ public TupleExpr optimize(TupleExpr expr, EvaluationStatistics evaluationStatist
270260
return query;
271261
}
272262

263+
/**
264+
* Returns the federation members that are active in the current federation. By default it is all federation
265+
* members. If the passed {@link Dataset} is a {@link FedXDataset}, the defined {@link FedXDataset#getEndpoints()}
266+
* are used.
267+
*
268+
* @param evaluationStatisticss to keep the current query context
269+
* @return
270+
*/
271+
protected List<Endpoint> getMembersFromContext(FederationEvaluationStatistics evaluationStatisticss) {
272+
Dataset queryDataset = evaluationStatisticss.getDataset();
273+
274+
// if the query uses a FROM clause, the external dataset
275+
// is wrapped as primary dataset in FallbackDataset
276+
if (queryDataset instanceof FallbackDataset) {
277+
queryDataset = ((FallbackDataset) queryDataset).getPrimary();
278+
}
279+
280+
if (queryDataset instanceof FedXDataset) {
281+
// run the query against a selected set of endpoints
282+
FedXDataset ds = (FedXDataset) queryDataset;
283+
return federationContext.getEndpointManager().getEndpoints(ds.getEndpoints());
284+
} else {
285+
// evaluate against entire federation
286+
FedX fed = federationContext.getFederation();
287+
return fed.getMembers();
288+
}
289+
}
290+
273291
/**
274292
* Perform source selection for all statements of the query. As a result of this method all statement nodes are
275293
* annotated with their relevant sources.

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.eclipse.rdf4j.model.Statement;
2525
import org.eclipse.rdf4j.model.util.Values;
2626
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
27+
import org.eclipse.rdf4j.model.vocabulary.FOAF;
28+
import org.eclipse.rdf4j.model.vocabulary.RDF;
2729
import org.eclipse.rdf4j.model.vocabulary.RDFS;
2830
import org.eclipse.rdf4j.query.AbstractTupleQueryResultHandler;
2931
import org.eclipse.rdf4j.query.BindingSet;
@@ -35,6 +37,7 @@
3537
import org.eclipse.rdf4j.query.TupleQueryResult;
3638
import org.eclipse.rdf4j.query.TupleQueryResultHandlerException;
3739
import org.eclipse.rdf4j.query.impl.SimpleDataset;
40+
import org.eclipse.rdf4j.repository.Repository;
3841
import org.eclipse.rdf4j.repository.RepositoryConnection;
3942
import org.eclipse.rdf4j.repository.util.Repositories;
4043
import org.junit.jupiter.api.Assertions;
@@ -575,4 +578,83 @@ public void test_EscapingQuotedLiteral() throws Exception {
575578
}
576579

577580
}
581+
582+
@Test
583+
public void test_reduceFederation() throws Exception {
584+
585+
List<Endpoint> endpoints = prepareTest(
586+
Arrays.asList("/tests/basic/data_emptyStore.ttl", "/tests/basic/data_emptyStore.ttl"));
587+
588+
Repository repo1 = getRepository(1);
589+
Repository repo2 = getRepository(2);
590+
591+
String repo1Id = endpoints.get(0).getId();
592+
593+
IRI graph1 = Values.iri("http://example.org/graph1");
594+
IRI graph2 = Values.iri("http://example.org/graph2");
595+
596+
try (RepositoryConnection con = repo1.getConnection()) {
597+
con.add(Values.iri("http://example.org/repo1/p1"), RDF.TYPE, FOAF.PERSON, graph1);
598+
con.add(Values.iri("http://example.org/repo2/p2"), RDF.TYPE, FOAF.PERSON, graph2);
599+
}
600+
601+
try (RepositoryConnection con = repo2.getConnection()) {
602+
con.add(Values.iri("http://example.org/repo2/p3"), RDF.TYPE, FOAF.PERSON, graph1);
603+
}
604+
605+
Repository fedxRepo = fedxRule.getRepository();
606+
607+
// 1: regular federation
608+
try (RepositoryConnection con = fedxRepo.getConnection()) {
609+
TupleQuery tupleQuery = con.prepareTupleQuery(
610+
"PREFIX foaf: <http://xmlns.com/foaf/0.1/> "
611+
+ "SELECT * WHERE { "
612+
+ " ?subClass a foaf:Person. "
613+
+ " } "
614+
);
615+
616+
// expect from both repos
617+
Assertions.assertEquals(3, QueryResults.asSet(tupleQuery.evaluate()).size());
618+
619+
// now we scope it additional to graph1
620+
tupleQuery = con.prepareTupleQuery(
621+
"PREFIX foaf: <http://xmlns.com/foaf/0.1/> "
622+
+ "SELECT * FROM <http://example.org/graph1> WHERE { "
623+
+ " ?subClass a foaf:Person. "
624+
+ " } ");
625+
626+
// expect results defined in graph1 (1 in repo1, 2 from repo2)
627+
Assertions.assertEquals(2, QueryResults.asSet(tupleQuery.evaluate()).size());
628+
}
629+
630+
// 2: reduce to federation member 1 id
631+
// 2a: additionall restrict to named graph
632+
FedXDataset fedXDataset = new FedXDataset(new SimpleDataset());
633+
fedXDataset.addEndpoint(repo1Id);
634+
635+
try (RepositoryConnection con = fedxRepo.getConnection()) {
636+
TupleQuery tupleQuery = con.prepareTupleQuery(
637+
"PREFIX foaf: <http://xmlns.com/foaf/0.1/> "
638+
+ "SELECT * WHERE { "
639+
+ " ?subClass a foaf:Person. "
640+
+ " } "
641+
);
642+
tupleQuery.setDataset(fedXDataset);
643+
644+
// expect result from repo 1
645+
Assertions.assertEquals(2, QueryResults.asSet(tupleQuery.evaluate()).size());
646+
647+
// now we scope it additional to graph1
648+
tupleQuery = con.prepareTupleQuery(
649+
"PREFIX foaf: <http://xmlns.com/foaf/0.1/> "
650+
+ "SELECT * FROM <http://example.org/graph1> WHERE { "
651+
+ " ?subClass a foaf:Person. "
652+
+ " } ");
653+
tupleQuery.setDataset(fedXDataset);
654+
655+
// expect result from graph1 from repo1
656+
Assertions.assertEquals(1, QueryResults.asSet(tupleQuery.evaluate()).size());
657+
}
658+
659+
}
578660
}

0 commit comments

Comments
 (0)