Skip to content

Commit fd3b92c

Browse files
committed
Apply limit and offset to IndexProvider.totals method
Fixes #3571 Signed-off-by: Oleksandr Porunov <alexandr.porunov@gmail.com>
1 parent 245e79f commit fd3b92c

File tree

9 files changed

+67
-26
lines changed

9 files changed

+67
-26
lines changed

docs/changelog.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,34 @@ For more information on features and bug fixes in 1.0.0, see the GitHub mileston
110110
The index management has received an overhaul which enables proper index removal.
111111
The schema action `REMOVE_INDEX` is no longer available and has been replaced by `DISCARD_INDEX`.
112112

113+
##### `totals` for direct index queries now applies provided offset and limit
114+
115+
Direct index queries which search count for totals (`vertexTotals`, `edgeTotals`, `propertyTotals` and direct execution of
116+
`IndexProvider.totals`) now apply provided `limit` and `offset`. Previously provided `limit` and `offset` were ignored.
117+
For example, previously the following query would return `500` if there were `500` indexed elements:
118+
119+
```groovy
120+
gremlin> graph.indexQuery("textIndex", "v.\"text\":fooBar").limit(10).vertexTotals()
121+
```
122+
123+
Now the above query will return `10` elements because we limited the result to `10` elements only.
124+
Same applies to `offset`. Previously the following query would return `500` if there were `500` indexed elements:
125+
126+
```groovy
127+
gremlin> graph.indexQuery("textIndex", "v.\"text\":fooBar").offset(10).vertexTotals()
128+
```
129+
130+
Now the above query will return `490` as a result because we skip count of the first `10` elements.
131+
`offset` provided with `limit` will apply `offset` first and `limit` last.
132+
For example, the above query will return `10` elements now if there were `500` indexed elements:
133+
134+
```groovy
135+
gremlin> graph.indexQuery("textIndex", "v.\"text\":fooBar").limit(10).offset(10).vertexTotals()
136+
```
137+
138+
The new logic is applied similarly to Direct Index Queries `vertexTotals()`, `edgeTotals()`, `propertyTotals()` as well
139+
as internal JanusGraph method `IndexProvider.totals`.
140+
113141
##### Add support for Java 11
114142

115143
JanusGraph now officially supports Java 11 in addition to Java 8. We encourage everyone to update to Java 11.

docs/index-backend/direct-index-query.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,18 @@ retrieving and ranking all documents. This shortcut is exposed through
8787
the ".vertexTotals()", ".edgeTotals()", and ".propertyTotals()" methods.
8888

8989
The totals can be retrieved using the same query syntax as the
90-
indexQuery builder, but size is overwritten to be 0.
90+
indexQuery builder.
9191
```groovy
9292
graph.indexQuery("vertexByText", "v.text:(farm uncle berry)").vertexTotals()
9393
```
9494

95+
!!! note
96+
In JanusGraph versions earlier than 1.0.0 provided `limit` and `offset` was ignored
97+
for any `totals` queries. Starting from JanusGraph 1.0.0 provided `limit` and `offset` are
98+
applied to the final `totals` result (`vertexTotals()`, `edgeTotals()`, `propertyTotals()` as well
99+
as internal JanusGraph method `IndexProvider.totals`). This was made to have consistent behaviour between
100+
`totals` and `stream` direct index queries.
101+
95102
## Gotchas
96103

97104
### Property Key Names

janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphIndexTest.java

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,12 +1938,14 @@ public void testRawQueries() {
19381938
assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "v.text:(beautiful are ducks)").vertexStream());
19391939
assertCount(numV / strings.length * 2 - 10, graph.indexQuery(VINDEX, "v.text:(beautiful are ducks)").offset(10).vertexStream());
19401940
long total = graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(Integer.MAX_VALUE).vertexStream().count();
1941-
assertCount(10, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).vertexStream());
1942-
assertEquals(total, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).vertexTotals());
1943-
assertCount(10, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(10).vertexStream());
1944-
assertEquals(total, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(10).vertexTotals());
1945-
assertCount(0, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(numV).vertexStream());
1946-
assertEquals(total, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(numV).vertexTotals());
1941+
int limit = 10;
1942+
long expectedLimitedCount = Math.min(total, limit);
1943+
assertCount(expectedLimitedCount, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(limit).vertexStream());
1944+
assertEquals(expectedLimitedCount, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(limit).vertexTotals());
1945+
assertCount(expectedLimitedCount, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(limit).offset(10).vertexStream());
1946+
assertEquals(expectedLimitedCount, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(limit).offset(10).vertexTotals());
1947+
assertCount(0, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(limit).offset(numV).vertexStream());
1948+
assertEquals(0, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(limit).offset(numV).vertexTotals());
19471949
//Test name mapping
19481950
assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "xtext:ducks").vertexStream());
19491951
assertCount(0, graph.indexQuery(VINDEX, "text:ducks").vertexStream());
@@ -1954,36 +1956,38 @@ public void testRawQueries() {
19541956
//Same queries for edges
19551957
assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:ducks").edgeStream());
19561958
total = graph.indexQuery(EINDEX, "e.text:ducks").limit(Integer.MAX_VALUE).edgeStream().count();
1959+
expectedLimitedCount = Math.min(total, limit);
19571960
assertEquals(total, (long) numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:ducks").edgeTotals());
19581961
assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:(farm uncle berry)").edgeStream());
19591962
assertCount(numV / strings.length, graph.indexQuery(EINDEX, "e.text:(farm uncle berry) AND e.name:\"Uncle Berry has a farm\"").edgeStream());
19601963
assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:(beautiful are ducks)").edgeStream());
19611964
assertCount(numV / strings.length * 2 - 10, graph.indexQuery(EINDEX, "e.text:(beautiful are ducks)").offset(10).edgeStream());
19621965
total = graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(Integer.MAX_VALUE).edgeStream().count();
1963-
assertCount(10, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).edgeStream());
1964-
assertEquals(total, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).edgeTotals());
1965-
assertCount(10, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(10).edgeStream());
1966-
assertEquals(total, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(10).edgeTotals());
1967-
assertCount(0, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(numV).edgeStream());
1968-
assertEquals(total, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(numV).edgeTotals());
1966+
assertCount(expectedLimitedCount, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(limit).edgeStream());
1967+
assertEquals(expectedLimitedCount, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(limit).edgeTotals());
1968+
assertCount(expectedLimitedCount, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(limit).offset(10).edgeStream());
1969+
assertEquals(expectedLimitedCount, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(limit).offset(10).edgeTotals());
1970+
assertCount(0, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(limit).offset(numV).edgeStream());
1971+
assertEquals(0, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(limit).offset(numV).edgeTotals());
19691972
//Test name mapping
19701973
assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "text:ducks").edgeStream());
19711974

19721975
//Same queries for properties
19731976
assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:ducks").propertyStream());
19741977
total = graph.indexQuery(PINDEX, "p.text:ducks").limit(Integer.MAX_VALUE).propertyStream().count();
1978+
expectedLimitedCount = Math.min(total, limit);
19751979
assertEquals(total, (long) numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:ducks").propertyTotals());
19761980
assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:(farm uncle berry)").propertyStream());
19771981
assertCount(numV / strings.length, graph.indexQuery(PINDEX, "p.text:(farm uncle berry) AND p.name:\"Uncle Berry has a farm\"").propertyStream());
19781982
assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:(beautiful are ducks)").propertyStream());
19791983
assertCount(numV / strings.length * 2 - 10, graph.indexQuery(PINDEX, "p.text:(beautiful are ducks)").offset(10).propertyStream());
19801984
total = graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(Integer.MAX_VALUE).propertyStream().count();
1981-
assertCount(10, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).propertyStream());
1982-
assertEquals(total, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).propertyTotals());
1983-
assertCount(10, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(10).propertyStream());
1984-
assertEquals(total, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(10).propertyTotals());
1985-
assertCount(0, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(numV).propertyStream());
1986-
assertEquals(total, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(numV).propertyTotals());
1985+
assertCount(expectedLimitedCount, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(limit).propertyStream());
1986+
assertEquals(expectedLimitedCount, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(limit).propertyTotals());
1987+
assertCount(expectedLimitedCount, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(limit).offset(10).propertyStream());
1988+
assertEquals(expectedLimitedCount, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(limit).offset(10).propertyTotals());
1989+
assertCount(0, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(limit).offset(numV).propertyStream());
1990+
assertEquals(0, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(limit).offset(numV).propertyTotals());
19871991
//Test name mapping
19881992
assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "text:ducks").propertyStream());
19891993
}

janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ static void checkKeyValidity(String key) {
112112
Stream<RawQuery.Result<String>> query(RawQuery query, KeyInformation.IndexRetriever information, BaseTransaction tx) throws BackendException;
113113

114114
/**
115-
* Executes the given raw query against the index and returns the total hits. e.g. limit=0
115+
* Executes the given raw query against the index and returns the total hits after specified offset and in limit scope if specified.
116116
*
117117
* @param query Query to execute
118118
* @param information Information on the keys used in the query accessible through {@link KeyInformation.IndexRetriever}.

janusgraph-core/src/main/java/org/janusgraph/graphdb/query/QueryUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,10 @@ public static long applyQueryLimitAfterCount(long count, Query query){
407407
return Math.max(0L, (query.hasLimit()) ? Math.min(count, query.getLimit()) : count);
408408
}
409409

410+
public static long applyOffsetWithQueryLimitAfterCount(long count, int offset, Query query){
411+
return applyQueryLimitAfterCount((offset > 0) ? count - offset : count, query);
412+
}
413+
410414
public interface IndexCall<R> {
411415

412416
Collection<R> call(int limit);

janusgraph-core/src/main/java/org/janusgraph/graphdb/query/graph/IndexQueryBuilder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ private <E extends JanusGraphElement> Stream<Result<E>> execute(ElementCategory
216216
private Long executeTotals(ElementCategory resultType) {
217217
Preconditions.checkNotNull(indexName);
218218
Preconditions.checkNotNull(query);
219-
this.setLimit(0);
220219
if (tx.hasModifications())
221220
log.warn("Modifications in this transaction might not be accurately reflected in this index query: {}",query);
222221
return serializer.executeTotals(this,resultType,tx.getTxHandle(),tx);

janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchIndex.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1285,9 +1285,10 @@ private ElasticSearchResponse runCommonQuery(RawQuery query, KeyInformation.Inde
12851285

12861286
private long runCountQuery(RawQuery query) throws BackendException{
12871287
try {
1288-
return client.countTotal(
1288+
long countTotal = client.countTotal(
12891289
getIndexStoreName(query.getStore()),
12901290
compat.createRequestBody(compat.queryString(query.getQuery()), query.getParameters()));
1291+
return QueryUtil.applyOffsetWithQueryLimitAfterCount(countTotal, query.getOffset(), query);
12911292
} catch (final IOException | UncheckedIOException e) {
12921293
throw new PermanentBackendException(e);
12931294
}

janusgraph-lucene/src/main/java/org/janusgraph/diskstorage/lucene/LuceneIndex.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,11 +1029,9 @@ public Long totals(RawQuery query, KeyInformation.IndexRetriever information, Ba
10291029

10301030
final long time = System.currentTimeMillis();
10311031
// Lucene doesn't like limits of 0. Also, it doesn't efficiently build a total list.
1032-
query.setLimit(1);
1033-
// We ignore offset and limit for totals
10341032
final TopDocs docs = searcher.search(q, 1);
10351033
log.debug("Executed query [{}] in {} ms", q, System.currentTimeMillis() - time);
1036-
return docs.totalHits.value;
1034+
return QueryUtil.applyOffsetWithQueryLimitAfterCount(docs.totalHits.value, query.getOffset(), query);
10371035
} catch (final IOException e) {
10381036
throw new TemporaryBackendException("Could not execute Lucene query", e);
10391037
}

janusgraph-solr/src/main/java/org/janusgraph/diskstorage/solr/SolrIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ public Long totals(RawQuery query, KeyInformation.IndexRetriever information,
802802
final QueryResponse response = solrClient.query(collection, runCommonQuery(query, information, tx,
803803
collection, keyIdField));
804804
logger.debug("Executed query [{}] in {} ms", query.getQuery(), response.getElapsedTime());
805-
return response.getResults().getNumFound();
805+
return QueryUtil.applyOffsetWithQueryLimitAfterCount(response.getResults().getNumFound(), query.getOffset(), query);
806806
} catch (final IOException e) {
807807
logger.error("Query did not complete : ", e);
808808
throw new PermanentBackendException(e);

0 commit comments

Comments
 (0)