Query DSL: Numeric Range Filter - A filter that uses the field data cache to perform numeric tests, closes #435.
This commit is contained in:
parent
cc3a1dbe90
commit
d9f966d83c
|
@ -43,6 +43,10 @@ public interface FieldDataType<T extends FieldData> {
|
|||
public static final LongFieldDataType LONG = new LongFieldDataType();
|
||||
public static final FloatFieldDataType FLOAT = new FloatFieldDataType();
|
||||
public static final DoubleFieldDataType DOUBLE = new DoubleFieldDataType();
|
||||
|
||||
public static boolean isNumeric(FieldDataType type) {
|
||||
return type == INT || type == LONG || type == FLOAT || type == DOUBLE;
|
||||
}
|
||||
}
|
||||
|
||||
Class<T> fieldDataClass();
|
||||
|
|
|
@ -249,6 +249,7 @@ public class IndexQueryParserModule extends AbstractModule {
|
|||
bindings.processXContentQueryFilter(TermFilterParser.NAME, TermFilterParser.class);
|
||||
bindings.processXContentQueryFilter(TermsFilterParser.NAME, TermsFilterParser.class);
|
||||
bindings.processXContentQueryFilter(RangeFilterParser.NAME, RangeFilterParser.class);
|
||||
bindings.processXContentQueryFilter(NumericRangeFilterParser.NAME, NumericRangeFilterParser.class);
|
||||
bindings.processXContentQueryFilter(PrefixFilterParser.NAME, PrefixFilterParser.class);
|
||||
bindings.processXContentQueryFilter(ScriptFilterParser.NAME, ScriptFilterParser.class);
|
||||
bindings.processXContentQueryFilter(GeoDistanceFilterParser.NAME, GeoDistanceFilterParser.class);
|
||||
|
|
|
@ -173,6 +173,16 @@ public abstract class FilterBuilders {
|
|||
return new RangeFilterBuilder(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that restricts search results to values that are within the given numeric range. Uses the
|
||||
* field data cache (loading all the values for the specified field into memory)
|
||||
*
|
||||
* @param name The field name
|
||||
*/
|
||||
public static NumericRangeFilterBuilder numericRangeFilter(String name) {
|
||||
return new NumericRangeFilterBuilder(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that simply wraps a query.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.index.query.xcontent;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A filter that restricts search results to values that are within the given numeric range.
|
||||
*
|
||||
* <p>Uses the field data cache (loading all the values for the specified field into memory).
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class NumericRangeFilterBuilder extends BaseFilterBuilder {
|
||||
|
||||
private final String name;
|
||||
|
||||
private Object from;
|
||||
|
||||
private Object to;
|
||||
|
||||
private boolean includeLower = true;
|
||||
|
||||
private boolean includeUpper = true;
|
||||
|
||||
private String filterName;
|
||||
|
||||
/**
|
||||
* A filter that restricts search results to values that are within the given range.
|
||||
*
|
||||
* @param name The field name
|
||||
*/
|
||||
public NumericRangeFilterBuilder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder from(Object from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder from(int from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder from(long from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder from(float from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder from(double from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gt(Object from) {
|
||||
this.from = from;
|
||||
this.includeLower = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gt(int from) {
|
||||
this.from = from;
|
||||
this.includeLower = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gt(long from) {
|
||||
this.from = from;
|
||||
this.includeLower = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gt(float from) {
|
||||
this.from = from;
|
||||
this.includeLower = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gt(double from) {
|
||||
this.from = from;
|
||||
this.includeLower = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gte(Object from) {
|
||||
this.from = from;
|
||||
this.includeLower = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gte(int from) {
|
||||
this.from = from;
|
||||
this.includeLower = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gte(long from) {
|
||||
this.from = from;
|
||||
this.includeLower = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gte(float from) {
|
||||
this.from = from;
|
||||
this.includeLower = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The from part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder gte(double from) {
|
||||
this.from = from;
|
||||
this.includeLower = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder to(Object to) {
|
||||
this.to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder to(int to) {
|
||||
this.to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder to(long to) {
|
||||
this.to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder to(float to) {
|
||||
this.to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder to(double to) {
|
||||
this.to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lt(Object to) {
|
||||
this.to = to;
|
||||
this.includeUpper = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lt(int to) {
|
||||
this.to = to;
|
||||
this.includeUpper = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lt(long to) {
|
||||
this.to = to;
|
||||
this.includeUpper = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lt(float to) {
|
||||
this.to = to;
|
||||
this.includeUpper = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lt(double to) {
|
||||
this.to = to;
|
||||
this.includeUpper = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lte(String to) {
|
||||
this.to = to;
|
||||
this.includeUpper = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lte(int to) {
|
||||
this.to = to;
|
||||
this.includeUpper = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lte(long to) {
|
||||
this.to = to;
|
||||
this.includeUpper = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lte(float to) {
|
||||
this.to = to;
|
||||
this.includeUpper = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to part of the filter query. Null indicates unbounded.
|
||||
*/
|
||||
public NumericRangeFilterBuilder lte(double to) {
|
||||
this.to = to;
|
||||
this.includeUpper = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the lower bound be included or not. Defaults to <tt>true</tt>.
|
||||
*/
|
||||
public NumericRangeFilterBuilder includeLower(boolean includeLower) {
|
||||
this.includeLower = includeLower;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the upper bound be included or not. Defaults to <tt>true</tt>.
|
||||
*/
|
||||
public NumericRangeFilterBuilder includeUpper(boolean includeUpper) {
|
||||
this.includeUpper = includeUpper;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NumericRangeFilterBuilder filterName(String filterName) {
|
||||
this.filterName = filterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(NumericRangeFilterParser.NAME);
|
||||
|
||||
builder.startObject(name);
|
||||
builder.field("from", from);
|
||||
builder.field("to", to);
|
||||
builder.field("include_lower", includeLower);
|
||||
builder.field("include_upper", includeUpper);
|
||||
builder.endObject();
|
||||
|
||||
if (filterName != null) {
|
||||
builder.field("_name", filterName);
|
||||
}
|
||||
|
||||
builder.endObject();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.index.query.xcontent;
|
||||
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.AbstractIndexComponent;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.index.field.data.FieldDataType;
|
||||
import org.elasticsearch.index.mapper.FieldMapper;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.query.QueryParsingException;
|
||||
import org.elasticsearch.index.search.NumericRangeFieldDataFilter;
|
||||
import org.elasticsearch.index.settings.IndexSettings;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.elasticsearch.index.query.support.QueryParsers.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class NumericRangeFilterParser extends AbstractIndexComponent implements XContentFilterParser {
|
||||
|
||||
public static final String NAME = "numeric_range";
|
||||
|
||||
@Inject public NumericRangeFilterParser(Index index, @IndexSettings Settings settings) {
|
||||
super(index, settings);
|
||||
}
|
||||
|
||||
@Override public String[] names() {
|
||||
return new String[]{NAME, "numericRange"};
|
||||
}
|
||||
|
||||
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
|
||||
XContentParser parser = parseContext.parser();
|
||||
|
||||
boolean cache = false; // default to false, since its using fielddata cache
|
||||
String fieldName = null;
|
||||
String from = null;
|
||||
String to = null;
|
||||
boolean includeLower = true;
|
||||
boolean includeUpper = true;
|
||||
|
||||
String filterName = null;
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
fieldName = currentFieldName;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else {
|
||||
if ("from".equals(currentFieldName)) {
|
||||
from = parser.textOrNull();
|
||||
} else if ("to".equals(currentFieldName)) {
|
||||
to = parser.textOrNull();
|
||||
} else if ("include_lower".equals(currentFieldName) || "includeLower".equals(currentFieldName)) {
|
||||
includeLower = parser.booleanValue();
|
||||
} else if ("include_upper".equals(currentFieldName) || "includeUpper".equals(currentFieldName)) {
|
||||
includeUpper = parser.booleanValue();
|
||||
} else if ("gt".equals(currentFieldName)) {
|
||||
from = parser.textOrNull();
|
||||
includeLower = false;
|
||||
} else if ("gte".equals(currentFieldName) || "ge".equals(currentFieldName)) {
|
||||
from = parser.textOrNull();
|
||||
includeLower = true;
|
||||
} else if ("lt".equals(currentFieldName)) {
|
||||
to = parser.textOrNull();
|
||||
includeUpper = false;
|
||||
} else if ("lte".equals(currentFieldName) || "le".equals(currentFieldName)) {
|
||||
to = parser.textOrNull();
|
||||
includeUpper = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (token.isValue()) {
|
||||
if ("_name".equals(currentFieldName)) {
|
||||
filterName = parser.text();
|
||||
} else if ("_cache".equals(currentFieldName)) {
|
||||
cache = parser.booleanValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filter filter;
|
||||
MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
|
||||
|
||||
if (smartNameFieldMappers == null || !smartNameFieldMappers.hasMapper()) {
|
||||
throw new QueryParsingException(index, "failed to find mapping for field [" + fieldName + "]");
|
||||
}
|
||||
|
||||
FieldMapper mapper = smartNameFieldMappers.mapper();
|
||||
if (mapper.fieldDataType() == FieldDataType.DefaultTypes.INT) {
|
||||
filter = NumericRangeFieldDataFilter.newIntRange(parseContext.indexCache().fieldData(), mapper.names().indexName(),
|
||||
from == null ? null : Integer.parseInt(from),
|
||||
to == null ? null : Integer.parseInt(to),
|
||||
includeLower, includeUpper);
|
||||
} else if (mapper.fieldDataType() == FieldDataType.DefaultTypes.LONG) {
|
||||
filter = NumericRangeFieldDataFilter.newLongRange(parseContext.indexCache().fieldData(), mapper.names().indexName(),
|
||||
from == null ? null : Long.parseLong(from),
|
||||
to == null ? null : Long.parseLong(to),
|
||||
includeLower, includeUpper);
|
||||
} else if (mapper.fieldDataType() == FieldDataType.DefaultTypes.FLOAT) {
|
||||
filter = NumericRangeFieldDataFilter.newFloatRange(parseContext.indexCache().fieldData(), mapper.names().indexName(),
|
||||
from == null ? null : Float.parseFloat(from),
|
||||
to == null ? null : Float.parseFloat(to),
|
||||
includeLower, includeUpper);
|
||||
} else if (mapper.fieldDataType() == FieldDataType.DefaultTypes.DOUBLE) {
|
||||
filter = NumericRangeFieldDataFilter.newDoubleRange(parseContext.indexCache().fieldData(), mapper.names().indexName(),
|
||||
from == null ? null : Double.parseDouble(from),
|
||||
to == null ? null : Double.parseDouble(to),
|
||||
includeLower, includeUpper);
|
||||
} else {
|
||||
throw new QueryParsingException(index, "field [" + fieldName + "] is not numeric");
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
filter = parseContext.cacheFilter(filter);
|
||||
}
|
||||
filter = wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
|
||||
if (filterName != null) {
|
||||
parseContext.addNamedFilter(filterName, filter);
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ public class RangeFilterParser extends AbstractIndexComponent implements XConten
|
|||
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
|
||||
XContentParser parser = parseContext.parser();
|
||||
|
||||
boolean cache = true; // default to true, usually same range are used, I think....
|
||||
boolean cache = true; // default to true, since anyhow NumericRangeFilter and TermFilter construct an OpenBitSet
|
||||
String fieldName = null;
|
||||
String from = null;
|
||||
String to = null;
|
||||
|
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.index.search;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
import org.elasticsearch.common.lucene.docset.DocSet;
|
||||
import org.elasticsearch.common.lucene.docset.GetDocSet;
|
||||
import org.elasticsearch.index.cache.field.data.FieldDataCache;
|
||||
import org.elasticsearch.index.field.data.FieldDataType;
|
||||
import org.elasticsearch.index.field.data.doubles.DoubleFieldData;
|
||||
import org.elasticsearch.index.field.data.floats.FloatFieldData;
|
||||
import org.elasticsearch.index.field.data.ints.IntFieldData;
|
||||
import org.elasticsearch.index.field.data.longs.LongFieldData;
|
||||
import org.elasticsearch.index.field.data.shorts.ShortFieldData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A numeric filter that can be much faster than {@link org.apache.lucene.search.NumericRangeFilter} at the
|
||||
* expense of loading numeric values of the field to memory using {@link org.elasticsearch.index.cache.field.data.FieldDataCache}.
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public abstract class NumericRangeFieldDataFilter<T> extends Filter {
|
||||
|
||||
final FieldDataCache fieldDataCache;
|
||||
final String field;
|
||||
final T lowerVal;
|
||||
final T upperVal;
|
||||
final boolean includeLower;
|
||||
final boolean includeUpper;
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public T getLowerVal() {
|
||||
return lowerVal;
|
||||
}
|
||||
|
||||
public T getUpperVal() {
|
||||
return upperVal;
|
||||
}
|
||||
|
||||
public boolean isIncludeLower() {
|
||||
return includeLower;
|
||||
}
|
||||
|
||||
public boolean isIncludeUpper() {
|
||||
return includeUpper;
|
||||
}
|
||||
|
||||
protected NumericRangeFieldDataFilter(FieldDataCache fieldDataCache, String field, T lowerVal, T upperVal, boolean includeLower, boolean includeUpper) {
|
||||
this.fieldDataCache = fieldDataCache;
|
||||
this.field = field;
|
||||
this.lowerVal = lowerVal;
|
||||
this.upperVal = upperVal;
|
||||
this.includeLower = includeLower;
|
||||
this.includeUpper = includeUpper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder(field).append(":");
|
||||
return sb.append(includeLower ? '[' : '{')
|
||||
.append((lowerVal == null) ? "*" : lowerVal.toString())
|
||||
.append(" TO ")
|
||||
.append((upperVal == null) ? "*" : upperVal.toString())
|
||||
.append(includeUpper ? ']' : '}')
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof NumericRangeFieldDataFilter)) return false;
|
||||
NumericRangeFieldDataFilter other = (NumericRangeFieldDataFilter) o;
|
||||
|
||||
if (!this.field.equals(other.field)
|
||||
|| this.includeLower != other.includeLower
|
||||
|| this.includeUpper != other.includeUpper
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.lowerVal != null ? !this.lowerVal.equals(other.lowerVal) : other.lowerVal != null) return false;
|
||||
if (this.upperVal != null ? !this.upperVal.equals(other.upperVal) : other.upperVal != null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
int h = field.hashCode();
|
||||
h ^= (lowerVal != null) ? lowerVal.hashCode() : 550356204;
|
||||
h = (h << 1) | (h >>> 31); // rotate to distinguish lower from upper
|
||||
h ^= (upperVal != null) ? upperVal.hashCode() : -1674416163;
|
||||
h ^= (includeLower ? 1549299360 : -365038026) ^ (includeUpper ? 1721088258 : 1948649653);
|
||||
return h;
|
||||
}
|
||||
|
||||
public static NumericRangeFieldDataFilter<Short> newShortRange(FieldDataCache fieldDataCache, String field, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) {
|
||||
return new NumericRangeFieldDataFilter<Short>(fieldDataCache, field, lowerVal, upperVal, includeLower, includeUpper) {
|
||||
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
|
||||
final short inclusiveLowerPoint, inclusiveUpperPoint;
|
||||
if (lowerVal != null) {
|
||||
short i = lowerVal.shortValue();
|
||||
if (!includeLower && i == Short.MAX_VALUE)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
inclusiveLowerPoint = (short) (includeLower ? i : (i + 1));
|
||||
} else {
|
||||
inclusiveLowerPoint = Short.MIN_VALUE;
|
||||
}
|
||||
if (upperVal != null) {
|
||||
short i = upperVal.shortValue();
|
||||
if (!includeUpper && i == Short.MIN_VALUE)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
inclusiveUpperPoint = (short) (includeUpper ? i : (i - 1));
|
||||
} else {
|
||||
inclusiveUpperPoint = Short.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (inclusiveLowerPoint > inclusiveUpperPoint)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
|
||||
final ShortFieldData fieldData = (ShortFieldData) this.fieldDataCache.cache(FieldDataType.DefaultTypes.SHORT, reader, field);
|
||||
return new GetDocSet(reader.maxDoc()) {
|
||||
|
||||
@Override public boolean isCacheable() {
|
||||
// not cacheable for several reasons:
|
||||
// 1. It is only relevant when _cache is set to true, and then, we really want to create in mem bitset
|
||||
// 2. Its already fast without in mem bitset, since it works with field data
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean get(int doc) throws IOException {
|
||||
if (!fieldData.hasValue(doc)) {
|
||||
return false;
|
||||
}
|
||||
if (fieldData.multiValued()) {
|
||||
short[] values = fieldData.values(doc);
|
||||
for (short value : values) {
|
||||
if (value >= inclusiveLowerPoint && value <= inclusiveUpperPoint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
short value = fieldData.value(doc);
|
||||
return value >= inclusiveLowerPoint && value <= inclusiveUpperPoint;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static NumericRangeFieldDataFilter<Integer> newIntRange(FieldDataCache fieldDataCache, String field, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) {
|
||||
return new NumericRangeFieldDataFilter<Integer>(fieldDataCache, field, lowerVal, upperVal, includeLower, includeUpper) {
|
||||
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
|
||||
final int inclusiveLowerPoint, inclusiveUpperPoint;
|
||||
if (lowerVal != null) {
|
||||
int i = lowerVal.intValue();
|
||||
if (!includeLower && i == Integer.MAX_VALUE)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
inclusiveLowerPoint = includeLower ? i : (i + 1);
|
||||
} else {
|
||||
inclusiveLowerPoint = Integer.MIN_VALUE;
|
||||
}
|
||||
if (upperVal != null) {
|
||||
int i = upperVal.intValue();
|
||||
if (!includeUpper && i == Integer.MIN_VALUE)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
inclusiveUpperPoint = includeUpper ? i : (i - 1);
|
||||
} else {
|
||||
inclusiveUpperPoint = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (inclusiveLowerPoint > inclusiveUpperPoint)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
|
||||
final IntFieldData fieldData = (IntFieldData) this.fieldDataCache.cache(FieldDataType.DefaultTypes.INT, reader, field);
|
||||
return new GetDocSet(reader.maxDoc()) {
|
||||
|
||||
@Override public boolean isCacheable() {
|
||||
// not cacheable for several reasons:
|
||||
// 1. It is only relevant when _cache is set to true, and then, we really want to create in mem bitset
|
||||
// 2. Its already fast without in mem bitset, since it works with field data
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean get(int doc) throws IOException {
|
||||
if (!fieldData.hasValue(doc)) {
|
||||
return false;
|
||||
}
|
||||
if (fieldData.multiValued()) {
|
||||
int[] values = fieldData.values(doc);
|
||||
for (int value : values) {
|
||||
if (value >= inclusiveLowerPoint && value <= inclusiveUpperPoint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
int value = fieldData.value(doc);
|
||||
return value >= inclusiveLowerPoint && value <= inclusiveUpperPoint;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static NumericRangeFieldDataFilter<Long> newLongRange(FieldDataCache fieldDataCache, String field, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) {
|
||||
return new NumericRangeFieldDataFilter<Long>(fieldDataCache, field, lowerVal, upperVal, includeLower, includeUpper) {
|
||||
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
|
||||
final long inclusiveLowerPoint, inclusiveUpperPoint;
|
||||
if (lowerVal != null) {
|
||||
long i = lowerVal.longValue();
|
||||
if (!includeLower && i == Long.MAX_VALUE)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
inclusiveLowerPoint = includeLower ? i : (i + 1l);
|
||||
} else {
|
||||
inclusiveLowerPoint = Long.MIN_VALUE;
|
||||
}
|
||||
if (upperVal != null) {
|
||||
long i = upperVal.longValue();
|
||||
if (!includeUpper && i == Long.MIN_VALUE)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
inclusiveUpperPoint = includeUpper ? i : (i - 1l);
|
||||
} else {
|
||||
inclusiveUpperPoint = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (inclusiveLowerPoint > inclusiveUpperPoint)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
|
||||
final LongFieldData fieldData = (LongFieldData) this.fieldDataCache.cache(FieldDataType.DefaultTypes.LONG, reader, field);
|
||||
return new GetDocSet(reader.maxDoc()) {
|
||||
|
||||
@Override public boolean isCacheable() {
|
||||
// not cacheable for several reasons:
|
||||
// 1. It is only relevant when _cache is set to true, and then, we really want to create in mem bitset
|
||||
// 2. Its already fast without in mem bitset, since it works with field data
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean get(int doc) throws IOException {
|
||||
if (!fieldData.hasValue(doc)) {
|
||||
return false;
|
||||
}
|
||||
if (fieldData.multiValued()) {
|
||||
long[] values = fieldData.values(doc);
|
||||
for (long value : values) {
|
||||
if (value >= inclusiveLowerPoint && value <= inclusiveUpperPoint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
long value = fieldData.value(doc);
|
||||
return value >= inclusiveLowerPoint && value <= inclusiveUpperPoint;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static NumericRangeFieldDataFilter<Float> newFloatRange(FieldDataCache fieldDataCache, String field, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) {
|
||||
return new NumericRangeFieldDataFilter<Float>(fieldDataCache, field, lowerVal, upperVal, includeLower, includeUpper) {
|
||||
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
|
||||
// we transform the floating point numbers to sortable integers
|
||||
// using NumericUtils to easier find the next bigger/lower value
|
||||
final float inclusiveLowerPoint, inclusiveUpperPoint;
|
||||
if (lowerVal != null) {
|
||||
float f = lowerVal.floatValue();
|
||||
if (!includeUpper && f > 0.0f && Float.isInfinite(f))
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
int i = NumericUtils.floatToSortableInt(f);
|
||||
inclusiveLowerPoint = NumericUtils.sortableIntToFloat(includeLower ? i : (i + 1));
|
||||
} else {
|
||||
inclusiveLowerPoint = Float.NEGATIVE_INFINITY;
|
||||
}
|
||||
if (upperVal != null) {
|
||||
float f = upperVal.floatValue();
|
||||
if (!includeUpper && f < 0.0f && Float.isInfinite(f))
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
int i = NumericUtils.floatToSortableInt(f);
|
||||
inclusiveUpperPoint = NumericUtils.sortableIntToFloat(includeUpper ? i : (i - 1));
|
||||
} else {
|
||||
inclusiveUpperPoint = Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
if (inclusiveLowerPoint > inclusiveUpperPoint)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
|
||||
final FloatFieldData fieldData = (FloatFieldData) this.fieldDataCache.cache(FieldDataType.DefaultTypes.FLOAT, reader, field);
|
||||
return new GetDocSet(reader.maxDoc()) {
|
||||
|
||||
@Override public boolean isCacheable() {
|
||||
// not cacheable for several reasons:
|
||||
// 1. It is only relevant when _cache is set to true, and then, we really want to create in mem bitset
|
||||
// 2. Its already fast without in mem bitset, since it works with field data
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean get(int doc) throws IOException {
|
||||
if (!fieldData.hasValue(doc)) {
|
||||
return false;
|
||||
}
|
||||
if (fieldData.multiValued()) {
|
||||
float[] values = fieldData.values(doc);
|
||||
for (float value : values) {
|
||||
if (value >= inclusiveLowerPoint && value <= inclusiveUpperPoint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
float value = fieldData.value(doc);
|
||||
return value >= inclusiveLowerPoint && value <= inclusiveUpperPoint;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static NumericRangeFieldDataFilter<Double> newDoubleRange(FieldDataCache fieldDataCache, String field, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) {
|
||||
return new NumericRangeFieldDataFilter<Double>(fieldDataCache, field, lowerVal, upperVal, includeLower, includeUpper) {
|
||||
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
|
||||
// we transform the floating point numbers to sortable integers
|
||||
// using NumericUtils to easier find the next bigger/lower value
|
||||
final double inclusiveLowerPoint, inclusiveUpperPoint;
|
||||
if (lowerVal != null) {
|
||||
double f = lowerVal.doubleValue();
|
||||
if (!includeUpper && f > 0.0 && Double.isInfinite(f))
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
long i = NumericUtils.doubleToSortableLong(f);
|
||||
inclusiveLowerPoint = NumericUtils.sortableLongToDouble(includeLower ? i : (i + 1L));
|
||||
} else {
|
||||
inclusiveLowerPoint = Double.NEGATIVE_INFINITY;
|
||||
}
|
||||
if (upperVal != null) {
|
||||
double f = upperVal.doubleValue();
|
||||
if (!includeUpper && f < 0.0 && Double.isInfinite(f))
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
long i = NumericUtils.doubleToSortableLong(f);
|
||||
inclusiveUpperPoint = NumericUtils.sortableLongToDouble(includeUpper ? i : (i - 1L));
|
||||
} else {
|
||||
inclusiveUpperPoint = Double.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
if (inclusiveLowerPoint > inclusiveUpperPoint)
|
||||
return DocSet.EMPTY_DOC_SET;
|
||||
|
||||
final DoubleFieldData fieldData = (DoubleFieldData) this.fieldDataCache.cache(FieldDataType.DefaultTypes.DOUBLE, reader, field);
|
||||
return new GetDocSet(reader.maxDoc()) {
|
||||
|
||||
@Override public boolean isCacheable() {
|
||||
// not cacheable for several reasons:
|
||||
// 1. It is only relevant when _cache is set to true, and then, we really want to create in mem bitset
|
||||
// 2. Its already fast without in mem bitset, since it works with field data
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean get(int doc) throws IOException {
|
||||
if (!fieldData.hasValue(doc)) {
|
||||
return false;
|
||||
}
|
||||
if (fieldData.multiValued()) {
|
||||
double[] values = fieldData.values(doc);
|
||||
for (double value : values) {
|
||||
if (value >= inclusiveLowerPoint && value <= inclusiveUpperPoint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
double value = fieldData.value(doc);
|
||||
return value >= inclusiveLowerPoint && value <= inclusiveUpperPoint;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ import org.elasticsearch.index.query.IndexQueryParser;
|
|||
import org.elasticsearch.index.query.IndexQueryParserModule;
|
||||
import org.elasticsearch.index.query.IndexQueryParserService;
|
||||
import org.elasticsearch.index.query.ParsedQuery;
|
||||
import org.elasticsearch.index.search.NumericRangeFieldDataFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoBoundingBoxFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoDistanceFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoPolygonFilter;
|
||||
|
@ -551,6 +552,35 @@ public class SimpleIndexQueryParserTests {
|
|||
assertThat(rangeFilter.includesMax(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test public void testNumericRangeFilteredQueryBuilder() throws IOException {
|
||||
IndexQueryParser queryParser = queryParser();
|
||||
Query parsedQuery = queryParser.parse(filtered(termQuery("name.first", "shay"), numericRangeFilter("age").from(23).to(54).includeLower(true).includeUpper(false))).query();
|
||||
assertThat(parsedQuery, instanceOf(FilteredQuery.class));
|
||||
Filter filter = ((FilteredQuery) parsedQuery).getFilter();
|
||||
assertThat(filter, instanceOf(NumericRangeFieldDataFilter.class));
|
||||
NumericRangeFieldDataFilter<Number> rangeFilter = (NumericRangeFieldDataFilter<Number>) filter;
|
||||
assertThat(rangeFilter.getField(), equalTo("age"));
|
||||
assertThat(rangeFilter.getLowerVal().intValue(), equalTo(23));
|
||||
assertThat(rangeFilter.getUpperVal().intValue(), equalTo(54));
|
||||
assertThat(rangeFilter.isIncludeLower(), equalTo(true));
|
||||
assertThat(rangeFilter.isIncludeUpper(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test public void testNumericRangeFilteredQuery() throws IOException {
|
||||
IndexQueryParser queryParser = queryParser();
|
||||
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/numeric_range-filter.json");
|
||||
Query parsedQuery = queryParser.parse(query).query();
|
||||
assertThat(parsedQuery, instanceOf(FilteredQuery.class));
|
||||
Filter filter = ((FilteredQuery) parsedQuery).getFilter();
|
||||
assertThat(filter, instanceOf(NumericRangeFieldDataFilter.class));
|
||||
NumericRangeFieldDataFilter<Number> rangeFilter = (NumericRangeFieldDataFilter<Number>) filter;
|
||||
assertThat(rangeFilter.getField(), equalTo("age"));
|
||||
assertThat(rangeFilter.getLowerVal().intValue(), equalTo(23));
|
||||
assertThat(rangeFilter.getUpperVal().intValue(), equalTo(54));
|
||||
assertThat(rangeFilter.isIncludeLower(), equalTo(true));
|
||||
assertThat(rangeFilter.isIncludeUpper(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test public void testBoolFilteredQuery() throws IOException {
|
||||
IndexQueryParser queryParser = queryParser();
|
||||
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/bool-filter.json");
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"filtered" : {
|
||||
"query" : {
|
||||
"term" : { "name.first" : "shay" }
|
||||
},
|
||||
"filter" : {
|
||||
"numeric_range" : {
|
||||
"age" : { "from" : "23", "to" : "54", "include_lower" : true, "include_upper": false}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue