Skip to content

Commit 1634d28

Browse files
committed
Query DSL: Add limit filter, closes elastic#976.
1 parent 4345a82 commit 1634d28

File tree

11 files changed

+274
-2
lines changed

11 files changed

+274
-2
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to Elastic Search and Shay Banon under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. Elastic Search licenses this
6+
* file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common.lucene.search;
21+
22+
import org.apache.lucene.index.IndexReader;
23+
import org.apache.lucene.search.DocIdSet;
24+
import org.elasticsearch.common.lucene.docset.GetDocSet;
25+
26+
import java.io.IOException;
27+
28+
public class LimitFilter extends NoCacheFilter {
29+
30+
private final int limit;
31+
32+
public LimitFilter(int limit) {
33+
this.limit = limit;
34+
}
35+
36+
public int getLimit() {
37+
return limit;
38+
}
39+
40+
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
41+
return new LimitDocSet(reader.maxDoc(), limit);
42+
}
43+
44+
public static class LimitDocSet extends GetDocSet {
45+
46+
private final int limit;
47+
private int counter;
48+
49+
public LimitDocSet(int maxDoc, int limit) {
50+
super(maxDoc);
51+
this.limit = limit;
52+
}
53+
54+
@Override public boolean get(int doc) throws IOException {
55+
if (++counter > limit) {
56+
return false;
57+
}
58+
return true;
59+
}
60+
}
61+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Licensed to Elastic Search and Shay Banon under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. Elastic Search licenses this
6+
* file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common.lucene.search;
21+
22+
import org.apache.lucene.search.Filter;
23+
24+
/**
25+
* A marker interface for {@link org.apache.lucene.search.Filter} denoting the filter
26+
* as one that should not be cached, ever.
27+
*/
28+
public abstract class NoCacheFilter extends Filter {
29+
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/support/AbstractConcurrentMapFilterCache.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.common.RamUsage;
2626
import org.elasticsearch.common.lab.LongsLAB;
2727
import org.elasticsearch.common.lucene.docset.DocSet;
28+
import org.elasticsearch.common.lucene.search.NoCacheFilter;
2829
import org.elasticsearch.common.settings.Settings;
2930
import org.elasticsearch.common.unit.ByteSizeUnit;
3031
import org.elasticsearch.common.unit.ByteSizeValue;
@@ -121,6 +122,9 @@ protected ConcurrentMap<Filter, DocSet> buildFilterMap() {
121122
}
122123

123124
@Override public Filter cache(Filter filterToCache) {
125+
if (filterToCache instanceof NoCacheFilter) {
126+
return filterToCache;
127+
}
124128
if (isCached(filterToCache)) {
125129
return filterToCache;
126130
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/support/AbstractWeightedFilterCache.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.elasticsearch.common.concurrentlinkedhashmap.Weigher;
3030
import org.elasticsearch.common.lab.LongsLAB;
3131
import org.elasticsearch.common.lucene.docset.DocSet;
32+
import org.elasticsearch.common.lucene.search.NoCacheFilter;
3233
import org.elasticsearch.common.settings.Settings;
3334
import org.elasticsearch.common.unit.ByteSizeUnit;
3435
import org.elasticsearch.common.unit.ByteSizeValue;
@@ -138,6 +139,9 @@ protected AbstractWeightedFilterCache(Index index, @IndexSettings Settings index
138139
}
139140

140141
@Override public Filter cache(Filter filterToCache) {
142+
if (filterToCache instanceof NoCacheFilter) {
143+
return filterToCache;
144+
}
141145
if (isCached(filterToCache)) {
142146
return filterToCache;
143147
}

modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ private static class DefaultQueryProcessors extends QueryParsersProcessor {
255255
bindings.processXContentQueryFilter(HasChildFilterParser.NAME, HasChildFilterParser.class);
256256
bindings.processXContentQueryFilter(TypeFilterParser.NAME, TypeFilterParser.class);
257257
bindings.processXContentQueryFilter(IdsFilterParser.NAME, IdsFilterParser.class);
258+
bindings.processXContentQueryFilter(LimitFilterParser.NAME, LimitFilterParser.class);
258259
bindings.processXContentQueryFilter(TermFilterParser.NAME, TermFilterParser.class);
259260
bindings.processXContentQueryFilter(TermsFilterParser.NAME, TermsFilterParser.class);
260261
bindings.processXContentQueryFilter(RangeFilterParser.NAME, RangeFilterParser.class);

modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FilterBuilders.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public static MatchAllFilterBuilder matchAllFilter() {
3535
return new MatchAllFilterBuilder();
3636
}
3737

38+
/**
39+
* A filter that limits the results to the provided limit value (per shard!).
40+
*/
41+
public static LimitFilterBuilder limitFilter(int limit) {
42+
return new LimitFilterBuilder(limit);
43+
}
44+
3845
/**
3946
* Creates a new ids filter with the provided doc/mapping types.
4047
*
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to Elastic Search and Shay Banon under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. Elastic Search licenses this
6+
* file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.query.xcontent;
21+
22+
import org.elasticsearch.common.xcontent.XContentBuilder;
23+
24+
import java.io.IOException;
25+
26+
public class LimitFilterBuilder extends BaseFilterBuilder {
27+
28+
private final int limit;
29+
30+
public LimitFilterBuilder(int limit) {
31+
this.limit = limit;
32+
}
33+
34+
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
35+
builder.startObject(LimitFilterParser.NAME);
36+
builder.field("value", limit);
37+
builder.endObject();
38+
}
39+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Licensed to Elastic Search and Shay Banon under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. Elastic Search licenses this
6+
* file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.query.xcontent;
21+
22+
import org.apache.lucene.search.Filter;
23+
import org.elasticsearch.common.inject.Inject;
24+
import org.elasticsearch.common.lucene.search.LimitFilter;
25+
import org.elasticsearch.common.settings.Settings;
26+
import org.elasticsearch.common.xcontent.XContentParser;
27+
import org.elasticsearch.index.AbstractIndexComponent;
28+
import org.elasticsearch.index.Index;
29+
import org.elasticsearch.index.query.QueryParsingException;
30+
import org.elasticsearch.index.settings.IndexSettings;
31+
32+
import java.io.IOException;
33+
34+
public class LimitFilterParser extends AbstractIndexComponent implements XContentFilterParser {
35+
36+
public static final String NAME = "limit";
37+
38+
@Inject public LimitFilterParser(Index index, @IndexSettings Settings settings) {
39+
super(index, settings);
40+
}
41+
42+
@Override public String[] names() {
43+
return new String[]{NAME};
44+
}
45+
46+
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
47+
XContentParser parser = parseContext.parser();
48+
49+
int limit = -1;
50+
String currentFieldName = null;
51+
XContentParser.Token token;
52+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
53+
if (token == XContentParser.Token.FIELD_NAME) {
54+
currentFieldName = parser.currentName();
55+
} else if (token.isValue()) {
56+
if ("value".equals(currentFieldName)) {
57+
limit = parser.intValue();
58+
}
59+
}
60+
}
61+
62+
if (limit == -1) {
63+
throw new QueryParsingException(index, "No value specified for limit filter");
64+
}
65+
66+
return new LimitFilter(limit);
67+
}
68+
}

modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,23 @@
2121

2222
import org.apache.lucene.index.Term;
2323
import org.apache.lucene.search.*;
24-
import org.apache.lucene.search.spans.*;
24+
import org.apache.lucene.search.spans.SpanFirstQuery;
25+
import org.apache.lucene.search.spans.SpanNearQuery;
26+
import org.apache.lucene.search.spans.SpanNotQuery;
27+
import org.apache.lucene.search.spans.SpanOrQuery;
28+
import org.apache.lucene.search.spans.SpanTermQuery;
2529
import org.apache.lucene.util.NumericUtils;
2630
import org.elasticsearch.common.inject.Injector;
2731
import org.elasticsearch.common.inject.ModulesBuilder;
28-
import org.elasticsearch.common.lucene.search.*;
32+
import org.elasticsearch.common.lucene.search.AndFilter;
33+
import org.elasticsearch.common.lucene.search.LimitFilter;
34+
import org.elasticsearch.common.lucene.search.MoreLikeThisQuery;
35+
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
36+
import org.elasticsearch.common.lucene.search.NotFilter;
37+
import org.elasticsearch.common.lucene.search.OrFilter;
38+
import org.elasticsearch.common.lucene.search.Queries;
39+
import org.elasticsearch.common.lucene.search.TermFilter;
40+
import org.elasticsearch.common.lucene.search.XBooleanFilter;
2941
import org.elasticsearch.common.lucene.search.function.BoostScoreFunction;
3042
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
3143
import org.elasticsearch.common.settings.ImmutableSettings;
@@ -956,6 +968,19 @@ private XContentIndexQueryParser queryParser() throws IOException {
956968
assertThat(((TermFilter) filteredQuery.getFilter()).getTerm(), equalTo(new Term("name.last", "banon")));
957969
}
958970

971+
@Test public void testLimitFilter() throws Exception {
972+
IndexQueryParser queryParser = queryParser();
973+
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/limit-filter.json");
974+
Query parsedQuery = queryParser.parse(query).query();
975+
assertThat(parsedQuery, instanceOf(FilteredQuery.class));
976+
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery;
977+
assertThat(filteredQuery.getFilter(), instanceOf(LimitFilter.class));
978+
assertThat(((LimitFilter) filteredQuery.getFilter()).getLimit(), equalTo(2));
979+
980+
assertThat(filteredQuery.getQuery(), instanceOf(TermQuery.class));
981+
assertThat(((TermQuery) filteredQuery.getQuery()).getTerm(), equalTo(new Term("name.first", "shay")));
982+
}
983+
959984
@Test public void testTermFilterQuery() throws Exception {
960985
IndexQueryParser queryParser = queryParser();
961986
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/term-filter.json");
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"filtered" : {
3+
"filter" : {
4+
"limit" : {
5+
"value" : 2
6+
}
7+
},
8+
"query" : {
9+
"term" : {
10+
"name.first" : "shay"
11+
}
12+
}
13+
}
14+
}

modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/query/SimpleQueryTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,26 @@ private void idsFilterTests(String index) throws Exception {
195195
assertThat(searchResponse.hits().totalHits(), equalTo(0l));
196196
}
197197

198+
@Test public void testLimitFilter() throws Exception {
199+
try {
200+
client.admin().indices().prepareDelete("test").execute().actionGet();
201+
} catch (Exception e) {
202+
// ignore
203+
}
204+
205+
client.admin().indices().prepareCreate("test").setSettings(ImmutableSettings.settingsBuilder().put("number_of_shards", 1)).execute().actionGet();
206+
207+
client.prepareIndex("test", "type1", "1").setSource("field1", "value1_1").execute().actionGet();
208+
client.prepareIndex("test", "type1", "2").setSource("field1", "value1_2").execute().actionGet();
209+
client.prepareIndex("test", "type1", "3").setSource("field2", "value2_3").execute().actionGet();
210+
client.prepareIndex("test", "type1", "4").setSource("field3", "value3_4").execute().actionGet();
211+
212+
client.admin().indices().prepareRefresh().execute().actionGet();
213+
214+
SearchResponse searchResponse = client.prepareSearch().setQuery(filteredQuery(matchAllQuery(), limitFilter(2))).execute().actionGet();
215+
assertThat(searchResponse.hits().totalHits(), equalTo(2l));
216+
}
217+
198218
@Test public void filterExistsMissingTests() throws Exception {
199219
try {
200220
client.admin().indices().prepareDelete("test").execute().actionGet();

0 commit comments

Comments
 (0)