Skip to content

Commit 517353e

Browse files
authored
GH-4920 SPARQLConnection.size() now uses count query (#4972)
2 parents 08a518a + a01fab9 commit 517353e

2 files changed

Lines changed: 103 additions & 6 deletions

File tree

core/repository/sparql/src/main/java/org/eclipse/rdf4j/repository/sparql/SPARQLConnection.java

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import java.io.InputStream;
1818
import java.io.Reader;
1919
import java.net.URL;
20+
import java.util.Arrays;
2021
import java.util.Objects;
22+
import java.util.stream.Collectors;
2123

2224
import org.apache.http.client.HttpClient;
2325
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
@@ -39,6 +41,8 @@
3941
import org.eclipse.rdf4j.model.impl.DynamicModelFactory;
4042
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
4143
import org.eclipse.rdf4j.model.util.Literals;
44+
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
45+
import org.eclipse.rdf4j.model.vocabulary.SESAME;
4246
import org.eclipse.rdf4j.query.BindingSet;
4347
import org.eclipse.rdf4j.query.BooleanQuery;
4448
import org.eclipse.rdf4j.query.GraphQuery;
@@ -79,6 +83,8 @@
7983
*/
8084
public class SPARQLConnection extends AbstractRepositoryConnection implements HttpClientDependent {
8185

86+
private static final String COUNT_EVERYTHING = "SELECT (COUNT(*) AS ?count) WHERE { ?s ?p ?o }";
87+
8288
private static final String EVERYTHING = "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }";
8389

8490
private static final String EVERYTHING_WITH_GRAPH = "SELECT * WHERE { ?s ?p ?o . OPTIONAL { GRAPH ?ctx { ?s ?p ?o } } }";
@@ -281,16 +287,61 @@ public boolean isEmpty() throws RepositoryException {
281287

282288
@Override
283289
public long size(Resource... contexts) throws RepositoryException {
284-
try (RepositoryResult<Statement> stmts = getStatements(null, null, null, true, contexts)) {
285-
long i = 0;
286-
while (stmts.hasNext()) {
287-
stmts.next();
288-
i++;
290+
String query = sizeAsTupleQuery(contexts);
291+
TupleQuery tq = prepareTupleQuery(SPARQL, query);
292+
try (TupleQueryResult res = tq.evaluate()) {
293+
if (res.hasNext()) {
294+
295+
Value value = res.next().getBinding("count").getValue();
296+
if (value instanceof Literal) {
297+
return ((Literal) value).longValue();
298+
} else {
299+
return 0;
300+
}
301+
}
302+
} catch (QueryEvaluationException e) {
303+
throw new RepositoryException(e);
304+
}
305+
return 0;
306+
}
307+
308+
String sizeAsTupleQuery(Resource... contexts) {
309+
310+
// in case the context is null we want the
311+
// default graph of the remote store i.e. ask without graph/from.
312+
if (contexts != null && isQuadMode() && contexts.length > 0) {
313+
// this is an optimization for the case that we can use a GRAPH instead of a FROM.
314+
if (contexts.length == 1 && isExposableGraphIri(contexts[0])) {
315+
return "SELECT (COUNT(*) AS ?count) WHERE { GRAPH <" + contexts[0].stringValue()
316+
+ "> { ?s ?p ?o}}";
317+
} else {
318+
// If we had an default graph setting that is sesame/rdf4j specific
319+
// we must drop it before sending it over the wire. Otherwise
320+
// gather up the given contexts and send them as a from clauses
321+
// to make the matching dataset.
322+
String graphs = Arrays.stream(contexts)
323+
.filter(SPARQLConnection::isExposableGraphIri)
324+
.map(Resource::stringValue)
325+
.map(s -> "FROM <" + s + ">")
326+
.collect(Collectors.joining(" "));
327+
return "SELECT (COUNT(*) AS ?count) " + graphs + "WHERE { ?s ?p ?o}";
289328
}
290-
return i;
329+
} else {
330+
return COUNT_EVERYTHING;
291331
}
292332
}
293333

334+
/**
335+
* For the sparql protocol a context must be an IRI However we can't send out the RDF4j internal default graph IRIs
336+
*
337+
* @param resource to test if it can be the IRI for a named graph
338+
* @return true if it the input can be a foreign named graph.
339+
*/
340+
private static boolean isExposableGraphIri(Resource resource) {
341+
// We use the instanceof test to avoid any issue with a null pointer.
342+
return resource instanceof IRI && !RDF4J.NIL.equals(resource) && !SESAME.NIL.equals(resource);
343+
}
344+
294345
@Override
295346
public RepositoryResult<Statement> getStatements(Resource subj, IRI pred, Value obj, boolean includeInferred,
296347
Resource... contexts) throws RepositoryException {

core/repository/sparql/src/test/java/org/eclipse/rdf4j/repository/sparql/SPARQLConnectionTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,41 @@
1212

1313
import static org.assertj.core.api.Assertions.assertThat;
1414
import static org.eclipse.rdf4j.model.util.Values.iri;
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.jupiter.api.Assertions.assertFalse;
17+
import static org.junit.jupiter.api.Assertions.assertNotNull;
1518
import static org.mockito.ArgumentMatchers.any;
1619
import static org.mockito.ArgumentMatchers.anyBoolean;
1720
import static org.mockito.ArgumentMatchers.anyInt;
21+
import static org.mockito.Mockito.atLeastOnce;
1822
import static org.mockito.Mockito.mock;
1923
import static org.mockito.Mockito.never;
2024
import static org.mockito.Mockito.times;
2125
import static org.mockito.Mockito.verify;
26+
import static org.mockito.Mockito.when;
27+
28+
import java.lang.ref.WeakReference;
2229

2330
import org.eclipse.rdf4j.http.client.SPARQLProtocolSession;
2431
import org.eclipse.rdf4j.model.IRI;
2532
import org.eclipse.rdf4j.model.ValueFactory;
2633
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
2734
import org.eclipse.rdf4j.model.vocabulary.FOAF;
2835
import org.eclipse.rdf4j.model.vocabulary.RDF;
36+
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
2937
import org.eclipse.rdf4j.model.vocabulary.RDFS;
38+
import org.eclipse.rdf4j.query.impl.MapBindingSet;
39+
import org.eclipse.rdf4j.query.impl.SimpleBinding;
40+
import org.eclipse.rdf4j.query.impl.TupleQueryResultBuilder;
41+
import org.eclipse.rdf4j.query.parser.ParsedQuery;
42+
import org.eclipse.rdf4j.query.parser.sparql.SPARQLParser;
43+
import org.eclipse.rdf4j.query.parser.sparql.SPARQLParserFactory;
3044
import org.eclipse.rdf4j.rio.ParserConfig;
3145
import org.junit.jupiter.api.BeforeEach;
3246
import org.junit.jupiter.api.Test;
3347
import org.mockito.ArgumentCaptor;
48+
import org.mockito.Mock;
49+
import org.mockito.invocation.InvocationOnMock;
3450

3551
public class SPARQLConnectionTest {
3652

@@ -100,6 +116,36 @@ public void testAddSingleContextHandling() throws Exception {
100116
assertThat(sparqlUpdate).containsPattern(expectedAddPattern).containsPattern(expectedRemovePattern);
101117
}
102118

119+
@Test
120+
public void testSizeQuery() throws Exception {
121+
122+
String sizeAsTupleQuery = subject.sizeAsTupleQuery();
123+
ParsedQuery query = new SPARQLParserFactory().getParser().parseQuery(sizeAsTupleQuery, "http://example.org/");
124+
assertNotNull(query);
125+
126+
sizeAsTupleQuery = subject.sizeAsTupleQuery(vf.createIRI("urn:g1"));
127+
query = new SPARQLParserFactory().getParser().parseQuery(sizeAsTupleQuery, "http://example.org/");
128+
assertNotNull(query);
129+
130+
sizeAsTupleQuery = subject.sizeAsTupleQuery(vf.createIRI("urn:g1"), vf.createIRI("urn:g2"));
131+
query = new SPARQLParserFactory().getParser().parseQuery(sizeAsTupleQuery, "http://example.org/");
132+
assertNotNull(query);
133+
134+
sizeAsTupleQuery = subject.sizeAsTupleQuery(vf.createIRI("urn:g1"), vf.createBNode());
135+
query = new SPARQLParserFactory().getParser().parseQuery(sizeAsTupleQuery, "http://example.org/");
136+
assertNotNull(query);
137+
138+
sizeAsTupleQuery = subject.sizeAsTupleQuery(RDF4J.NIL);
139+
query = new SPARQLParserFactory().getParser().parseQuery(sizeAsTupleQuery, "http://example.org/");
140+
assertNotNull(query);
141+
assertFalse(sizeAsTupleQuery.contains("nil"));
142+
143+
sizeAsTupleQuery = subject.sizeAsTupleQuery(null);
144+
query = new SPARQLParserFactory().getParser().parseQuery(sizeAsTupleQuery, "http://example.org/");
145+
146+
assertNotNull(query);
147+
}
148+
103149
@Test
104150
public void testAddMultipleContextHandling() throws Exception {
105151
ArgumentCaptor<String> sparqlUpdateCaptor = ArgumentCaptor.forClass(String.class);

0 commit comments

Comments
 (0)