From 76fca7f2dd10a5072e395f652f3923dbd22fa9d5 Mon Sep 17 00:00:00 2001 From: Richard Baah Date: Sat, 18 Jul 2026 12:52:10 -0400 Subject: [PATCH 1/4] support white space prefixed parse for ints and floats --- arrow-cast/src/parse.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index f4033f08b397..c6973134a2bc 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -459,6 +459,14 @@ impl Parser for Float32Type { impl Parser for Float64Type { fn parse(string: &str) -> Option { + let mut string = string; + if string + .as_bytes() + .first() + .is_some_and(|first_byte| !first_byte.is_ascii_digit()) + { + string = string.trim_start() + } lexical_core::parse(string.as_bytes()).ok() } } @@ -467,9 +475,17 @@ macro_rules! parser_primitive { ($t:ty) => { impl Parser for $t { fn parse(string: &str) -> Option { + let mut string = string; if !string.as_bytes().last().is_some_and(|x| x.is_ascii_digit()) { return None; } + if string + .as_bytes() + .first() + .is_some_and(|first| !first.is_ascii_digit()) + { + string = string.trim_start(); + }; match atoi::FromRadix10SignedChecked::from_radix_10_signed_checked( string.as_bytes(), ) { @@ -2872,4 +2888,19 @@ mod tests { assert_eq!(interval.days, 0); assert_eq!(interval.nanoseconds, NANOS_PER_SECOND); } + #[test] + fn test_parse_prefix_white_space() { + assert_eq!(Float64Type::parse(" 1.5"), Some(1.5)); + assert_eq!(Float64Type::parse("\t\n 20.54"), Some(20.54)); + assert_eq!(Float64Type::parse("\n2.5"), Some(2.5)); + assert_eq!(Float64Type::parse("\n\t\n\t\n40.5123"), Some(40.5123)); + assert_eq!(Float64Type::parse(" 1.5"), Some(1.5)); + assert_eq!(Int32Type::parse(" 3"), Some(3)); + assert_eq!(Int32Type::parse(" 30"), Some(30)); + assert_eq!(Int32Type::parse("\n \n 100"), Some(100)); + assert_eq!(Int32Type::parse(" \n25"), Some(25)); + assert_eq!(Int32Type::parse("\t800"), Some(800)); + assert_eq!(Int32Type::parse("\t \n \t 851"), Some(851)); + assert_eq!(Int32Type::parse("\t\n\t\n\n\n\t1"), Some(1)); + } } From a33da79084efa75a8ea6313aa1d5f4899ed6fdc5 Mon Sep 17 00:00:00 2001 From: rich-T-kid Date: Sat, 18 Jul 2026 21:10:48 -0400 Subject: [PATCH 2/4] use trim_ascii_start instead of trim_start --- arrow-cast/src/parse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index c6973134a2bc..fba3d77f1444 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -465,7 +465,7 @@ impl Parser for Float64Type { .first() .is_some_and(|first_byte| !first_byte.is_ascii_digit()) { - string = string.trim_start() + string = string.trim_ascii_start() } lexical_core::parse(string.as_bytes()).ok() } @@ -484,7 +484,7 @@ macro_rules! parser_primitive { .first() .is_some_and(|first| !first.is_ascii_digit()) { - string = string.trim_start(); + string = string.trim_ascii_start(); }; match atoi::FromRadix10SignedChecked::from_radix_10_signed_checked( string.as_bytes(), From c7979dbbd07fd1c64f8ae432a15da9d470dcc811 Mon Sep 17 00:00:00 2001 From: rich-T-kid Date: Sat, 18 Jul 2026 22:37:56 -0400 Subject: [PATCH 3/4] add support for f32/16 --- arrow-cast/src/parse.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index fba3d77f1444..2c09901aedd2 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -445,6 +445,7 @@ pub trait Parser: ArrowPrimitiveType { impl Parser for Float16Type { fn parse(string: &str) -> Option { + let string = truncate_white_space(string); lexical_core::parse(string.as_bytes()) .ok() .map(f16::from_f32) @@ -453,23 +454,29 @@ impl Parser for Float16Type { impl Parser for Float32Type { fn parse(string: &str) -> Option { + let string = truncate_white_space(string); lexical_core::parse(string.as_bytes()).ok() } } impl Parser for Float64Type { fn parse(string: &str) -> Option { - let mut string = string; - if string - .as_bytes() - .first() - .is_some_and(|first_byte| !first_byte.is_ascii_digit()) - { - string = string.trim_ascii_start() - } + let string = truncate_white_space(string); lexical_core::parse(string.as_bytes()).ok() } } +#[inline] +fn truncate_white_space(string: &str) -> &str { + if string + .as_bytes() + .first() + .is_some_and(|first_byte| !first_byte.is_ascii_digit()) + { + string.trim_ascii_start() + } else { + string + } +} macro_rules! parser_primitive { ($t:ty) => { From 5643099353e52895468572dc9f327610a9d6b6b5 Mon Sep 17 00:00:00 2001 From: rich-T-kid Date: Sun, 19 Jul 2026 09:34:10 -0400 Subject: [PATCH 4/4] add negative test --- arrow-cast/src/parse.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index 2c09901aedd2..57fbdd40acf8 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -481,8 +481,7 @@ fn truncate_white_space(string: &str) -> &str { macro_rules! parser_primitive { ($t:ty) => { impl Parser for $t { - fn parse(string: &str) -> Option { - let mut string = string; + fn parse(mut string: &str) -> Option { if !string.as_bytes().last().is_some_and(|x| x.is_ascii_digit()) { return None; } @@ -2900,8 +2899,11 @@ mod tests { assert_eq!(Float64Type::parse(" 1.5"), Some(1.5)); assert_eq!(Float64Type::parse("\t\n 20.54"), Some(20.54)); assert_eq!(Float64Type::parse("\n2.5"), Some(2.5)); + assert_eq!(Float64Type::parse("\n-942.5423"), Some(-942.5423)); assert_eq!(Float64Type::parse("\n\t\n\t\n40.5123"), Some(40.5123)); assert_eq!(Float64Type::parse(" 1.5"), Some(1.5)); + assert_eq!(Float64Type::parse("\n\t\n\t\n-40.5123"), Some(-40.5123)); + assert_eq!(Float64Type::parse(" -1.5"), Some(-1.5)); assert_eq!(Int32Type::parse(" 3"), Some(3)); assert_eq!(Int32Type::parse(" 30"), Some(30)); assert_eq!(Int32Type::parse("\n \n 100"), Some(100)); @@ -2909,5 +2911,7 @@ mod tests { assert_eq!(Int32Type::parse("\t800"), Some(800)); assert_eq!(Int32Type::parse("\t \n \t 851"), Some(851)); assert_eq!(Int32Type::parse("\t\n\t\n\n\n\t1"), Some(1)); + assert_eq!(Int32Type::parse(" \n-25"), Some(-25)); + assert_eq!(Int32Type::parse("\t-800"), Some(-800)); } }