From 4041b3e934218e907e6c036cbf6fe9ef395fccd2 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 10 Jul 2026 21:24:40 -0400 Subject: [PATCH 1/2] allow users to skip utf8 validation --- arrow-row/src/lib.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arrow-row/src/lib.rs b/arrow-row/src/lib.rs index 0773f08f2686..f2b1b4ed2228 100644 --- a/arrow-row/src/lib.rs +++ b/arrow-row/src/lib.rs @@ -1282,6 +1282,16 @@ impl RowParser { }, } } + /// Like [`RowConverter::parser`] but lets the caller control UTF-8 validation on decode. + /// Pass `false` to skip validation when the bytes are already known to be valid UTF-8. + pub unsafe fn with_validate_utf8_flag(fields: Arc<[SortField]>, validate_utf8: bool) -> Self { + Self { + config: RowConfig { + fields, + validate_utf8, + }, + } + } /// Creates a [`Row`] from the provided `bytes`. /// @@ -6676,6 +6686,25 @@ mod tests { assert_eq!(rows_iter.next_back(), None); } + /// Round-trip through `with_validate_utf8_flag(false)` confirms skipping validation + /// preserves values. + #[test] + fn test_row_parser_skip_utf8_validation_roundtrip() { + let converter = RowConverter::new(vec![SortField::new(DataType::Utf8)]).unwrap(); + let array = StringArray::from(vec!["arrow", "rust"]); + let rows = converter.convert_columns(&[Arc::new(array) as _]).unwrap(); + let binary = rows.try_into_binary().expect("fits in i32 offsets"); + + let parser = + unsafe { RowParser::with_validate_utf8_flag(Arc::clone(&converter.fields), false) }; + + let decoded = converter + .convert_rows(binary.iter().map(|b| parser.parse(b.unwrap()))) + .unwrap(); + let got: Vec<_> = decoded[0].as_string::().iter().flatten().collect(); + assert_eq!(got, vec!["arrow", "rust"]); + } + #[test] fn row_iter_next_back() { let row_converter = RowConverter::new(vec![SortField::new(DataType::UInt8)]).unwrap(); From 04ff5d7ae69fda53ce14f7dd3e70be85cf3e8532 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 10 Jul 2026 21:33:20 -0400 Subject: [PATCH 2/2] add smaller API for change --- arrow-row/src/lib.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/arrow-row/src/lib.rs b/arrow-row/src/lib.rs index f2b1b4ed2228..f1cb42d3d8e6 100644 --- a/arrow-row/src/lib.rs +++ b/arrow-row/src/lib.rs @@ -1256,6 +1256,14 @@ impl RowConverter { RowParser::new(Arc::clone(&self.fields)) } + /// Like [`Self::parser`] but skips UTF-8 validation on decode. + /// + /// # Safety + /// The caller must ensure all row bytes contain valid UTF-8 for string columns. + pub unsafe fn parser_skip_utf8_validation(&self) -> RowParser { + unsafe { RowParser::with_skip_utf8_validate(Arc::clone(&self.fields)) } + } + /// Returns the size of this instance in bytes /// /// Includes the size of `Self`. @@ -1282,13 +1290,15 @@ impl RowParser { }, } } - /// Like [`RowConverter::parser`] but lets the caller control UTF-8 validation on decode. - /// Pass `false` to skip validation when the bytes are already known to be valid UTF-8. - pub unsafe fn with_validate_utf8_flag(fields: Arc<[SortField]>, validate_utf8: bool) -> Self { + /// Like [`RowConverter::parser`] but skips UTF-8 validation on decode. + /// + /// # Safety + /// The caller must ensure all row bytes contain valid UTF-8 for string columns. + unsafe fn with_skip_utf8_validate(fields: Arc<[SortField]>) -> Self { Self { config: RowConfig { fields, - validate_utf8, + validate_utf8: false, }, } } @@ -6686,8 +6696,7 @@ mod tests { assert_eq!(rows_iter.next_back(), None); } - /// Round-trip through `with_validate_utf8_flag(false)` confirms skipping validation - /// preserves values. + /// Round-trip through `with_skip_utf8_validate` confirms skipping validation preserves values. #[test] fn test_row_parser_skip_utf8_validation_roundtrip() { let converter = RowConverter::new(vec![SortField::new(DataType::Utf8)]).unwrap(); @@ -6695,8 +6704,8 @@ mod tests { let rows = converter.convert_columns(&[Arc::new(array) as _]).unwrap(); let binary = rows.try_into_binary().expect("fits in i32 offsets"); - let parser = - unsafe { RowParser::with_validate_utf8_flag(Arc::clone(&converter.fields), false) }; + // SAFETY: bytes come from this RowConverter and are known-valid UTF-8. + let parser = unsafe { RowParser::with_skip_utf8_validate(Arc::clone(&converter.fields)) }; let decoded = converter .convert_rows(binary.iter().map(|b| parser.parse(b.unwrap())))