Allow big integers and decimals to be mapped dynamically. (#42827)

This PR proposes to model big integers as longs (and big decimals as doubles)
in the context of dynamic mappings.

Previously, the dynamic mapping logic did not recognize big integers or
decimals, and would an error of the form "No matching token for number_type
[BIG_INTEGER]" when a dynamic big integer was encountered. It now accepts these
numeric types and interprets them as 'long' and 'double' respectively. This
allows `dynamic_templates` to accept and and remap them as another type such as
`keyword` or `scaled_float`.

Addresses #37846.
This commit is contained in:
Julie Tibshirani 2019-06-13 12:07:40 -07:00
parent 3928c624a3
commit 4b1d8e4433
4 changed files with 71 additions and 3 deletions

View File

@ -114,7 +114,7 @@ public interface XContentParser extends Closeable {
} }
enum NumberType { enum NumberType {
INT, LONG, FLOAT, DOUBLE INT, BIG_INTEGER, LONG, FLOAT, DOUBLE, BIG_DECIMAL
} }
XContentType contentType(); XContentType contentType();

View File

@ -199,12 +199,16 @@ public class JsonXContentParser extends AbstractXContentParser {
switch (numberType) { switch (numberType) {
case INT: case INT:
return NumberType.INT; return NumberType.INT;
case BIG_INTEGER:
return NumberType.BIG_INTEGER;
case LONG: case LONG:
return NumberType.LONG; return NumberType.LONG;
case FLOAT: case FLOAT:
return NumberType.FLOAT; return NumberType.FLOAT;
case DOUBLE: case DOUBLE:
return NumberType.DOUBLE; return NumberType.DOUBLE;
case BIG_DECIMAL:
return NumberType.BIG_DECIMAL;
} }
throw new IllegalStateException("No matching token for number_type [" + numberType + "]"); throw new IllegalStateException("No matching token for number_type [" + numberType + "]");
} }

View File

@ -765,13 +765,17 @@ final class DocumentParser {
return builder; return builder;
} else if (token == XContentParser.Token.VALUE_NUMBER) { } else if (token == XContentParser.Token.VALUE_NUMBER) {
XContentParser.NumberType numberType = context.parser().numberType(); XContentParser.NumberType numberType = context.parser().numberType();
if (numberType == XContentParser.NumberType.INT || numberType == XContentParser.NumberType.LONG) { if (numberType == XContentParser.NumberType.INT
|| numberType == XContentParser.NumberType.LONG
|| numberType == XContentParser.NumberType.BIG_INTEGER) {
Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.LONG); Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.LONG);
if (builder == null) { if (builder == null) {
builder = newLongBuilder(currentFieldName, context.indexSettings().getIndexVersionCreated()); builder = newLongBuilder(currentFieldName, context.indexSettings().getIndexVersionCreated());
} }
return builder; return builder;
} else if (numberType == XContentParser.NumberType.FLOAT || numberType == XContentParser.NumberType.DOUBLE) { } else if (numberType == XContentParser.NumberType.FLOAT
|| numberType == XContentParser.NumberType.DOUBLE
|| numberType == XContentParser.NumberType.BIG_DECIMAL) {
Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.DOUBLE); Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.DOUBLE);
if (builder == null) { if (builder == null) {
// no templates are defined, we use float by default instead of double // no templates are defined, we use float by default instead of double

View File

@ -19,6 +19,8 @@
package org.elasticsearch.index.mapper; package org.elasticsearch.index.mapper;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -37,6 +39,8 @@ import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.test.InternalSettingsPlugin; import org.elasticsearch.test.InternalSettingsPlugin;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -706,6 +710,62 @@ public class DocumentParserTests extends ESSingleNodeTestCase {
assertEquals(0, doc.rootDoc().getFields("foo").length); assertEquals(0, doc.rootDoc().getFields("foo").length);
} }
public void testDynamicBigInteger() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startArray("dynamic_templates").startObject()
.startObject("big-integer-to-keyword")
.field("match", "big-*")
.field("match_mapping_type", "long")
.startObject("mapping").field("type", "keyword").endObject()
.endObject()
.endObject().endArray()
.endObject()
.endObject());
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BigInteger value = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
BytesReference bytes = BytesReference.bytes(XContentFactory.jsonBuilder().startObject()
.field("big-integer", value)
.endObject());
ParsedDocument doc = mapper.parse(new SourceToParse("test", "type", "1", bytes, XContentType.JSON));
IndexableField[] fields = doc.rootDoc().getFields("big-integer");
assertEquals(2, fields.length);
assertTrue(fields[0].fieldType() instanceof KeywordFieldMapper.KeywordFieldType);
assertEquals(new BytesRef(value.toString()), fields[0].binaryValue());
}
public void testDynamicBigDecimal() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startArray("dynamic_templates").startObject()
.startObject("big-decimal-to-scaled-float")
.field("match", "big-*")
.field("match_mapping_type", "double")
.startObject("mapping")
.field("type", "keyword")
.endObject()
.endObject()
.endObject().endArray()
.endObject()
.endObject());
BigDecimal value = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(10.1));
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = BytesReference.bytes(XContentFactory.jsonBuilder().startObject()
.field("big-decimal", value)
.endObject());
ParsedDocument doc = mapper.parse(new SourceToParse("test", "type", "1", bytes, XContentType.JSON));
IndexableField[] fields = doc.rootDoc().getFields("big-decimal");
assertEquals(2, fields.length);
assertTrue(fields[0].fieldType() instanceof KeywordFieldMapper.KeywordFieldType);
assertEquals(new BytesRef(value.toString()), fields[0].binaryValue());
}
public void testDynamicDottedFieldNameLongArray() throws Exception { public void testDynamicDottedFieldNameLongArray() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser(); DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type") String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")