diff --git a/core/sail/elasticsearch/src/main/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndex.java b/core/sail/elasticsearch/src/main/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndex.java index 910c6eb385b..29a2d1e74c5 100644 --- a/core/sail/elasticsearch/src/main/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndex.java +++ b/core/sail/elasticsearch/src/main/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndex.java @@ -577,10 +577,19 @@ protected Iterable query(Resource subject, QuerySpec sp } SearchHits hits; + Integer numDocs = spec.getNumDocs(); if (subject != null) { - hits = search(subject, request, qb); + if (numDocs != null) { + hits = search(subject, request, qb, numDocs); + } else { + hits = search(subject, request, qb); + } } else { - hits = search(request, qb); + if (numDocs != null) { + hits = search(request, qb, numDocs); + } else { + hits = search(request, qb); + } } return Iterables.transform(hits, new Function<>() { @@ -600,11 +609,24 @@ public DocumentScore apply(SearchHit hit) { * @return search hits */ public SearchHits search(Resource resource, SearchRequestBuilder request, QueryBuilder query) { + return search(resource, request, query, -1); + } + + /** + * Evaluates the given query only for the given resource. + * + * @param resource + * @param request + * @param query + * @param numDocs + * @return search hits + */ + public SearchHits search(Resource resource, SearchRequestBuilder request, QueryBuilder query, int numDocs) { // rewrite the query QueryBuilder idQuery = QueryBuilders.termQuery(SearchFields.URI_FIELD_NAME, SearchFields.getResourceID(resource)); QueryBuilder combinedQuery = QueryBuilders.boolQuery().must(idQuery).must(query); - return search(request, combinedQuery); + return search(request, combinedQuery, numDocs); } @Override @@ -712,9 +734,22 @@ private ShapeRelation toSpatialOp(String relation) { * Evaluates the given query and returns the results as a TopDocs instance. */ public SearchHits search(SearchRequestBuilder request, QueryBuilder query) { + return search(request, query, -1); + } + + /** + * Evaluates the given query and returns the results as a TopDocs instance. + */ + public SearchHits search(SearchRequestBuilder request, QueryBuilder query, int numDocs) { String[] types = getTypes(); int nDocs; - if (maxDocs > 0) { + if (numDocs > 0) { + if (maxQueryDocs > 0 && maxQueryDocs < numDocs) { + nDocs = maxQueryDocs; + } else { + nDocs = numDocs; + } + } else if (maxDocs > 0) { nDocs = maxDocs; } else { long docCount = client.prepareSearch(indexName) diff --git a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/AbstractSearchIndex.java b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/AbstractSearchIndex.java index d8f85a4ff58..9423e9dd95f 100644 --- a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/AbstractSearchIndex.java +++ b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/AbstractSearchIndex.java @@ -66,6 +66,7 @@ public abstract class AbstractSearchIndex implements SearchIndex { } protected int maxDocs; + protected int maxQueryDocs; protected Set wktFields = Collections.singleton(SearchFields.getPropertyField(GEO.AS_WKT)); @@ -77,6 +78,8 @@ public abstract class AbstractSearchIndex implements SearchIndex { public void initialize(Properties parameters) throws Exception { String maxDocParam = parameters.getProperty(LuceneSail.MAX_DOCUMENTS_KEY); maxDocs = (maxDocParam != null) ? Integer.parseInt(maxDocParam) : -1; + String maxQueryDocParam = parameters.getProperty(LuceneSail.MAX_QUERY_DOCUMENTS_KEY); + maxQueryDocs = (maxQueryDocParam != null) ? Integer.parseInt(maxQueryDocParam) : maxDocs; String wktFieldParam = parameters.getProperty(LuceneSail.WKT_FIELDS); if (wktFieldParam != null) { diff --git a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSail.java b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSail.java index 37e6e8c0db8..c3b0d90a665 100644 --- a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSail.java +++ b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSail.java @@ -296,6 +296,13 @@ public class LuceneSail extends NotifyingSailWrapper { */ public static final String MAX_DOCUMENTS_KEY = "maxDocuments"; + /** + * Set the key "maxQueryDocuments=<n>" as sail parameter to limit the maximum number of documents the user can + * query at a time to return from a search query. The default is the value of the {@link #MAX_DOCUMENTS_KEY} + * parameter. + */ + public static final String MAX_QUERY_DOCUMENTS_KEY = "maxQueryDocuments"; + /** * Set this key to configure which fields contain WKT and should be spatially indexed. The value should be a * space-separated list of URIs. Default is http://www.opengis.net/ont/geosparql#asWKT. diff --git a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSailSchema.java b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSailSchema.java index 13e5d891818..78d56b24734 100644 --- a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSailSchema.java +++ b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/LuceneSailSchema.java @@ -52,6 +52,8 @@ public class LuceneSailSchema { public static final IRI CONTEXT; + public static final IRI NUM_DOCS; + static { ValueFactory factory = SimpleValueFactory.getInstance(); // compatible with beta4: // creating a new factory @@ -73,5 +75,6 @@ public class LuceneSailSchema { WITHIN_DISTANCE = factory.createIRI(NAMESPACE + "withinDistance"); DISTANCE = factory.createIRI(NAMESPACE + "distance"); CONTEXT = factory.createIRI(NAMESPACE + "context"); + NUM_DOCS = factory.createIRI(NAMESPACE + "numDocs"); } } diff --git a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpec.java b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpec.java index c6886ccd084..0e647c592f7 100644 --- a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpec.java +++ b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpec.java @@ -16,7 +16,9 @@ import java.util.stream.Collectors; import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.algebra.QueryModelNode; import org.eclipse.rdf4j.query.algebra.SingletonSet; import org.eclipse.rdf4j.query.algebra.StatementPattern; @@ -67,21 +69,43 @@ private static void append(Var var, StringBuilder buffer) { private final StatementPattern idPattern; + private final StatementPattern numDocsPattern; + private final Resource subject; private final String matchesVarName; private final String scoreVarName; + private final Integer numDocs; + public QuerySpec(StatementPattern matchesPattern, Collection queryPatterns, StatementPattern scorePattern, StatementPattern typePattern, StatementPattern idPattern, Resource subject) { + this(matchesPattern, queryPatterns, scorePattern, typePattern, idPattern, null, subject); + } + + public QuerySpec(StatementPattern matchesPattern, Collection queryPatterns, + StatementPattern scorePattern, StatementPattern typePattern, + StatementPattern idPattern, StatementPattern numDocsPattern, Resource subject) { this.matchesPattern = matchesPattern; this.queryPatterns = queryPatterns; this.scorePattern = scorePattern; this.typePattern = typePattern; this.idPattern = idPattern; + this.numDocsPattern = numDocsPattern; this.subject = subject; + if (numDocsPattern != null) { + Value val = numDocsPattern.getObjectVar().getValue(); + if (val != null && val.isLiteral()) { + this.numDocs = ((Literal) val).intValue(); + } else { + throw new IllegalArgumentException("numDocs should be constant literal value"); + } + } else { + this.numDocs = null; + } + if (matchesPattern != null) { this.matchesVarName = matchesPattern.getSubjectVar().getName(); } else { @@ -101,9 +125,11 @@ public QuerySpec(String matchesVarName, String propertyVarName, String scoreVarN this.matchesPattern = null; this.scorePattern = null; this.typePattern = null; + this.numDocsPattern = null; this.queryPatterns = Set.of(); this.idPattern = null; this.subject = subject; + this.numDocs = null; } @Override @@ -121,6 +147,7 @@ public QueryModelNode removeQueryPatterns() { replace(getScorePattern(), replacement); replace(getTypePattern(), replacement); replace(getIdPattern(), replacement); + replace(getNumDocsPattern(), replacement); final QueryModelNode placeholder = new SingletonSet(); @@ -154,6 +181,10 @@ public StatementPattern getScorePattern() { return scorePattern; } + public StatementPattern getNumDocsPattern() { + return numDocsPattern; + } + /** * The variable name associated with the query score * @@ -163,6 +194,10 @@ public String getScoreVariableName() { return scoreVarName; } + public Integer getNumDocs() { + return numDocs; + } + public StatementPattern getTypePattern() { return typePattern; } diff --git a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilder.java b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilder.java index c62a30266ab..73c88dc10f6 100644 --- a/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilder.java +++ b/core/sail/lucene-api/src/main/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilder.java @@ -15,6 +15,7 @@ import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.INDEXID; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.LUCENE_QUERY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.MATCHES; +import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.NUM_DOCS; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.PROPERTY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.QUERY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.SCORE; @@ -152,7 +153,7 @@ public void process(TupleExpr tupleExpr, BindingSet bindings, Collection queryPatterns; try { @@ -161,6 +162,7 @@ public void process(TupleExpr tupleExpr, BindingSet bindings, Collection boostPatterns = new ArrayList<>(); + public ArrayList numDocsPatterns = new ArrayList<>(); + /** * Method implementing the visitor pattern that gathers all statements using a predicate from the LuceneSail's * namespace. @@ -487,6 +496,8 @@ public void meet(StatementPattern node) { idPatterns.add(node); } else if (BOOST.equals(predicate)) { boostPatterns.add(node); + } else if (NUM_DOCS.equals(predicate)) { + numDocsPatterns.add(node); } else if (TYPE.equals(predicate)) { Value object = node.getObjectVar().getValue(); if (LUCENE_QUERY.equals(object)) { diff --git a/core/sail/lucene-api/src/test/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilderTest.java b/core/sail/lucene-api/src/test/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilderTest.java index 89c474e0d44..d7009f4f909 100644 --- a/core/sail/lucene-api/src/test/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilderTest.java +++ b/core/sail/lucene-api/src/test/java/org/eclipse/rdf4j/sail/lucene/QuerySpecBuilderTest.java @@ -14,6 +14,7 @@ import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.BOOST; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.LUCENE_QUERY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.MATCHES; +import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.NUM_DOCS; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.QUERY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.SCORE; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.SNIPPET; @@ -55,6 +56,7 @@ public void testQueryInterpretation() { "<" + TYPE + "> <" + LUCENE_QUERY + ">; " + "<" + QUERY + "> \"my Lucene query\"; " + "<" + SCORE + "> ?Score; " + + "<" + NUM_DOCS + "> 76; " + "<" + SNIPPET + "> ?Snippet ]. } "; ParsedQuery query = parser.parseQuery(buffer, null); TupleExpr tupleExpr = query.getTupleExpr(); @@ -69,6 +71,8 @@ public void testQueryInterpretation() { assertEquals("Score", querySpec.getScorePattern().getObjectVar().getName()); assertEquals("Snippet", param.getSnippetPattern().getObjectVar().getName()); assertEquals(LUCENE_QUERY, querySpec.getTypePattern().getObjectVar().getValue()); + assertEquals(76, querySpec.getNumDocs()); + assertEquals(76, ((Literal) querySpec.getNumDocsPattern().getObjectVar().getValue()).intValue()); assertEquals("my Lucene query", param.getQuery()); assertNull(querySpec.getSubject()); } @@ -80,11 +84,13 @@ public void testMultipleQueriesInterpretation() { "<" + TYPE + "> <" + LUCENE_QUERY + ">; " + "<" + QUERY + "> \"my Lucene query\"; " + "<" + SCORE + "> ?score1; " + + "<" + NUM_DOCS + "> 86; " + "<" + SNIPPET + "> ?snippet1 ]. " + " ?sub2 <" + MATCHES + "> [ " + "<" + TYPE + "> <" + LUCENE_QUERY + ">; " + "<" + QUERY + "> \"second lucene query\"; " + "<" + SCORE + "> ?score2; " + + "<" + NUM_DOCS + "> 13; " + "<" + SNIPPET + "> ?snippet2 ]. " + // and connect them both via any X in between, just as salt to make the // parser do something @@ -103,6 +109,7 @@ public void testMultipleQueriesInterpretation() { // Matched the first assertEquals("sub1", querySpec.getMatchesPattern().getSubjectVar().getName()); assertEquals(1, querySpec.getQueryPatterns().size()); + assertEquals(86, querySpec.getNumDocs()); QuerySpec.QueryParam param = querySpec.getQueryPatterns().iterator().next(); assertEquals("my Lucene query", ((Literal) param.getQueryPattern().getObjectVar().getValue()).getLabel()); @@ -116,6 +123,7 @@ public void testMultipleQueriesInterpretation() { // and the second assertEquals("sub2", querySpec.getMatchesPattern().getSubjectVar().getName()); assertEquals(1, querySpec.getQueryPatterns().size()); + assertEquals(13, querySpec.getNumDocs()); QuerySpec.QueryParam param = querySpec.getQueryPatterns().iterator().next(); assertEquals("second lucene query", ((Literal) param.getQueryPattern().getObjectVar().getValue()).getLabel()); diff --git a/core/sail/lucene/src/main/java/org/eclipse/rdf4j/sail/lucene/impl/LuceneIndex.java b/core/sail/lucene/src/main/java/org/eclipse/rdf4j/sail/lucene/impl/LuceneIndex.java index 42174324308..5a4965b088d 100644 --- a/core/sail/lucene/src/main/java/org/eclipse/rdf4j/sail/lucene/impl/LuceneIndex.java +++ b/core/sail/lucene/src/main/java/org/eclipse/rdf4j/sail/lucene/impl/LuceneIndex.java @@ -751,11 +751,21 @@ protected Iterable query(Resource subject, QuerySpec sp highlighter = null; } + Integer numDocs = spec.getNumDocs(); + TopDocs docs; if (subject != null) { - docs = search(subject, q); + if (numDocs != null) { + docs = search(subject, q, numDocs); + } else { + docs = search(subject, q); + } } else { - docs = search(q); + if (numDocs != null) { + docs = search(q, numDocs); + } else { + docs = search(q); + } } return Iterables.transform(Arrays.asList(docs.scoreDocs), (ScoreDoc doc) -> new LuceneDocumentScore(doc, highlighter, LuceneIndex.this)); @@ -960,12 +970,25 @@ public synchronized String getSnippet(String fieldName, String text, Highlighter * @throws IOException */ public synchronized TopDocs search(Resource resource, Query query) throws IOException { + return search(resource, query, -1); + } + + /** + * Evaluates the given query only for the given resource. + * + * @param resource + * @param query + * @param numDocs + * @return top documents + * @throws IOException + */ + public synchronized TopDocs search(Resource resource, Query query, int numDocs) throws IOException { // rewrite the query TermQuery idQuery = new TermQuery(new Term(SearchFields.URI_FIELD_NAME, SearchFields.getResourceID(resource))); BooleanQuery.Builder combinedQuery = new BooleanQuery.Builder(); combinedQuery.add(idQuery, Occur.MUST); combinedQuery.add(query, Occur.MUST); - return search(combinedQuery.build()); + return search(combinedQuery.build(), numDocs); } /** @@ -976,8 +999,26 @@ public synchronized TopDocs search(Resource resource, Query query) throws IOExce * @throws IOException */ public synchronized TopDocs search(Query query) throws IOException { + return search(query, -1); + } + + /** + * Evaluates the given query and returns the results as a TopDocs instance. + * + * @param query + * @param numDocs + * @return top documents + * @throws IOException + */ + public synchronized TopDocs search(Query query, int numDocs) throws IOException { int nDocs; - if (maxDocs > 0) { + if (numDocs > 0) { + if (maxQueryDocs > 0 && maxQueryDocs < numDocs) { + nDocs = maxQueryDocs; + } else { + nDocs = numDocs; + } + } else if (maxDocs > 0) { nDocs = maxDocs; } else { nDocs = Math.max(getIndexReader().numDocs(), 1); diff --git a/core/sail/solr/src/main/java/org/eclipse/rdf4j/sail/solr/SolrIndex.java b/core/sail/solr/src/main/java/org/eclipse/rdf4j/sail/solr/SolrIndex.java index 31cdfb3a440..34ea7b799a8 100644 --- a/core/sail/solr/src/main/java/org/eclipse/rdf4j/sail/solr/SolrIndex.java +++ b/core/sail/solr/src/main/java/org/eclipse/rdf4j/sail/solr/SolrIndex.java @@ -317,11 +317,20 @@ protected Iterable query(Resource subject, QuerySpec sp q.addField(SearchFields.URI_FIELD_NAME); } q.addField("score"); + Integer numDocs = spec.getNumDocs(); try { if (subject != null) { - response = search(subject, q); + if (numDocs != null) { + response = search(subject, q, numDocs); + } else { + response = search(subject, q); + } } else { - response = search(q); + if (numDocs != null) { + response = search(q, numDocs); + } else { + response = search(q); + } } } catch (SolrServerException e) { throw new IOException(e); @@ -346,10 +355,25 @@ protected Iterable query(Resource subject, QuerySpec sp * @throws IOException */ public QueryResponse search(Resource resource, SolrQuery query) throws SolrServerException, IOException { + return search(resource, query, -1); + } + + /** + * Evaluates the given query only for the given resource. + * + * @param resource + * @param query + * @param numDocs + * @return response + * @throws SolrServerException + * @throws IOException + */ + public QueryResponse search(Resource resource, SolrQuery query, int numDocs) + throws SolrServerException, IOException { // rewrite the query String idQuery = termQuery(SearchFields.URI_FIELD_NAME, SearchFields.getResourceID(resource)); query.setQuery(query.getQuery() + " AND " + idQuery); - return search(query); + return search(query, numDocs); } @Override @@ -553,8 +577,27 @@ public double getY() { * @throws IOException */ public QueryResponse search(SolrQuery query) throws SolrServerException, IOException { + return search(query, -1); + } + + /** + * Evaluates the given query and returns the results as a TopDocs instance. + * + * @param query + * @param numDocs + * @return query response + * @throws SolrServerException + * @throws IOException + */ + public QueryResponse search(SolrQuery query, int numDocs) throws SolrServerException, IOException { int nDocs; - if (maxDocs > 0) { + if (numDocs > 0) { + if (maxQueryDocs > 0 && maxQueryDocs < numDocs) { + nDocs = maxQueryDocs; + } else { + nDocs = numDocs; + } + } else if (maxDocs > 0) { nDocs = maxDocs; } else { long docCount = client.query(query.setRows(0)).getResults().getNumFound(); diff --git a/testsuites/lucene/src/main/java/org/eclipse/testsuite/rdf4j/sail/lucene/AbstractLuceneSailTest.java b/testsuites/lucene/src/main/java/org/eclipse/testsuite/rdf4j/sail/lucene/AbstractLuceneSailTest.java index d17d17b353a..84391587d15 100644 --- a/testsuites/lucene/src/main/java/org/eclipse/testsuite/rdf4j/sail/lucene/AbstractLuceneSailTest.java +++ b/testsuites/lucene/src/main/java/org/eclipse/testsuite/rdf4j/sail/lucene/AbstractLuceneSailTest.java @@ -11,6 +11,7 @@ package org.eclipse.testsuite.rdf4j.sail.lucene; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.MATCHES; +import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.NUM_DOCS; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.PROPERTY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.QUERY; import static org.eclipse.rdf4j.sail.lucene.LuceneSailSchema.SCORE; @@ -21,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -33,6 +35,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Literal; @@ -53,6 +56,7 @@ import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.RepositoryException; import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.util.Repositories; import org.eclipse.rdf4j.sail.lucene.LuceneSail; import org.eclipse.rdf4j.sail.lucene.LuceneSailSchema; import org.eclipse.rdf4j.sail.memory.MemoryStore; @@ -107,17 +111,19 @@ public abstract class AbstractLuceneSailTest { protected abstract void configure(LuceneSail sail); - @BeforeEach - public void setUp() { - // set logging, uncomment this to get better logging for debugging - // org.apache.log4j.BasicConfigurator.configure(); - + private void createTestSail(Consumer config) throws IOException { + if (repository != null) { + repository.shutDown(); + repository = null; + } // setup a LuceneSail MemoryStore memoryStore = new MemoryStore(); // enable lock tracking org.eclipse.rdf4j.common.concurrent.locks.Properties.setLockTrackingEnabled(true); sail = new LuceneSail(); + configure(sail); + config.accept(sail); sail.setBaseSail(memoryStore); // create a Repository wrapping the LuceneSail @@ -139,10 +145,19 @@ public void setUp() { } } + @BeforeEach + public void setUp() throws Exception { + // set logging, uncomment this to get better logging for debugging + // org.apache.log4j.BasicConfigurator.configure(); + createTestSail(lc -> { + }); + } + @AfterEach public void tearDown() throws RepositoryException { if (repository != null) { repository.shutDown(); + repository = null; } org.eclipse.rdf4j.common.concurrent.locks.Properties.setLockTrackingEnabled(false); } @@ -1081,6 +1096,67 @@ public void run() { assertEquals(0, exceptions.size(), "Exceptions occurred during testMultithreadedAdd, see stacktraces above"); } + @Test + public void testMaxNumDocsResult() throws IOException { + for (int i = 1; i <= 3; i++) { + final int j = i; + createTestSail(lc -> lc.setParameter(LuceneSail.MAX_QUERY_DOCUMENTS_KEY, String.valueOf(j))); + Repositories.consumeNoTransaction(repository, conn -> { + try (TupleQueryResult res = conn.prepareTupleQuery( + "SELECT ?Resource {\n" + + " ?Resource <" + MATCHES + "> [\n " + + " <" + QUERY + "> \"one\";\n " + + " <" + NUM_DOCS + "> 3;\n " + + " ]. } " + ).evaluate()) { + for (int k = 0; k < j; k++) { + assertTrue(res.hasNext(), "missing result #" + k); + res.next(); + } + if (res.hasNext()) { + StringBuilder b = new StringBuilder(); + int r = 0; + do { + b.append("\n#").append(r++).append(res.next()); + } while (res.hasNext()); + fail("can't have more than " + j + " result(s)" + b); + } + } + ; + }); + } + } + + @Test + public void testNumDocsResult() { + for (int i = 1; i <= 3; i++) { + final int j = i; + Repositories.consumeNoTransaction(repository, conn -> { + try (TupleQueryResult res = conn.prepareTupleQuery( + "SELECT ?Resource {\n" + + " ?Resource <" + MATCHES + "> [\n " + + " <" + QUERY + "> \"one\";\n " + + " <" + NUM_DOCS + "> " + j + ";\n " + + " ]. } " + ).evaluate()) { + for (int k = 0; k < j; k++) { + assertTrue(res.hasNext(), "missing result #" + k); + res.next(); + } + if (res.hasNext()) { + StringBuilder b = new StringBuilder(); + int r = 0; + do { + b.append("\n#").append(r++).append(res.next()); + } while (res.hasNext()); + fail("can't have more than " + j + " result(s)" + b); + } + } + ; + }); + } + } + protected void assertQueryResult(String literal, IRI predicate, Resource resultUri) { try (RepositoryConnection connection = repository.getConnection()) { // fire a query for all subjects with a given term