Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.elasticsearch.index.mapper.NumberFieldMapper Maven / Gradle / Ivy
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.mapper;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.HalfFloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.elasticsearch.Version;
import org.elasticsearch.action.fieldstats.FieldStats;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType;
import org.elasticsearch.index.fielddata.plain.DocValuesIndexFieldData;
import org.elasticsearch.index.mapper.LegacyNumberFieldMapper.Defaults;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.DocValueFormat;
import org.joda.time.DateTimeZone;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** A {@link FieldMapper} for numeric types: byte, short, int, long, float and double. */
public class NumberFieldMapper extends FieldMapper {
// this is private since it has a different default
static final Setting COERCE_SETTING =
Setting.boolSetting("index.mapping.coerce", true, Property.IndexScope);
public static class Builder extends FieldMapper.Builder {
private Boolean ignoreMalformed;
private Boolean coerce;
public Builder(String name, NumberType type) {
super(name, new NumberFieldType(type), new NumberFieldType(type));
builder = this;
}
public Builder ignoreMalformed(boolean ignoreMalformed) {
this.ignoreMalformed = ignoreMalformed;
return builder;
}
protected Explicit ignoreMalformed(BuilderContext context) {
if (ignoreMalformed != null) {
return new Explicit<>(ignoreMalformed, true);
}
if (context.indexSettings() != null) {
return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false);
}
return Defaults.IGNORE_MALFORMED;
}
public Builder coerce(boolean coerce) {
this.coerce = coerce;
return builder;
}
protected Explicit coerce(BuilderContext context) {
if (coerce != null) {
return new Explicit<>(coerce, true);
}
if (context.indexSettings() != null) {
return new Explicit<>(COERCE_SETTING.get(context.indexSettings()), false);
}
return Defaults.COERCE;
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
}
@Override
public NumberFieldMapper build(BuilderContext context) {
setupFieldType(context);
return new NumberFieldMapper(name, fieldType, defaultFieldType, ignoreMalformed(context),
coerce(context), includeInAll, context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
}
}
public static class TypeParser implements Mapper.TypeParser {
final NumberType type;
public TypeParser(NumberType type) {
this.type = type;
}
@Override
public Mapper.Builder,?> parse(String name, Map node,
ParserContext parserContext) throws MapperParsingException {
if (parserContext.indexVersionCreated().before(Version.V_5_0_0_alpha2)) {
switch (type) {
case BYTE:
return new LegacyByteFieldMapper.TypeParser().parse(name, node, parserContext);
case SHORT:
return new LegacyShortFieldMapper.TypeParser().parse(name, node, parserContext);
case INTEGER:
return new LegacyIntegerFieldMapper.TypeParser().parse(name, node, parserContext);
case LONG:
return new LegacyLongFieldMapper.TypeParser().parse(name, node, parserContext);
case FLOAT:
return new LegacyFloatFieldMapper.TypeParser().parse(name, node, parserContext);
case DOUBLE:
return new LegacyDoubleFieldMapper.TypeParser().parse(name, node, parserContext);
default:
throw new AssertionError();
}
}
Builder builder = new Builder(name, type);
TypeParsers.parseField(builder, name, node, parserContext);
for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = iterator.next();
String propName = entry.getKey();
Object propNode = entry.getValue();
if (propName.equals("null_value")) {
if (propNode == null) {
throw new MapperParsingException("Property [null_value] cannot be null.");
}
builder.nullValue(type.parse(propNode, false));
iterator.remove();
} else if (propName.equals("ignore_malformed")) {
builder.ignoreMalformed(TypeParsers.nodeBooleanValue(name,"ignore_malformed", propNode));
iterator.remove();
} else if (propName.equals("coerce")) {
builder.coerce(TypeParsers.nodeBooleanValue(name, "coerce", propNode));
iterator.remove();
}
}
return builder;
}
}
public enum NumberType {
HALF_FLOAT("half_float", NumericType.HALF_FLOAT) {
@Override
Float parse(Object value, boolean coerce) {
return (Float) FLOAT.parse(value, false);
}
@Override
Float parse(XContentParser parser, boolean coerce) throws IOException {
return parser.floatValue(coerce);
}
@Override
Query termQuery(String field, Object value) {
float v = parse(value, false);
return HalfFloatPoint.newExactQuery(field, v);
}
@Override
Query termsQuery(String field, List values) {
float[] v = new float[values.size()];
for (int i = 0; i < values.size(); ++i) {
v[i] = parse(values.get(i), false);
}
return HalfFloatPoint.newSetQuery(field, v);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
float l = Float.NEGATIVE_INFINITY;
float u = Float.POSITIVE_INFINITY;
if (lowerTerm != null) {
l = parse(lowerTerm, false);
if (includeLower) {
l = HalfFloatPoint.nextDown(l);
}
l = HalfFloatPoint.nextUp(l);
}
if (upperTerm != null) {
u = parse(upperTerm, false);
if (includeUpper) {
u = HalfFloatPoint.nextUp(u);
}
u = HalfFloatPoint.nextDown(u);
}
Query query = HalfFloatPoint.newRangeQuery(field, l, u);
if (hasDocValues) {
Query dvQuery = SortedNumericDocValuesField.newRangeQuery(field,
HalfFloatPoint.halfFloatToSortableShort(l),
HalfFloatPoint.halfFloatToSortableShort(u));
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
List fields = new ArrayList<>();
if (indexed) {
fields.add(new HalfFloatPoint(name, value.floatValue()));
}
if (docValued) {
fields.add(new SortedNumericDocValuesField(name,
HalfFloatPoint.halfFloatToSortableShort(value.floatValue())));
}
if (stored) {
fields.add(new StoredField(name, value.floatValue()));
}
return fields;
}
@Override
FieldStats.Double stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
FieldInfo fi = org.apache.lucene.index.MultiFields.getMergedFieldInfos(reader).fieldInfo(fieldName);
if (fi == null) {
return null;
}
long size = PointValues.size(reader, fieldName);
if (size == 0) {
return new FieldStats.Double(reader.maxDoc(), 0, -1, -1, isSearchable, isAggregatable);
}
int docCount = PointValues.getDocCount(reader, fieldName);
byte[] min = PointValues.getMinPackedValue(reader, fieldName);
byte[] max = PointValues.getMaxPackedValue(reader, fieldName);
return new FieldStats.Double(reader.maxDoc(), docCount, -1L, size,
isSearchable, isAggregatable,
HalfFloatPoint.decodeDimension(min, 0), HalfFloatPoint.decodeDimension(max, 0));
}
},
FLOAT("float", NumericType.FLOAT) {
@Override
Float parse(Object value, boolean coerce) {
if (value instanceof Number) {
return ((Number) value).floatValue();
}
if (value instanceof BytesRef) {
value = ((BytesRef) value).utf8ToString();
}
return Float.parseFloat(value.toString());
}
@Override
Float parse(XContentParser parser, boolean coerce) throws IOException {
return parser.floatValue(coerce);
}
@Override
Query termQuery(String field, Object value) {
float v = parse(value, false);
return FloatPoint.newExactQuery(field, v);
}
@Override
Query termsQuery(String field, List values) {
float[] v = new float[values.size()];
for (int i = 0; i < values.size(); ++i) {
v[i] = parse(values.get(i), false);
}
return FloatPoint.newSetQuery(field, v);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
float l = Float.NEGATIVE_INFINITY;
float u = Float.POSITIVE_INFINITY;
if (lowerTerm != null) {
l = parse(lowerTerm, false);
if (includeLower == false) {
l = FloatPoint.nextUp(l);
}
}
if (upperTerm != null) {
u = parse(upperTerm, false);
if (includeUpper == false) {
u = FloatPoint.nextDown(u);
}
}
Query query = FloatPoint.newRangeQuery(field, l, u);
if (hasDocValues) {
Query dvQuery = SortedNumericDocValuesField.newRangeQuery(field,
NumericUtils.floatToSortableInt(l),
NumericUtils.floatToSortableInt(u));
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
List fields = new ArrayList<>();
if (indexed) {
fields.add(new FloatPoint(name, value.floatValue()));
}
if (docValued) {
fields.add(new SortedNumericDocValuesField(name,
NumericUtils.floatToSortableInt(value.floatValue())));
}
if (stored) {
fields.add(new StoredField(name, value.floatValue()));
}
return fields;
}
@Override
FieldStats.Double stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
FieldInfo fi = org.apache.lucene.index.MultiFields.getMergedFieldInfos(reader).fieldInfo(fieldName);
if (fi == null) {
return null;
}
long size = PointValues.size(reader, fieldName);
if (size == 0) {
return new FieldStats.Double(reader.maxDoc(), 0, -1, -1, isSearchable, isAggregatable);
}
int docCount = PointValues.getDocCount(reader, fieldName);
byte[] min = PointValues.getMinPackedValue(reader, fieldName);
byte[] max = PointValues.getMaxPackedValue(reader, fieldName);
return new FieldStats.Double(reader.maxDoc(),docCount, -1L, size,
isSearchable, isAggregatable,
FloatPoint.decodeDimension(min, 0), FloatPoint.decodeDimension(max, 0));
}
},
DOUBLE("double", NumericType.DOUBLE) {
@Override
Double parse(Object value, boolean coerce) {
return objectToDouble(value);
}
@Override
Double parse(XContentParser parser, boolean coerce) throws IOException {
return parser.doubleValue(coerce);
}
@Override
Query termQuery(String field, Object value) {
double v = parse(value, false);
return DoublePoint.newExactQuery(field, v);
}
@Override
Query termsQuery(String field, List values) {
double[] v = new double[values.size()];
for (int i = 0; i < values.size(); ++i) {
v[i] = parse(values.get(i), false);
}
return DoublePoint.newSetQuery(field, v);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
double l = Double.NEGATIVE_INFINITY;
double u = Double.POSITIVE_INFINITY;
if (lowerTerm != null) {
l = parse(lowerTerm, false);
if (includeLower == false) {
l = DoublePoint.nextUp(l);
}
}
if (upperTerm != null) {
u = parse(upperTerm, false);
if (includeUpper == false) {
u = DoublePoint.nextDown(u);
}
}
Query query = DoublePoint.newRangeQuery(field, l, u);
if (hasDocValues) {
Query dvQuery = SortedNumericDocValuesField.newRangeQuery(field,
NumericUtils.doubleToSortableLong(l),
NumericUtils.doubleToSortableLong(u));
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
List fields = new ArrayList<>();
if (indexed) {
fields.add(new DoublePoint(name, value.doubleValue()));
}
if (docValued) {
fields.add(new SortedNumericDocValuesField(name,
NumericUtils.doubleToSortableLong(value.doubleValue())));
}
if (stored) {
fields.add(new StoredField(name, value.doubleValue()));
}
return fields;
}
@Override
FieldStats.Double stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
FieldInfo fi = org.apache.lucene.index.MultiFields.getMergedFieldInfos(reader).fieldInfo(fieldName);
if (fi == null) {
return null;
}
long size = PointValues.size(reader, fieldName);
if (size == 0) {
return new FieldStats.Double(reader.maxDoc(),0, -1, -1, isSearchable, isAggregatable);
}
int docCount = PointValues.getDocCount(reader, fieldName);
byte[] min = PointValues.getMinPackedValue(reader, fieldName);
byte[] max = PointValues.getMaxPackedValue(reader, fieldName);
return new FieldStats.Double(reader.maxDoc(),docCount, -1L, size,
isSearchable, isAggregatable,
DoublePoint.decodeDimension(min, 0), DoublePoint.decodeDimension(max, 0));
}
},
BYTE("byte", NumericType.BYTE) {
@Override
Byte parse(Object value, boolean coerce) {
double doubleValue = objectToDouble(value);
if (doubleValue < Byte.MIN_VALUE || doubleValue > Byte.MAX_VALUE) {
throw new IllegalArgumentException("Value [" + value + "] is out of range for a byte");
}
if (!coerce && doubleValue % 1 != 0) {
throw new IllegalArgumentException("Value [" + value + "] has a decimal part");
}
if (value instanceof Number) {
return ((Number) value).byteValue();
}
return (byte) doubleValue;
}
@Override
Short parse(XContentParser parser, boolean coerce) throws IOException {
int value = parser.intValue(coerce);
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw new IllegalArgumentException("Value [" + value + "] is out of range for a byte");
}
return (short) value;
}
@Override
Query termQuery(String field, Object value) {
return INTEGER.termQuery(field, value);
}
@Override
Query termsQuery(String field, List values) {
return INTEGER.termsQuery(field, values);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
return INTEGER.rangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues);
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
return INTEGER.createFields(name, value, indexed, docValued, stored);
}
@Override
FieldStats.Long stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
return (FieldStats.Long) INTEGER.stats(reader, fieldName, isSearchable, isAggregatable);
}
@Override
Number valueForSearch(Number value) {
return value.byteValue();
}
},
SHORT("short", NumericType.SHORT) {
@Override
Short parse(Object value, boolean coerce) {
double doubleValue = objectToDouble(value);
if (doubleValue < Short.MIN_VALUE || doubleValue > Short.MAX_VALUE) {
throw new IllegalArgumentException("Value [" + value + "] is out of range for a short");
}
if (!coerce && doubleValue % 1 != 0) {
throw new IllegalArgumentException("Value [" + value + "] has a decimal part");
}
if (value instanceof Number) {
return ((Number) value).shortValue();
}
return (short) doubleValue;
}
@Override
Short parse(XContentParser parser, boolean coerce) throws IOException {
return parser.shortValue(coerce);
}
@Override
Query termQuery(String field, Object value) {
return INTEGER.termQuery(field, value);
}
@Override
Query termsQuery(String field, List values) {
return INTEGER.termsQuery(field, values);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
return INTEGER.rangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues);
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
return INTEGER.createFields(name, value, indexed, docValued, stored);
}
@Override
FieldStats.Long stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
return (FieldStats.Long) INTEGER.stats(reader, fieldName, isSearchable, isAggregatable);
}
@Override
Number valueForSearch(Number value) {
return value.shortValue();
}
},
INTEGER("integer", NumericType.INT) {
@Override
Integer parse(Object value, boolean coerce) {
double doubleValue = objectToDouble(value);
if (doubleValue < Integer.MIN_VALUE || doubleValue > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Value [" + value + "] is out of range for an integer");
}
if (!coerce && doubleValue % 1 != 0) {
throw new IllegalArgumentException("Value [" + value + "] has a decimal part");
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
return (int) doubleValue;
}
@Override
Integer parse(XContentParser parser, boolean coerce) throws IOException {
return parser.intValue(coerce);
}
@Override
Query termQuery(String field, Object value) {
if (hasDecimalPart(value)) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part");
}
int v = parse(value, true);
return IntPoint.newExactQuery(field, v);
}
@Override
Query termsQuery(String field, List values) {
int[] v = new int[values.size()];
int upTo = 0;
for (int i = 0; i < values.size(); i++) {
Object value = values.get(i);
if (!hasDecimalPart(value)) {
v[upTo++] = parse(value, true);
}
}
if (upTo == 0) {
return Queries.newMatchNoDocsQuery("All values have a decimal part");
}
if (upTo != v.length) {
v = Arrays.copyOf(v, upTo);
}
return IntPoint.newSetQuery(field, v);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
int l = Integer.MIN_VALUE;
int u = Integer.MAX_VALUE;
if (lowerTerm != null) {
l = parse(lowerTerm, true);
// if the lower bound is decimal:
// - if the bound is positive then we increment it:
// if lowerTerm=1.5 then the (inclusive) bound becomes 2
// - if the bound is negative then we leave it as is:
// if lowerTerm=-1.5 then the (inclusive) bound becomes -1 due to the call to longValue
boolean lowerTermHasDecimalPart = hasDecimalPart(lowerTerm);
if ((lowerTermHasDecimalPart == false && includeLower == false) ||
(lowerTermHasDecimalPart && signum(lowerTerm) > 0)) {
if (l == Integer.MAX_VALUE) {
return new MatchNoDocsQuery();
}
++l;
}
}
if (upperTerm != null) {
u = parse(upperTerm, true);
boolean upperTermHasDecimalPart = hasDecimalPart(upperTerm);
if ((upperTermHasDecimalPart == false && includeUpper == false) ||
(upperTermHasDecimalPart && signum(upperTerm) < 0)) {
if (u == Integer.MIN_VALUE) {
return new MatchNoDocsQuery();
}
--u;
}
}
Query query = IntPoint.newRangeQuery(field, l, u);
if (hasDocValues) {
Query dvQuery = SortedNumericDocValuesField.newRangeQuery(field, l, u);
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
List fields = new ArrayList<>();
if (indexed) {
fields.add(new IntPoint(name, value.intValue()));
}
if (docValued) {
fields.add(new SortedNumericDocValuesField(name, value.intValue()));
}
if (stored) {
fields.add(new StoredField(name, value.intValue()));
}
return fields;
}
@Override
FieldStats.Long stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
FieldInfo fi = org.apache.lucene.index.MultiFields.getMergedFieldInfos(reader).fieldInfo(fieldName);
if (fi == null) {
return null;
}
long size = PointValues.size(reader, fieldName);
if (size == 0) {
return new FieldStats.Long(reader.maxDoc(), 0, -1, -1, isSearchable, isAggregatable);
}
int docCount = PointValues.getDocCount(reader, fieldName);
byte[] min = PointValues.getMinPackedValue(reader, fieldName);
byte[] max = PointValues.getMaxPackedValue(reader, fieldName);
return new FieldStats.Long(reader.maxDoc(),docCount, -1L, size,
isSearchable, isAggregatable,
IntPoint.decodeDimension(min, 0), IntPoint.decodeDimension(max, 0));
}
},
LONG("long", NumericType.LONG) {
@Override
Long parse(Object value, boolean coerce) {
double doubleValue = objectToDouble(value);
if (doubleValue < Long.MIN_VALUE || doubleValue > Long.MAX_VALUE) {
throw new IllegalArgumentException("Value [" + value + "] is out of range for a long");
}
if (!coerce && doubleValue % 1 != 0) {
throw new IllegalArgumentException("Value [" + value + "] has a decimal part");
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
// longs need special handling so we don't lose precision while parsing
String stringValue = (value instanceof BytesRef) ? ((BytesRef) value).utf8ToString() : value.toString();
try {
return Long.parseLong(stringValue);
} catch (NumberFormatException e) {
return (long) Double.parseDouble(stringValue);
}
}
@Override
Long parse(XContentParser parser, boolean coerce) throws IOException {
return parser.longValue(coerce);
}
@Override
Query termQuery(String field, Object value) {
if (hasDecimalPart(value)) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part");
}
long v = parse(value, true);
return LongPoint.newExactQuery(field, v);
}
@Override
Query termsQuery(String field, List values) {
long[] v = new long[values.size()];
int upTo = 0;
for (int i = 0; i < values.size(); i++) {
Object value = values.get(i);
if (!hasDecimalPart(value)) {
v[upTo++] = parse(value, true);
}
}
if (upTo == 0) {
return Queries.newMatchNoDocsQuery("All values have a decimal part");
}
if (upTo != v.length) {
v = Arrays.copyOf(v, upTo);
}
return LongPoint.newSetQuery(field, v);
}
@Override
Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues) {
long l = Long.MIN_VALUE;
long u = Long.MAX_VALUE;
if (lowerTerm != null) {
l = parse(lowerTerm, true);
// if the lower bound is decimal:
// - if the bound is positive then we increment it:
// if lowerTerm=1.5 then the (inclusive) bound becomes 2
// - if the bound is negative then we leave it as is:
// if lowerTerm=-1.5 then the (inclusive) bound becomes -1 due to the call to longValue
boolean lowerTermHasDecimalPart = hasDecimalPart(lowerTerm);
if ((lowerTermHasDecimalPart == false && includeLower == false) ||
(lowerTermHasDecimalPart && signum(lowerTerm) > 0)) {
if (l == Long.MAX_VALUE) {
return new MatchNoDocsQuery();
}
++l;
}
}
if (upperTerm != null) {
u = parse(upperTerm, true);
boolean upperTermHasDecimalPart = hasDecimalPart(upperTerm);
if ((upperTermHasDecimalPart == false && includeUpper == false) ||
(upperTermHasDecimalPart && signum(upperTerm) < 0)) {
if (u == Long.MIN_VALUE) {
return new MatchNoDocsQuery();
}
--u;
}
}
Query query = LongPoint.newRangeQuery(field, l, u);
if (hasDocValues) {
Query dvQuery = SortedNumericDocValuesField.newRangeQuery(field, l, u);
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
}
@Override
public List createFields(String name, Number value,
boolean indexed, boolean docValued, boolean stored) {
List fields = new ArrayList<>();
if (indexed) {
fields.add(new LongPoint(name, value.longValue()));
}
if (docValued) {
fields.add(new SortedNumericDocValuesField(name, value.longValue()));
}
if (stored) {
fields.add(new StoredField(name, value.longValue()));
}
return fields;
}
@Override
FieldStats.Long stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException {
FieldInfo fi = org.apache.lucene.index.MultiFields.getMergedFieldInfos(reader).fieldInfo(fieldName);
if (fi == null) {
return null;
}
long size = PointValues.size(reader, fieldName);
if (size == 0) {
return new FieldStats.Long(reader.maxDoc(), 0, -1, -1, isSearchable, isAggregatable);
}
int docCount = PointValues.getDocCount(reader, fieldName);
byte[] min = PointValues.getMinPackedValue(reader, fieldName);
byte[] max = PointValues.getMaxPackedValue(reader, fieldName);
return new FieldStats.Long(reader.maxDoc(),docCount, -1L, size,
isSearchable, isAggregatable,
LongPoint.decodeDimension(min, 0), LongPoint.decodeDimension(max, 0));
}
};
private final String name;
private final NumericType numericType;
NumberType(String name, NumericType numericType) {
this.name = name;
this.numericType = numericType;
}
/** Get the associated type name. */
public final String typeName() {
return name;
}
/** Get the associated numeric type */
final NumericType numericType() {
return numericType;
}
abstract Query termQuery(String field, Object value);
abstract Query termsQuery(String field, List values);
abstract Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
boolean includeLower, boolean includeUpper,
boolean hasDocValues);
abstract Number parse(XContentParser parser, boolean coerce) throws IOException;
abstract Number parse(Object value, boolean coerce);
public abstract List createFields(String name, Number value, boolean indexed,
boolean docValued, boolean stored);
abstract FieldStats extends Number> stats(IndexReader reader, String fieldName,
boolean isSearchable, boolean isAggregatable) throws IOException;
Number valueForSearch(Number value) {
return value;
}
/**
* Returns true if the object is a number and has a decimal part
*/
boolean hasDecimalPart(Object number) {
if (number instanceof Number) {
double doubleValue = ((Number) number).doubleValue();
return doubleValue % 1 != 0;
}
if (number instanceof BytesRef) {
number = ((BytesRef) number).utf8ToString();
}
if (number instanceof String) {
return Double.parseDouble((String) number) % 1 != 0;
}
return false;
}
/**
* Returns -1, 0, or 1 if the value is lower than, equal to, or greater than 0
*/
double signum(Object value) {
if (value instanceof Number) {
double doubleValue = ((Number) value).doubleValue();
return Math.signum(doubleValue);
}
if (value instanceof BytesRef) {
value = ((BytesRef) value).utf8ToString();
}
return Math.signum(Double.parseDouble(value.toString()));
}
/**
* Converts an Object to a double by checking it against known types first
*/
private static double objectToDouble(Object value) {
double doubleValue;
if (value instanceof Number) {
doubleValue = ((Number) value).doubleValue();
} else if (value instanceof BytesRef) {
doubleValue = Double.parseDouble(((BytesRef) value).utf8ToString());
} else {
doubleValue = Double.parseDouble(value.toString());
}
return doubleValue;
}
}
public static final class NumberFieldType extends MappedFieldType {
NumberType type;
public NumberFieldType(NumberType type) {
super();
this.type = Objects.requireNonNull(type);
setTokenized(false);
setHasDocValues(true);
setOmitNorms(true);
}
NumberFieldType(NumberFieldType other) {
super(other);
this.type = other.type;
}
@Override
public MappedFieldType clone() {
return new NumberFieldType(this);
}
@Override
public String typeName() {
return type.name;
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
failIfNotIndexed();
Query query = type.termQuery(name(), value);
if (boost() != 1f) {
query = new BoostQuery(query, boost());
}
return query;
}
@Override
public Query termsQuery(List values, QueryShardContext context) {
failIfNotIndexed();
Query query = type.termsQuery(name(), values);
if (boost() != 1f) {
query = new BoostQuery(query, boost());
}
return query;
}
@Override
public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, QueryShardContext context) {
failIfNotIndexed();
Query query = type.rangeQuery(name(), lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues());
if (boost() != 1f) {
query = new BoostQuery(query, boost());
}
return query;
}
@Override
public FieldStats stats(IndexReader reader) throws IOException {
return type.stats(reader, name(), isSearchable(), isAggregatable());
}
@Override
public IndexFieldData.Builder fielddataBuilder() {
failIfNoDocValues();
return new DocValuesIndexFieldData.Builder().numericType(type.numericType());
}
@Override
public Object valueForDisplay(Object value) {
if (value == null) {
return null;
}
return type.valueForSearch((Number) value);
}
@Override
public DocValueFormat docValueFormat(String format, DateTimeZone timeZone) {
if (timeZone != null) {
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName()
+ "] does not support custom time zones");
}
if (format == null) {
return DocValueFormat.RAW;
} else {
return new DocValueFormat.Decimal(format);
}
}
}
private Boolean includeInAll;
private Explicit ignoreMalformed;
private Explicit coerce;
private NumberFieldMapper(
String simpleName,
MappedFieldType fieldType,
MappedFieldType defaultFieldType,
Explicit ignoreMalformed,
Explicit coerce,
Boolean includeInAll,
Settings indexSettings,
MultiFields multiFields,
CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
this.ignoreMalformed = ignoreMalformed;
this.coerce = coerce;
this.includeInAll = includeInAll;
}
@Override
public NumberFieldType fieldType() {
return (NumberFieldType) super.fieldType();
}
@Override
protected String contentType() {
return fieldType.typeName();
}
@Override
protected NumberFieldMapper clone() {
return (NumberFieldMapper) super.clone();
}
@Override
protected void parseCreateField(ParseContext context, List fields) throws IOException {
final boolean includeInAll = context.includeInAll(this.includeInAll, this);
XContentParser parser = context.parser();
Object value;
Number numericValue = null;
if (context.externalValueSet()) {
value = context.externalValue();
} else if (parser.currentToken() == Token.VALUE_NULL) {
value = null;
} else if (coerce.value()
&& parser.currentToken() == Token.VALUE_STRING
&& parser.textLength() == 0) {
value = null;
} else {
try {
numericValue = fieldType().type.parse(parser, coerce.value());
} catch (IllegalArgumentException e) {
if (ignoreMalformed.value()) {
return;
} else {
throw e;
}
}
if (includeInAll) {
value = parser.textOrNull(); // preserve formatting
} else {
value = numericValue;
}
}
if (value == null) {
value = fieldType().nullValue();
}
if (value == null) {
return;
}
if (numericValue == null) {
numericValue = fieldType().type.parse(value, coerce.value());
}
if (includeInAll) {
context.allEntries().addText(fieldType().name(), value.toString(), fieldType().boost());
}
boolean indexed = fieldType().indexOptions() != IndexOptions.NONE;
boolean docValued = fieldType().hasDocValues();
boolean stored = fieldType().stored();
fields.addAll(fieldType().type.createFields(fieldType().name(), numericValue, indexed, docValued, stored));
}
@Override
protected void doMerge(Mapper mergeWith, boolean updateAllTypes) {
super.doMerge(mergeWith, updateAllTypes);
NumberFieldMapper other = (NumberFieldMapper) mergeWith;
this.includeInAll = other.includeInAll;
if (other.ignoreMalformed.explicit()) {
this.ignoreMalformed = other.ignoreMalformed;
}
if (other.coerce.explicit()) {
this.coerce = other.coerce;
}
}
@Override
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);
if (includeDefaults || ignoreMalformed.explicit()) {
builder.field("ignore_malformed", ignoreMalformed.value());
}
if (includeDefaults || coerce.explicit()) {
builder.field("coerce", coerce.value());
}
if (includeDefaults || fieldType().nullValue() != null) {
builder.field("null_value", fieldType().nullValue());
}
if (includeInAll != null) {
builder.field("include_in_all", includeInAll);
} else if (includeDefaults) {
builder.field("include_in_all", false);
}
}
}