This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-11626-78f334d9e1682770732f100f23313c91c23fc465
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git

commit dad78c3243db92fefd57b8b4f3cf65901f874263
Author: Matt Butrovich <[email protected]>
AuthorDate: Sat Oct 17 06:58:30 2026 +0000

    fix(arrow-cast): bump atoi to 3.1.1 to fix Int16 parse overflow (#11626)
    
    # Which issue does this PR close?
    
    - Closes #11591
    
    # Rationale for this change
    
    atoi 3.1.1 fixes pacman82/atoi-rs#54, which caused the bug in #11591.
    
    This is a regression in 60.0.0. #10219 bumped atoi from 2.0.0 to 3.1.0,
    and 59.3.1 still uses atoi 2.0.0. The integer parser in
    `arrow-cast/src/parse.rs` is otherwise unchanged between 59.3.1 and
    `main`, and the new tests pass on 59.3.1. This makes the fix a candidate
    for a 60.x backport.
    
    # What changes are included in this PR?
    
    The minimum atoi version in `arrow-cast` is now 3.1.1.
    
    # Are these changes tested?
    
    Yes. `test_parse_integer_bounds` in `arrow-cast/src/parse.rs` parses
    each integer type's bounds, one past each bound, and the all-nines
    values on either side of each bound, with and without leading
    whitespace. `test_cast_string_to_integral_overflow` in
    `arrow-cast/src/cast/mod.rs` now includes `"-40000"` cast to `Int16`.
    Both tests fail with atoi 3.1.0.
    
    # Are there any significant user-facing changes?
    
    No.
    
    ## AI Disclosure
    
    I wrote this PR with help from Claude but understand and support the
    core approach, implementation, and test coverage.
---
 Cargo.lock                 |  4 ++--
 arrow-cast/Cargo.toml      |  2 +-
 arrow-cast/src/cast/mod.rs | 10 ++++++++--
 arrow-cast/src/parse.rs    | 42 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 90152321ad..045c6a552f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -609,9 +609,9 @@ checksum = 
"f2d85fd3d291fabcc40c7232c92c280ec1754fd7b5d7ea769f143222143e179a"
 
 [[package]]
 name = "atoi"
-version = "3.1.0"
+version = "3.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "e7a8bbe9949e43a1edaa043038c68703b04774156afdfb62ba2cef5bf93d67be"
+checksum = "d12c37257553c19e9aee6cba77814fdc9e07844abfc8b3a2c377cd920ee5a993"
 dependencies = [
  "num-traits",
 ]
diff --git a/arrow-cast/Cargo.toml b/arrow-cast/Cargo.toml
index 2d1364e525..25c1ae358d 100644
--- a/arrow-cast/Cargo.toml
+++ b/arrow-cast/Cargo.toml
@@ -48,7 +48,7 @@ chrono = { workspace = true }
 half = { version = "2.1", default-features = false }
 num-traits = { version = "0.2.19", default-features = false, features = 
["std"] }
 lexical-core = { version = "1.0", default-features = false, features = 
["write-integers", "write-floats", "parse-integers", "parse-floats"] }
-atoi = "3.1.0"
+atoi = "3.1.1"
 comfy-table = { version = "8", optional = true, default-features = false }
 base64 = "0.23"
 ryu = "1.0.16"
diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs
index 94de6548f4..bfebdcdfe7 100644
--- a/arrow-cast/src/cast/mod.rs
+++ b/arrow-cast/src/cast/mod.rs
@@ -4728,6 +4728,7 @@ mod tests {
             Some("123"),
             Some("-123"),
             Some("86374"),
+            Some("-40000"),
             None,
         ])) as ArrayRef;
 
@@ -4736,8 +4737,13 @@ mod tests {
             format_options: FormatOptions::default(),
         };
         let res = cast_with_options(&str, &DataType::Int16, 
&options).expect("should cast to i16");
-        let expected =
-            Arc::new(Int16Array::from(vec![Some(123), Some(-123), None, 
None])) as ArrayRef;
+        let expected = Arc::new(Int16Array::from(vec![
+            Some(123),
+            Some(-123),
+            None,
+            None,
+            None,
+        ])) as ArrayRef;
         assert_eq!(&res, &expected);
     }
 
diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs
index 384eba2007..a571368d63 100644
--- a/arrow-cast/src/parse.rs
+++ b/arrow-cast/src/parse.rs
@@ -3631,6 +3631,48 @@ mod tests {
         assert_eq!(Date32Type::parse(""), None);
     }
 
+    #[test]
+    fn test_parse_integer_bounds() {
+        // The all-nines cases have as many digits as a bound but lie past it. 
atoi 3.1.0
+        // parsed -99999 as i16 without an overflow check (#11591).
+        macro_rules! check {
+            ($t:ty) => {{
+                type Native = <$t as ArrowPrimitiveType>::Native;
+                let (min, max) = (i128::from(Native::MIN), 
i128::from(Native::MAX));
+                let nines = 10_i128.pow(max.ilog10() + 1) - 1;
+                for v in [
+                    min,
+                    max,
+                    min - 1,
+                    max + 1,
+                    -nines,
+                    nines,
+                    -nines - 1,
+                    nines + 1,
+                ] {
+                    let expected = Native::try_from(v).ok();
+                    // The leading space takes the path that trims before 
parsing
+                    for s in [v.to_string(), format!(" {v}")] {
+                        assert_eq!(
+                            <$t>::parse(&s),
+                            expected,
+                            "{s:?} as {}",
+                            <$t as ArrowPrimitiveType>::DATA_TYPE
+                        );
+                    }
+                }
+            }};
+        }
+        check!(Int8Type);
+        check!(Int16Type);
+        check!(Int32Type);
+        check!(Int64Type);
+        check!(UInt8Type);
+        check!(UInt16Type);
+        check!(UInt32Type);
+        check!(UInt64Type);
+    }
+
     #[test]
     fn test_parse_interval_month_day_nano_config() {
         let interval = parse_interval_month_day_nano_config(

Reply via email to