Browse Source

XContentBuilder to handle BigInteger and BigDecimal (#32888)

Although we allow to index BigInteger and BigDecimal into a keyword
field, source filtering on these fields would fail
as XContentBuilder was not able to deserialize BigInteger and BigDecimal
to json.

This modifies XContentBuilder to allow to handle BigInteger and
BigDecimal.

Closes #32395
Mayya Sharipova 7 years ago
parent
commit
80c5d30f30

+ 79 - 1
libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java

@@ -25,6 +25,8 @@ import java.io.Flushable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.nio.file.Path;
 import java.time.ZonedDateTime;
 import java.util.Arrays;
@@ -103,7 +105,8 @@ public final class XContentBuilder implements Closeable, Flushable {
         writers.put(ZonedDateTime.class, (b, v) -> b.value(v.toString()));
         writers.put(Calendar.class, XContentBuilder::timeValue);
         writers.put(GregorianCalendar.class, XContentBuilder::timeValue);
-
+        writers.put(BigInteger.class, (b, v) -> b.value((BigInteger) v));
+        writers.put(BigDecimal.class, (b, v) -> b.value((BigDecimal) v));
 
         Map<Class<?>, HumanReadableTransformer> humanReadableTransformer = new HashMap<>();
         Map<Class<?>, Function<Object, Object>> dateTransformers = new HashMap<>();
@@ -546,6 +549,81 @@ public final class XContentBuilder implements Closeable, Flushable {
         return this;
     }
 
+    ////////////////////////////////////////////////////////////////////////////
+    // BigInteger
+    //////////////////////////////////
+
+    public XContentBuilder field(String name, BigInteger value) throws IOException {
+        if (value == null) {
+            return nullField(name);
+        }
+        ensureNameNotNull(name);
+        generator.writeNumberField(name, value);
+        return this;
+    }
+
+    public XContentBuilder array(String name, BigInteger[] values) throws IOException {
+        return field(name).values(values);
+    }
+
+    private XContentBuilder values(BigInteger[] values) throws IOException {
+        if (values == null) {
+            return nullValue();
+        }
+        startArray();
+        for (BigInteger b : values) {
+            value(b);
+        }
+        endArray();
+        return this;
+    }
+
+    public XContentBuilder value(BigInteger value) throws IOException {
+        if (value == null) {
+            return nullValue();
+        }
+        generator.writeNumber(value);
+        return this;
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // BigDecimal
+    //////////////////////////////////
+
+    public XContentBuilder field(String name, BigDecimal value) throws IOException {
+        if (value == null) {
+            return nullField(name);
+        }
+        ensureNameNotNull(name);
+        generator.writeNumberField(name, value);
+        return this;
+    }
+
+    public XContentBuilder array(String name, BigDecimal[] values) throws IOException {
+        return field(name).values(values);
+    }
+
+    private XContentBuilder values(BigDecimal[] values) throws IOException {
+        if (values == null) {
+            return nullValue();
+        }
+        startArray();
+        for (BigDecimal b : values) {
+            value(b);
+        }
+        endArray();
+        return this;
+    }
+
+    public XContentBuilder value(BigDecimal value) throws IOException {
+        if (value == null) {
+            return nullValue();
+        }
+        generator.writeNumber(value);
+        return this;
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // String
     //////////////////////////////////

+ 10 - 0
libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentGenerator.java

@@ -23,6 +23,8 @@ import java.io.Closeable;
 import java.io.Flushable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 
 public interface XContentGenerator extends Closeable, Flushable {
 
@@ -70,6 +72,14 @@ public interface XContentGenerator extends Closeable, Flushable {
 
     void writeNumber(short value) throws IOException;
 
+    void writeNumber(BigInteger value) throws IOException;
+
+    void writeNumberField(String name, BigInteger value) throws IOException;
+
+    void writeNumber(BigDecimal value) throws IOException;
+
+    void writeNumberField(String name, BigDecimal value) throws IOException;
+
     void writeStringField(String name, String value) throws IOException;
 
     void writeString(String value) throws IOException;

+ 25 - 0
libs/x-content/src/main/java/org/elasticsearch/common/xcontent/json/JsonXContentGenerator.java

@@ -42,6 +42,8 @@ import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Objects;
 import java.util.Set;
 
@@ -226,6 +228,19 @@ public class JsonXContentGenerator implements XContentGenerator {
         generator.writeNumberField(name, value);
     }
 
+    @Override
+    public void writeNumberField(String name, BigInteger value) throws IOException {
+        // as jackson's JsonGenerator doesn't have this method for BigInteger
+        // we have to implement it ourselves
+        generator.writeFieldName(name);
+        generator.writeNumber(value);
+    }
+
+    @Override
+    public void writeNumberField(String name, BigDecimal value) throws IOException {
+        generator.writeNumberField(name, value);
+    }
+
     @Override
     public void writeNumber(int value) throws IOException {
         generator.writeNumber(value);
@@ -246,6 +261,16 @@ public class JsonXContentGenerator implements XContentGenerator {
         generator.writeNumber(value);
     }
 
+    @Override
+    public void writeNumber(BigInteger value) throws IOException {
+        generator.writeNumber(value);
+    }
+
+    @Override
+    public void writeNumber(BigDecimal value) throws IOException {
+        generator.writeNumber(value);
+    }
+
     @Override
     public void writeStringField(String name, String value) throws IOException {
         generator.writeStringField(name, value);

+ 10 - 0
rest-api-spec/src/main/resources/rest-api-spec/test/search/10_source_filtering.yml

@@ -112,6 +112,16 @@ setup:
   - match:  { hits.hits.0._source.bigint: 72057594037927936 }
   - is_false:  hits.hits.0._source.include.field2
 
+
+---
+"_source filtering on bigint":
+- do:
+    search:
+      body:
+        _source: ["bigint"]
+        query: { match_all: {} }
+- match:  { hits.hits.0._source.bigint: 72057594037927936 }
+
 ---
 "fields in body":
   - do:

+ 31 - 0
server/src/test/java/org/elasticsearch/common/xcontent/BaseXContentTestCase.java

@@ -48,6 +48,7 @@ import org.joda.time.format.ISODateTimeFormat;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.file.Path;
 import java.time.DayOfWeek;
@@ -266,6 +267,36 @@ public abstract class BaseXContentTestCase extends ESTestCase {
                 .endObject());
     }
 
+    public void testBigIntegers() throws Exception {
+        assertResult("{'bigint':null}", () -> builder().startObject().field("bigint", (BigInteger) null).endObject());
+        assertResult("{'bigint':[]}", () -> builder().startObject().array("bigint", new BigInteger[]{}).endObject());
+
+        BigInteger bigInteger = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
+        String result = "{'bigint':" + bigInteger.toString() + "}";
+        assertResult(result, () -> builder().startObject().field("bigint", bigInteger).endObject());
+
+        result = "{'bigint':[" + bigInteger.toString() + "," + bigInteger.toString() + "," + bigInteger.toString() +"]}";
+        assertResult(result, () -> builder()
+            .startObject()
+            .array("bigint", bigInteger, bigInteger, bigInteger)
+            .endObject());
+    }
+
+    public void testBigDecimals() throws Exception {
+        assertResult("{'bigdecimal':null}", () -> builder().startObject().field("bigdecimal", (BigInteger) null).endObject());
+        assertResult("{'bigdecimal':[]}", () -> builder().startObject().array("bigdecimal", new BigInteger[]{}).endObject());
+
+        BigDecimal bigDecimal = new BigDecimal("234.43");
+        String result = "{'bigdecimal':" + bigDecimal.toString() + "}";
+        assertResult(result, () -> builder().startObject().field("bigdecimal", bigDecimal).endObject());
+
+        result = "{'bigdecimal':[" + bigDecimal.toString() + "," + bigDecimal.toString() + "," + bigDecimal.toString() +"]}";
+        assertResult(result, () -> builder()
+            .startObject()
+            .array("bigdecimal", bigDecimal, bigDecimal, bigDecimal)
+            .endObject());
+    }
+
     public void testStrings() throws IOException {
         assertResult("{'string':null}", () -> builder().startObject().field("string", (String) null).endObject());
         assertResult("{'string':'value'}", () -> builder().startObject().field("string", "value").endObject());