You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by jd...@apache.org on 2017/10/13 18:08:53 UTC

hive git commit: HIVE-17782: Inconsistent cast behavior from string to numeric types with regards to leading/trailing spaces (Jason Dere, reviewed by Ashutosh Chauhan)

Repository: hive
Updated Branches:
  refs/heads/master 0c9b66763 -> ea89de719


HIVE-17782: Inconsistent cast behavior from string to numeric types with regards to leading/trailing spaces (Jason Dere, reviewed by Ashutosh Chauhan)


Project: http://git-wip-us.apache.org/repos/asf/hive/repo
Commit: http://git-wip-us.apache.org/repos/asf/hive/commit/ea89de71
Tree: http://git-wip-us.apache.org/repos/asf/hive/tree/ea89de71
Diff: http://git-wip-us.apache.org/repos/asf/hive/diff/ea89de71

Branch: refs/heads/master
Commit: ea89de719eea48639a3ecf556a74b810b54a8e3f
Parents: 0c9b667
Author: Jason Dere <jd...@hortonworks.com>
Authored: Fri Oct 13 11:07:32 2017 -0700
Committer: Jason Dere <jd...@hortonworks.com>
Committed: Fri Oct 13 11:07:32 2017 -0700

----------------------------------------------------------------------
 .../hive/common/util/HiveStringUtils.java       |  51 +++++
 .../apache/hadoop/hive/ql/udf/UDFToByte.java    |   2 +-
 .../apache/hadoop/hive/ql/udf/UDFToInteger.java |   2 +-
 .../apache/hadoop/hive/ql/udf/UDFToLong.java    |   2 +-
 .../apache/hadoop/hive/ql/udf/UDFToShort.java   |   2 +-
 ql/src/test/queries/clientpositive/cast2.q      |   5 +
 ql/src/test/results/clientpositive/cast2.q.out  |  18 ++
 .../hadoop/hive/serde2/lazy/LazyByte.java       |  24 ++-
 .../hadoop/hive/serde2/lazy/LazyInteger.java    |  31 +++
 .../hadoop/hive/serde2/lazy/LazyLong.java       |  31 +++
 .../hadoop/hive/serde2/lazy/LazyShort.java      |  24 ++-
 .../hive/serde2/lazy/TestLazyPrimitive.java     | 197 +++++++++++++++++++
 12 files changed, 383 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/common/src/java/org/apache/hive/common/util/HiveStringUtils.java
----------------------------------------------------------------------
diff --git a/common/src/java/org/apache/hive/common/util/HiveStringUtils.java b/common/src/java/org/apache/hive/common/util/HiveStringUtils.java
index dc3ee98..bf34600 100644
--- a/common/src/java/org/apache/hive/common/util/HiveStringUtils.java
+++ b/common/src/java/org/apache/hive/common/util/HiveStringUtils.java
@@ -1007,6 +1007,57 @@ public class HiveStringUtils {
     return len;
   }
 
+  /**
+   * Checks if b is an ascii character
+   */
+  public static boolean isAscii(byte b) {
+    return (b & 0x80) == 0;
+  }
+
+  /**
+   * Returns the number of leading whitespace characters in the utf-8 string
+   */
+  public static int findLeadingSpaces(byte[] bytes, int start, int length) {
+    int numSpaces;
+    for (numSpaces = 0; numSpaces < length; ++numSpaces) {
+      int curPos = start + numSpaces;
+      if (isAscii(bytes[curPos]) && Character.isWhitespace(bytes[curPos])) {
+        continue;
+      }
+      break; // non-space character
+    }
+    return (numSpaces - start);
+  }
+
+  /**
+   * Returns the number of trailing whitespace characters in the utf-8 string
+   */
+  public static int findTrailingSpaces(byte[] bytes, int start, int length) {
+    int numSpaces;
+    for (numSpaces = 0; numSpaces < length; ++numSpaces) {
+      int curPos = start + (length - (numSpaces + 1));
+      if (isAscii(bytes[curPos]) && Character.isWhitespace(bytes[curPos])) {
+        continue;
+      } else {
+        break; // non-space character
+      }
+    }
+    return numSpaces;
+  }
+
+  /**
+   * Finds trimmed length of utf-8 string
+   */
+  public static int findTrimmedLength(byte[] bytes, int start, int length, int leadingSpaces) {
+    int trailingSpaces = findTrailingSpaces(bytes, start, length);
+    length = length - leadingSpaces;
+    // If string is entirely whitespace, no need to apply trailingSpaces.
+    if (length > 0) {
+      length = length - trailingSpaces;
+    }
+    return length;
+  }
+
   public static String normalizeIdentifier(String identifier) {
 	  return identifier.trim().toLowerCase();
 	}

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToByte.java
----------------------------------------------------------------------
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToByte.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToByte.java
index 12f530b..a5c49f3 100755
--- a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToByte.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToByte.java
@@ -172,7 +172,7 @@ public class UDFToByte extends UDF {
           return null;
         }
       try {
-        byteWritable.set(LazyByte.parseByte(i.getBytes(), 0, i.getLength(), 10));
+        byteWritable.set(LazyByte.parseByte(i.getBytes(), 0, i.getLength(), 10, true));
         return byteWritable;
       } catch (NumberFormatException e) {
         // MySQL returns 0 if the string is not a well-formed numeric value.

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToInteger.java
----------------------------------------------------------------------
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToInteger.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToInteger.java
index 1de7604..df756d0 100755
--- a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToInteger.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToInteger.java
@@ -175,7 +175,7 @@ public class UDFToInteger extends UDF {
       }
       try {
         intWritable.set(LazyInteger
-            .parseInt(i.getBytes(), 0, i.getLength(), 10));
+            .parseInt(i.getBytes(), 0, i.getLength(), 10, true));
         return intWritable;
       } catch (NumberFormatException e) {
         // MySQL returns 0 if the string is not a well-formed numeric value.

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToLong.java
----------------------------------------------------------------------
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToLong.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToLong.java
index 11f6a6c..4821d67 100755
--- a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToLong.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToLong.java
@@ -184,7 +184,7 @@ public class UDFToLong extends UDF {
       }
       try {
         longWritable
-            .set(LazyLong.parseLong(i.getBytes(), 0, i.getLength(), 10));
+            .set(LazyLong.parseLong(i.getBytes(), 0, i.getLength(), 10, true));
         return longWritable;
       } catch (NumberFormatException e) {
         // MySQL returns 0 if the string is not a well-formed numeric value.

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToShort.java
----------------------------------------------------------------------
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToShort.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToShort.java
index 5372549..df0fc96 100644
--- a/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToShort.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/UDFToShort.java
@@ -174,7 +174,7 @@ public class UDFToShort extends UDF {
       }
       try {
         shortWritable.set(LazyShort.parseShort(i.getBytes(), 0, i.getLength(),
-            10));
+            10, true));
         return shortWritable;
       } catch (NumberFormatException e) {
         // MySQL returns 0 if the string is not a well-formed numeric value.

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/ql/src/test/queries/clientpositive/cast2.q
----------------------------------------------------------------------
diff --git a/ql/src/test/queries/clientpositive/cast2.q b/ql/src/test/queries/clientpositive/cast2.q
new file mode 100644
index 0000000..12228be
--- /dev/null
+++ b/ql/src/test/queries/clientpositive/cast2.q
@@ -0,0 +1,5 @@
+
+select cast('1' as tinyint), cast('1' as smallint), cast('1' as int), cast('1' as bigint), cast('1' as float), cast('1' as double), cast('1' as decimal(10,2));
+
+-- Check that leading/trailing space is handled consistently for numeric types
+select cast(' 1 ' as tinyint), cast(' 1 ' as smallint), cast(' 1 ' as int), cast(' 1 ' as bigint), cast(' 1 ' as float), cast(' 1 ' as double), cast(' 1 ' as decimal(10,2));

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/ql/src/test/results/clientpositive/cast2.q.out
----------------------------------------------------------------------
diff --git a/ql/src/test/results/clientpositive/cast2.q.out b/ql/src/test/results/clientpositive/cast2.q.out
new file mode 100644
index 0000000..9fa3a9d
--- /dev/null
+++ b/ql/src/test/results/clientpositive/cast2.q.out
@@ -0,0 +1,18 @@
+PREHOOK: query: select cast('1' as tinyint), cast('1' as smallint), cast('1' as int), cast('1' as bigint), cast('1' as float), cast('1' as double), cast('1' as decimal(10,2))
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+#### A masked pattern was here ####
+POSTHOOK: query: select cast('1' as tinyint), cast('1' as smallint), cast('1' as int), cast('1' as bigint), cast('1' as float), cast('1' as double), cast('1' as decimal(10,2))
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+#### A masked pattern was here ####
+1	1	1	1	1.0	1.0	1
+PREHOOK: query: select cast(' 1 ' as tinyint), cast(' 1 ' as smallint), cast(' 1 ' as int), cast(' 1 ' as bigint), cast(' 1 ' as float), cast(' 1 ' as double), cast(' 1 ' as decimal(10,2))
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+#### A masked pattern was here ####
+POSTHOOK: query: select cast(' 1 ' as tinyint), cast(' 1 ' as smallint), cast(' 1 ' as int), cast(' 1 ' as bigint), cast(' 1 ' as float), cast(' 1 ' as double), cast(' 1 ' as decimal(10,2))
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+#### A masked pattern was here ####
+1	1	1	1	1.0	1.0	1

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyByte.java
----------------------------------------------------------------------
diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyByte.java b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyByte.java
index 1f9cead..405c905 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyByte.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyByte.java
@@ -95,7 +95,29 @@ public class LazyByte extends
    *           if the argument could not be parsed as a byte quantity.
    */
   public static byte parseByte(byte[] bytes, int start, int length, int radix) {
-    int intValue = LazyInteger.parseInt(bytes, start, length, radix);
+    return parseByte(bytes, start, length, radix, false);
+  }
+
+  /**
+   * Parses the string argument as if it was a byte value and returns the
+   * result. Throws NumberFormatException if the string does not represent a
+   * single byte quantity. The second argument specifies the radix to use when
+   * parsing the value.
+   *
+   * @param bytes
+   * @param start
+   * @param length
+   *          a UTF-8 encoded string representation of a single byte quantity.
+   * @param radix
+   *          the radix to use when parsing.
+   * @param trim
+   *          whether to trim leading/trailing whitespace
+   * @return byte the value represented by the argument
+   * @throws NumberFormatException
+   *           if the argument could not be parsed as a byte quantity.
+   */
+  public static byte parseByte(byte[] bytes, int start, int length, int radix, boolean trim) {
+    int intValue = LazyInteger.parseInt(bytes, start, length, radix, trim);
     byte result = (byte) intValue;
     if (result == intValue) {
       return result;

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyInteger.java
----------------------------------------------------------------------
diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyInteger.java b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyInteger.java
index 22742aa..9687c6d 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyInteger.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyInteger.java
@@ -22,6 +22,7 @@ import java.io.OutputStream;
 
 import org.apache.hadoop.hive.serde2.lazy.objectinspector.primitive.LazyIntObjectInspector;
 import org.apache.hadoop.io.IntWritable;
+import org.apache.hive.common.util.HiveStringUtils;
 
 /**
  * LazyObject for storing a value of Integer.
@@ -98,12 +99,42 @@ public class LazyInteger extends
    *              if the argument could not be parsed as an int quantity.
    */
   public static int parseInt(byte[] bytes, int start, int length, int radix) {
+    return parseInt(bytes, start, length, radix, false);
+  }
+
+  /**
+   * Parses the string argument as if it was an int value and returns the
+   * result. Throws NumberFormatException if the string does not represent an
+   * int quantity. The second argument specifies the radix to use when parsing
+   * the value.
+   *
+   * @param bytes
+   * @param start
+   * @param length
+   *          a UTF-8 encoded string representation of an int quantity.
+   * @param radix
+   *          the base to use for conversion.
+   * @param trim
+   *          whether to trim leading/trailing whitespace
+   * @return the value represented by the argument
+   * @exception NumberFormatException
+   *              if the argument could not be parsed as an int quantity.
+   */
+  public static int parseInt(byte[] bytes, int start, int length, int radix, boolean trim) {
     if (bytes == null) {
       throw new NumberFormatException("String is null");
     }
     if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
       throw new NumberFormatException("Invalid radix: " + radix);
     }
+    if (trim) {
+      // Handle leading/trailing whitespace
+      int leadingSpaces = HiveStringUtils.findLeadingSpaces(bytes, start, length);
+      int trailingSpaces = HiveStringUtils.findTrailingSpaces(bytes, start, length);
+      start = start + leadingSpaces;
+      // min() needed in the case that entire string is whitespace
+      length = length - Math.min(length, leadingSpaces + trailingSpaces);
+    }
     if (length == 0) {
       throw new NumberFormatException("Empty string!");
     }

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyLong.java
----------------------------------------------------------------------
diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyLong.java b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyLong.java
index c0d52b9..91c7e5e 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyLong.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyLong.java
@@ -22,6 +22,7 @@ import java.io.OutputStream;
 
 import org.apache.hadoop.hive.serde2.lazy.objectinspector.primitive.LazyLongObjectInspector;
 import org.apache.hadoop.io.LongWritable;
+import org.apache.hive.common.util.HiveStringUtils;
 
 /**
  * LazyObject for storing a value of Long.
@@ -98,12 +99,42 @@ public class LazyLong extends
    *              if the argument could not be parsed as an long quantity.
    */
   public static long parseLong(byte[] bytes, int start, int length, int radix) {
+    return parseLong(bytes, start, length, radix, false);
+  }
+
+  /**
+   * Parses the string argument as if it was an long value and returns the
+   * result. Throws NumberFormatException if the string does not represent an
+   * long quantity. The second argument specifies the radix to use when parsing
+   * the value.
+   *
+   * @param bytes
+   * @param start
+   * @param length
+   *          a UTF-8 encoded string representation of a long quantity.
+   * @param radix
+   *          the base to use for conversion.
+   * @param trim
+   *          whether to trim leading/trailing whitespace
+   * @return the value represented by the argument
+   * @exception NumberFormatException
+   *              if the argument could not be parsed as an long quantity.
+   */
+  public static long parseLong(byte[] bytes, int start, int length, int radix, boolean trim) {
     if (bytes == null) {
       throw new NumberFormatException("String is null");
     }
     if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
       throw new NumberFormatException("Invalid radix: " + radix);
     }
+    if (trim) {
+      // Handle leading/trailing whitespace
+      int leadingSpaces = HiveStringUtils.findLeadingSpaces(bytes, start, length);
+      int trailingSpaces = HiveStringUtils.findTrailingSpaces(bytes, start, length);
+      start = start + leadingSpaces;
+      // min() needed in the case that entire string is whitespace
+      length = length - Math.min(length, leadingSpaces + trailingSpaces);
+    }
     if (length == 0) {
       throw new NumberFormatException("Empty string!");
     }

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyShort.java
----------------------------------------------------------------------
diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyShort.java b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyShort.java
index b8b9488..39c2b60 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyShort.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/lazy/LazyShort.java
@@ -95,7 +95,29 @@ public class LazyShort extends
    *              if the argument could not be parsed as a short quantity.
    */
   public static short parseShort(byte[] bytes, int start, int length, int radix) {
-    int intValue = LazyInteger.parseInt(bytes, start, length, radix);
+    return parseShort(bytes, start, length, radix, false);
+  }
+
+  /**
+   * Parses the string argument as if it was a short value and returns the
+   * result. Throws NumberFormatException if the string does not represent a
+   * single short quantity. The second argument specifies the radix to use when
+   * parsing the value.
+   *
+   * @param bytes
+   * @param start
+   * @param length
+   *          a UTF-8 encoded string representation of a short quantity.
+   * @param radix
+   *          the radix to use when parsing.
+   * @param trim
+   *          whether to trim leading/trailing whitespace
+   * @return short the value represented by the argument
+   * @exception NumberFormatException
+   *              if the argument could not be parsed as a short quantity.
+   */
+  public static short parseShort(byte[] bytes, int start, int length, int radix, boolean trim) {
+    int intValue = LazyInteger.parseInt(bytes, start, length, radix, trim);
     short result = (short) intValue;
     if (result == intValue) {
       return result;

http://git-wip-us.apache.org/repos/asf/hive/blob/ea89de71/serde/src/test/org/apache/hadoop/hive/serde2/lazy/TestLazyPrimitive.java
----------------------------------------------------------------------
diff --git a/serde/src/test/org/apache/hadoop/hive/serde2/lazy/TestLazyPrimitive.java b/serde/src/test/org/apache/hadoop/hive/serde2/lazy/TestLazyPrimitive.java
index 3d7f11e..663d1f2 100644
--- a/serde/src/test/org/apache/hadoop/hive/serde2/lazy/TestLazyPrimitive.java
+++ b/serde/src/test/org/apache/hadoop/hive/serde2/lazy/TestLazyPrimitive.java
@@ -468,4 +468,201 @@ public class TestLazyPrimitive extends TestCase {
     }
   }
 
+  private void testIntCaseWithPass(String strVal, int intVal, boolean trim) {
+    Text text = new Text(strVal);
+    assertEquals(
+        intVal,
+        LazyInteger.parseInt(text.getBytes(), 0, text.getLength(), 10, trim));
+  }
+
+  private void testIntCaseWithFail(String strVal, boolean trim) {
+    Text text = new Text(strVal);
+    try {
+      LazyInteger.parseInt(text.getBytes(), 0, text.getLength(), 10, trim);
+      fail("Expected to fail while parsing '" + strVal + "'");
+    } catch (NumberFormatException err) {
+      // Error was expected
+    }
+  }
+
+  private void testLongCaseWithPass(String strVal, long longVal, boolean trim) {
+    Text text = new Text(strVal);
+    assertEquals(
+        longVal,
+        LazyLong.parseLong(text.getBytes(), 0, text.getLength(), 10, trim));
+  }
+
+  private void testLongCaseWithFail(String strVal, boolean trim) {
+    Text text = new Text(strVal);
+    try {
+      LazyLong.parseLong(text.getBytes(), 0, text.getLength(), 10, trim);
+      fail("Expected to fail while parsing '" + strVal + "'");
+    } catch (NumberFormatException err) {
+      // Error was expected
+    }
+  }
+
+  public void testLazyIntWithSpaces() throws Throwable {
+    Object[][] casesWithoutSpaces = {
+        {"0", 0},
+        {"-128", -128},
+        {"128", 128},
+        {"+128", 128},
+        {"-2147483648", -2147483648},
+        {"2147483647", 2147483647},
+        {"+2147483647", 2147483647},
+    };
+
+    Object[][] casesWithSpaces = {
+        {" 0", 0},
+        {"0 ", 0},
+        {" 0 ", 0},
+        {" -128", -128},
+        {"-128 ", -128},
+        {"   -128   ", -128},
+        {"  128", 128},
+        {"128  ", 128},
+        {"  128  ", 128},
+        {" +128", 128},
+        {"+128 ", 128},
+        {"   +128    ", 128},
+        {"   +128    ", 128},
+        {"   -2147483648", -2147483648},
+        {"-2147483648    ", -2147483648},
+        {"   -2147483648   ", -2147483648},
+        {"  2147483647", 2147483647},
+        {"2147483647  ", 2147483647},
+        {"  2147483647  ", 2147483647},
+        {"   +2147483647", 2147483647},
+        {"+2147483647   ", 2147483647},
+        {"   +2147483647   ", 2147483647},
+    };
+
+    String[] casesWithErrors = {
+        "",
+        "  ",
+        "one",
+        " one ",
+        "123:",
+        "123a",
+        " 123a ",
+        "a123",
+        " a123 ",
+        // Exceeds MAX_VALUE
+        "2147483648",
+        "-2147483649",
+    };
+
+    //
+    // trim=false
+    //
+    boolean trim = false;
+    for (Object[] testCase : casesWithoutSpaces) {
+      testIntCaseWithPass((String) testCase[0], ((Number) testCase[1]).intValue(), trim);
+    }
+    for (Object[] testCase : casesWithSpaces) {
+      // With trim=false, parsing cannot handle spaces
+      testIntCaseWithFail((String) testCase[0], trim);
+    }
+    for (String testCase : casesWithErrors) {
+      testIntCaseWithFail(testCase, trim);
+    }
+
+    //
+    // trim=true
+    //
+    trim = true;
+    for (Object[] testCase : casesWithoutSpaces) {
+      testIntCaseWithPass((String) testCase[0], ((Number) testCase[1]).intValue(), trim);
+    }
+    for (Object[] testCase : casesWithSpaces) {
+      // With trim=true, parsing can handle spaces
+      testIntCaseWithPass((String) testCase[0], ((Number) testCase[1]).intValue(), trim);
+    }
+    for (String testCase : casesWithErrors) {
+      testIntCaseWithFail(testCase, trim);
+    }
+  }
+
+  public void testLazyLongWithSpaces() throws Throwable {
+    Object[][] casesWithoutSpaces = {
+        {"0", 0},
+        {"-128", -128},
+        {"128", 128},
+        {"+128", 128},
+        {"-9223372036854775808", -9223372036854775808L},
+        {"9223372036854775807", 9223372036854775807L},
+        {"+9223372036854775807", 9223372036854775807L},
+    };
+
+    Object[][] casesWithSpaces = {
+        {" 0", 0},
+        {"0 ", 0},
+        {" 0 ", 0},
+        {" -128", -128},
+        {"-128 ", -128},
+        {"   -128   ", -128},
+        {"  128", 128},
+        {"128  ", 128},
+        {"  128  ", 128},
+        {" +128", 128},
+        {"+128 ", 128},
+        {"   +128    ", 128},
+        {"   +128    ", 128},
+        {"   -9223372036854775808", -9223372036854775808L},
+        {"-9223372036854775808    ", -9223372036854775808L},
+        {"   -9223372036854775808   ", -9223372036854775808L},
+        {"  9223372036854775807", 9223372036854775807L},
+        {"9223372036854775807  ", 9223372036854775807L},
+        {"  9223372036854775807  ", 9223372036854775807L},
+        {"   +9223372036854775807", 9223372036854775807L},
+        {"+9223372036854775807   ", 9223372036854775807L},
+        {"   +9223372036854775807   ", 9223372036854775807L},
+    };
+
+    String[] casesWithErrors = {
+        "",
+        "  ",
+        "one",
+        " one ",
+        "123:",
+        "123a",
+        " 123a ",
+        "a123",
+        " a123 ",
+        // Exceeds max value
+        "9223372036854775808",
+        "9223372036854775809",
+    };
+
+    //
+    // trim=false
+    //
+    boolean trim = false;
+    for (Object[] testCase : casesWithoutSpaces) {
+      testLongCaseWithPass((String) testCase[0], ((Number) testCase[1]).longValue(), trim);
+    }
+    for (Object[] testCase : casesWithSpaces) {
+      // With trim=false, parsing cannot handle spaces
+      testLongCaseWithFail((String) testCase[0], trim);
+    }
+    for (String testCase : casesWithErrors) {
+      testLongCaseWithFail(testCase, trim);
+    }
+
+    //
+    // trim=true
+    //
+    trim = true;
+    for (Object[] testCase : casesWithoutSpaces) {
+      testLongCaseWithPass((String) testCase[0], ((Number) testCase[1]).longValue(), trim);
+    }
+    for (Object[] testCase : casesWithSpaces) {
+      // With trim=true, parsing can handle spaces
+      testLongCaseWithPass((String) testCase[0], ((Number) testCase[1]).longValue(), trim);
+    }
+    for (String testCase : casesWithErrors) {
+      testLongCaseWithFail(testCase, trim);
+    }
+  }
 }