Jelajahi Sumber

Unsigned long should accept value with zero decimal (#75646)

Closes #67565
Nhat Nguyen 4 tahun lalu
induk
melakukan
8b9b8a9d9e

+ 21 - 4
x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java

@@ -508,21 +508,38 @@ public class UnsignedLongFieldMapper extends FieldMapper {
                     throw new IllegalArgumentException("Value [" + lv + "] is out of range for unsigned long.");
                 }
                 return lv;
+            } else if (value instanceof Double || value instanceof Float) {
+                final Number v = (Number) value;
+                if (Double.compare(v.doubleValue(), Math.floor(v.doubleValue())) != 0) {
+                    throw new IllegalArgumentException("Value \"" + value + "\" has a decimal part");
+                }
+                return parseUnsignedLong(v.longValue());
             } else if (value instanceof BigInteger) {
                 BigInteger bigIntegerValue = (BigInteger) value;
                 if (bigIntegerValue.compareTo(BIGINTEGER_2_64_MINUS_ONE) > 0 || bigIntegerValue.compareTo(BigInteger.ZERO) < 0) {
                     throw new IllegalArgumentException("Value [" + bigIntegerValue + "] is out of range for unsigned long");
                 }
                 return bigIntegerValue.longValue();
+            } else if (value instanceof BigDecimal) {
+                return parseUnsignedLong(((BigDecimal) value).toBigIntegerExact());
             }
             // throw exception for all other numeric types with decimal parts
-            throw new IllegalArgumentException("For input string: [" + value.toString() + "].");
+            throw new IllegalArgumentException("For input string: [" + value + "].");
         } else {
-            String stringValue = (value instanceof BytesRef) ? ((BytesRef) value).utf8ToString() : value.toString();
+            final String stringValue = (value instanceof BytesRef) ? ((BytesRef) value).utf8ToString() : value.toString();
             try {
                 return Long.parseUnsignedLong(stringValue);
-            } catch (NumberFormatException e) {
-                throw new IllegalArgumentException("For input string: \"" + stringValue + "\"");
+            } catch (NumberFormatException ignored) {
+                final BigInteger bigInteger;
+                try {
+                    final BigDecimal bigDecimal = new BigDecimal(stringValue);
+                    bigInteger = bigDecimal.toBigIntegerExact();
+                } catch (ArithmeticException e) {
+                    throw new IllegalArgumentException("Value \"" + stringValue + "\" has a decimal part");
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("For input string: \"" + stringValue + "\"");
+                }
+                return parseUnsignedLong(bigInteger);
             }
         }
     }

+ 24 - 1
x-pack/plugin/mapper-unsigned-long/src/test/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapperTests.java

@@ -26,6 +26,7 @@ import java.util.Collection;
 import java.util.List;
 
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
 
 public class UnsignedLongFieldMapperTests extends MapperTestCase {
 
@@ -96,7 +97,7 @@ public class UnsignedLongFieldMapperTests extends MapperTestCase {
         {
             ThrowingRunnable runnable = () -> mapper.parse(source(b -> b.field("field", 10.5)));
             MapperParsingException e = expectThrows(MapperParsingException.class, runnable);
-            assertThat(e.getCause().getMessage(), containsString("For input string: [10.5]"));
+            assertThat(e.getCause().getMessage(), containsString("Value \"10.5\" has a decimal part"));
         }
     }
 
@@ -205,6 +206,28 @@ public class UnsignedLongFieldMapperTests extends MapperTestCase {
         }
     }
 
+    public void testDecimalParts() throws IOException {
+        XContentBuilder mapping = fieldMapping(b -> b.field("type", "unsigned_long"));
+        DocumentMapper mapper = createDocumentMapper(mapping);
+        {
+            ThrowingRunnable runnable = () -> mapper.parse(source(b -> b.field("field", randomFrom("100.5", 100.5, 100.5f))));
+            MapperParsingException e = expectThrows(MapperParsingException.class, runnable);
+            assertThat(e.getCause().getMessage(), containsString("Value \"100.5\" has a decimal part"));
+        }
+        {
+            ThrowingRunnable runnable = () -> mapper.parse(source(b -> b.field("field", randomFrom("0.9", 0.9, 0.9f))));
+            MapperParsingException e = expectThrows(MapperParsingException.class, runnable);
+            assertThat(e.getCause().getMessage(), containsString("Value \"0.9\" has a decimal part"));
+        }
+        ParsedDocument doc = mapper.parse(source(b -> b.field("field", randomFrom("100.", "100.0", "100.00", 100.0, 100.0f))));
+        assertThat(doc.rootDoc().getFields("field")[0].numericValue().longValue(), equalTo(Long.MIN_VALUE + 100L));
+        assertThat(doc.rootDoc().getFields("field")[1].numericValue().longValue(), equalTo(Long.MIN_VALUE + 100L));
+
+        doc = mapper.parse(source(b -> b.field("field", randomFrom("0.", "0.0", ".00", 0.0, 0.0f))));
+        assertThat(doc.rootDoc().getFields("field")[0].numericValue().longValue(), equalTo(Long.MIN_VALUE));
+        assertThat(doc.rootDoc().getFields("field")[1].numericValue().longValue(), equalTo(Long.MIN_VALUE));
+    }
+
     public void testIndexingOutOfRangeValues() throws Exception {
         DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
         for (Object outOfRangeValue : new Object[] { "-1", -1L, "18446744073709551616", new BigInteger("18446744073709551616") }) {