Query DSL: Add null_value and existence to missing filter, filtering both null values and missing fields, closes #1970.
This commit is contained in:
parent
11c9c404e9
commit
5bd93d6f93
|
@ -210,5 +210,11 @@ public interface FieldMapper<T> {
|
|||
*/
|
||||
Filter rangeFilter(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context);
|
||||
|
||||
/**
|
||||
* Null value filter, returns <tt>null</tt> if there is no null value associated with the field.
|
||||
*/
|
||||
@Nullable
|
||||
Filter nullValueFilter();
|
||||
|
||||
FieldDataType fieldDataType();
|
||||
}
|
||||
|
|
|
@ -415,6 +415,11 @@ public abstract class AbstractFieldMapper<T> implements FieldMapper<T>, Mapper {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Mapper mergeWith, MergeContext mergeContext) throws MergeMappingException {
|
||||
if (!this.getClass().equals(mergeWith.getClass())) {
|
||||
|
|
|
@ -21,9 +21,11 @@ package org.elasticsearch.index.mapper.core;
|
|||
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.Fieldable;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.elasticsearch.common.Booleans;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.common.lucene.search.TermFilter;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
|
@ -160,6 +162,14 @@ public class BooleanFieldMapper extends AbstractFieldMapper<Boolean> {
|
|||
return "F";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return new TermFilter(names().createIndexNameTerm(nullValue ? "T" : "F"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Field parseCreateField(ParseContext context) throws IOException {
|
||||
if (!indexed() && !stored()) {
|
||||
|
|
|
@ -197,6 +197,17 @@ public class ByteFieldMapper extends NumberFieldMapper<Byte> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newIntRange(names.indexName(), precisionStep,
|
||||
nullValue.intValue(),
|
||||
nullValue.intValue(),
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -274,6 +274,19 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
long value = parseStringValue(nullValue);
|
||||
return NumericRangeFilter.newLongRange(names.indexName(), precisionStep,
|
||||
value,
|
||||
value,
|
||||
true, true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -198,6 +198,17 @@ public class DoubleFieldMapper extends NumberFieldMapper<Double> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newDoubleRange(names.indexName(), precisionStep,
|
||||
nullValue,
|
||||
nullValue,
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -193,6 +193,17 @@ public class FloatFieldMapper extends NumberFieldMapper<Float> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newFloatRange(names.indexName(), precisionStep,
|
||||
nullValue,
|
||||
nullValue,
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -198,6 +198,17 @@ public class IntegerFieldMapper extends NumberFieldMapper<Integer> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newIntRange(names.indexName(), precisionStep,
|
||||
nullValue,
|
||||
nullValue,
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -198,6 +198,17 @@ public class LongFieldMapper extends NumberFieldMapper<Long> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newLongRange(names.indexName(), precisionStep,
|
||||
nullValue,
|
||||
nullValue,
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -198,6 +198,17 @@ public class ShortFieldMapper extends NumberFieldMapper<Short> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newIntRange(names.indexName(), precisionStep,
|
||||
nullValue.intValue(),
|
||||
nullValue.intValue(),
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean customBoost() {
|
||||
return true;
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.index.mapper.core;
|
|||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.Fieldable;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
@ -214,6 +215,14 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
|
|||
return this.searchQuotedAnalyzer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return fieldFilter(nullValue, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Field parseCreateField(ParseContext context) throws IOException {
|
||||
String value = nullValue;
|
||||
|
|
|
@ -190,6 +190,17 @@ public class BoostFieldMapper extends NumberFieldMapper<Float> implements Intern
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
return NumericRangeFilter.newFloatRange(names.indexName(), precisionStep,
|
||||
nullValue,
|
||||
nullValue,
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preParse(ParseContext context) throws IOException {
|
||||
}
|
||||
|
|
|
@ -226,6 +226,18 @@ public class IpFieldMapper extends NumberFieldMapper<Long> {
|
|||
includeLower, includeUpper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter nullValueFilter() {
|
||||
if (nullValue == null) {
|
||||
return null;
|
||||
}
|
||||
final long value = ipToLong(nullValue);
|
||||
return NumericRangeFilter.newLongRange(names.indexName(), precisionStep,
|
||||
value,
|
||||
value,
|
||||
true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Fieldable parseCreateField(ParseContext context) throws IOException {
|
||||
String ipAsString;
|
||||
|
|
|
@ -25,8 +25,6 @@ import java.io.IOException;
|
|||
|
||||
/**
|
||||
* Constructs a filter that only match on documents that the field has a value in them.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class MissingFilterBuilder extends BaseFilterBuilder {
|
||||
|
||||
|
@ -34,10 +32,32 @@ public class MissingFilterBuilder extends BaseFilterBuilder {
|
|||
|
||||
private String filterName;
|
||||
|
||||
private Boolean nullValue;
|
||||
|
||||
private Boolean existence;
|
||||
|
||||
public MissingFilterBuilder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the missing filter automatically include fields with null value configured in the
|
||||
* mappings. Defaults to <tt>false</tt>.
|
||||
*/
|
||||
public MissingFilterBuilder nullValue(boolean nullValue) {
|
||||
this.nullValue = nullValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should hte missing filter include documents where the field doesn't exists in the docs.
|
||||
* Defaults to <tt>true</tt>.
|
||||
*/
|
||||
public MissingFilterBuilder existence(boolean existence) {
|
||||
this.existence = existence;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
|
||||
*/
|
||||
|
@ -46,11 +66,16 @@ public class MissingFilterBuilder extends BaseFilterBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(MissingFilterParser.NAME);
|
||||
builder.field("field", name);
|
||||
if (nullValue != null) {
|
||||
builder.field("null_value", nullValue);
|
||||
}
|
||||
if (existence != null) {
|
||||
builder.field("existence", existence);
|
||||
}
|
||||
if (filterName != null) {
|
||||
builder.field("_name", filterName);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.apache.lucene.search.Filter;
|
|||
import org.apache.lucene.search.TermRangeFilter;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.lucene.search.NotFilter;
|
||||
import org.elasticsearch.common.lucene.search.XBooleanFilter;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
|
||||
|
@ -52,6 +53,8 @@ public class MissingFilterParser implements FilterParser {
|
|||
|
||||
String fieldName = null;
|
||||
String filterName = null;
|
||||
boolean nullValue = false;
|
||||
boolean existence = true;
|
||||
|
||||
XContentParser.Token token;
|
||||
String currentFieldName = null;
|
||||
|
@ -61,6 +64,10 @@ public class MissingFilterParser implements FilterParser {
|
|||
} else if (token.isValue()) {
|
||||
if ("field".equals(currentFieldName)) {
|
||||
fieldName = parser.text();
|
||||
} else if ("null_value".equals(currentFieldName)) {
|
||||
nullValue = parser.booleanValue();
|
||||
} else if ("existence".equals(currentFieldName)) {
|
||||
existence = parser.booleanValue();
|
||||
} else if ("_name".equals(currentFieldName)) {
|
||||
filterName = parser.text();
|
||||
} else {
|
||||
|
@ -70,27 +77,66 @@ public class MissingFilterParser implements FilterParser {
|
|||
}
|
||||
|
||||
if (fieldName == null) {
|
||||
throw new QueryParsingException(parseContext.index(), "exists must be provided with a [field]");
|
||||
throw new QueryParsingException(parseContext.index(), "missing must be provided with a [field]");
|
||||
}
|
||||
|
||||
Filter filter = null;
|
||||
if (!existence && !nullValue) {
|
||||
throw new QueryParsingException(parseContext.index(), "missing must have either existence, or null_value, or both set to true");
|
||||
}
|
||||
|
||||
Filter existenceFilter = null;
|
||||
Filter nullFilter = null;
|
||||
|
||||
|
||||
MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
|
||||
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
|
||||
filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true, parseContext);
|
||||
}
|
||||
if (filter == null) {
|
||||
filter = new TermRangeFilter(fieldName, null, null, true, true);
|
||||
|
||||
if (existence) {
|
||||
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
|
||||
existenceFilter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true, parseContext);
|
||||
}
|
||||
if (existenceFilter == null) {
|
||||
existenceFilter = new TermRangeFilter(fieldName, null, null, true, true);
|
||||
}
|
||||
|
||||
// we always cache this one, really does not change... (exists)
|
||||
existenceFilter = parseContext.cacheFilter(existenceFilter, null);
|
||||
existenceFilter = new NotFilter(existenceFilter);
|
||||
// cache the not filter as well, so it will be faster
|
||||
existenceFilter = parseContext.cacheFilter(existenceFilter, null);
|
||||
}
|
||||
|
||||
// we always cache this one, really does not change... (exists)
|
||||
filter = parseContext.cacheFilter(filter, null);
|
||||
filter = new NotFilter(filter);
|
||||
// cache the not filter as well, so it will be faster
|
||||
filter = parseContext.cacheFilter(filter, null);
|
||||
if (nullValue) {
|
||||
if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
|
||||
nullFilter = smartNameFieldMappers.mapper().nullValueFilter();
|
||||
if (nullFilter != null) {
|
||||
// cache the not filter as well, so it will be faster
|
||||
nullFilter = parseContext.cacheFilter(nullFilter, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filter filter;
|
||||
if (nullFilter != null) {
|
||||
if (existenceFilter != null) {
|
||||
XBooleanFilter combined = new XBooleanFilter();
|
||||
combined.addShould(existenceFilter);
|
||||
combined.addShould(nullFilter);
|
||||
// cache the not filter as well, so it will be faster
|
||||
filter = parseContext.cacheFilter(combined, null);
|
||||
} else {
|
||||
filter = nullFilter;
|
||||
}
|
||||
} else {
|
||||
filter = existenceFilter;
|
||||
}
|
||||
|
||||
if (filter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
filter = wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
|
||||
if (filterName != null) {
|
||||
parseContext.addNamedFilter(filterName, filter);
|
||||
parseContext.addNamedFilter(filterName, existenceFilter);
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue