This is an automated email from the ASF dual-hosted git repository.
Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 92b5042818 fix: error on invalid type representation when parsing
REE/Map types (#11160)
92b5042818 is described below
commit 92b50428182929402f669aead6015a2c89f3be4a
Author: RIchard Baah <[email protected]>
AuthorDate: Fri Sep 25 22:34:49 2026 -0400
fix: error on invalid type representation when parsing REE/Map types
(#11160)
# Which issue does this PR close?
<!--
We generally require a GitHub issue to be filed for all bug fixes and
enhancements and this helps us generate change logs for our releases.
You can link an issue to this PR using the GitHub syntax.
-->
- Closes #11089.
# Rationale for this change
run end cannot be null.
map keys cannot be null.
we should enforce this at parsing time
<!--
Why are you proposing this change? If this is already explained clearly
in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand
your changes and offer better suggestions for fixes.
-->
# What changes are included in this PR?
return errors if the datatype we are parsing is invalid
<!--
There is no need to duplicate the description in the issue here but it
is sometimes worth providing a summary of the individual changes in this
PR.
-->
# Are these changes tested?
yes, existing test and I added a few extra ones
<!--
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code
If tests are not included in your PR, please explain why (for example,
are they covered by existing tests)?
If this PR claims a performance improvement, please include evidence
such as benchmark results.
-->
# Are there any user-facing changes?
no
<!--
If there are user-facing changes then we may require documentation to be
updated before approving the PR.
If there are any breaking changes to public APIs, please call them out.
-->
---
arrow-schema/src/datatype.rs | 9 ++++
arrow-schema/src/datatype_parse.rs | 93 +++++++++++++++++++++++++++-----------
2 files changed, 76 insertions(+), 26 deletions(-)
diff --git a/arrow-schema/src/datatype.rs b/arrow-schema/src/datatype.rs
index dc923fe844..a947d5ad15 100644
--- a/arrow-schema/src/datatype.rs
+++ b/arrow-schema/src/datatype.rs
@@ -415,6 +415,11 @@ pub enum DataType {
/// has two children: key type and the second the value type. The names of
the
/// child fields may be respectively "entries", "key", and "value", but
this is
/// not enforced.
+ ///
+ /// # Requirements
+ /// - The entries [`Field`] (first argument) must be non-nullable.
+ /// - The entries field must be a [`DataType::Struct`] with exactly 2
children.
+ /// - The first child (key) must be non-nullable.
Map(FieldRef, bool),
/// A run-end encoding (REE) is a variation of run-length encoding (RLE).
These
/// encodings are well-suited for representing data containing sequences
of the
@@ -427,6 +432,10 @@ pub enum DataType {
///
/// These child arrays are prescribed the standard names of "run_ends" and
"values"
/// respectively.
+ ///
+ /// # Requirements
+ /// - The run_ends [`Field`] (first argument) must be non-nullable and of
type
+ /// [`DataType::Int16`], [`DataType::Int32`], or [`DataType::Int64`].
RunEndEncoded(FieldRef, FieldRef),
}
diff --git a/arrow-schema/src/datatype_parse.rs
b/arrow-schema/src/datatype_parse.rs
index 67e142e6bc..46e4e59075 100644
--- a/arrow-schema/src/datatype_parse.rs
+++ b/arrow-schema/src/datatype_parse.rs
@@ -580,6 +580,31 @@ impl<'a> Parser<'a> {
fn parse_map(&mut self) -> ArrowResult<DataType> {
self.expect_token(Token::LParen)?;
let field = self.parse_field()?;
+ if field.is_nullable() {
+ return Err(make_error(self.val, "Map entries field cannot be
nullable"));
+ }
+ if let DataType::Struct(fields) = field.data_type() {
+ if fields.len() != 2 {
+ return Err(make_error(
+ self.val,
+ &format!(
+ "Map entries must contain two children, got {}",
+ fields.len()
+ ),
+ ));
+ }
+ if fields[0].is_nullable() {
+ return Err(make_error(self.val, "Map key field cannot be
nullable"));
+ }
+ } else {
+ return Err(make_error(
+ self.val,
+ &format!(
+ "Map entries must be a Struct type, got {}",
+ field.data_type()
+ ),
+ ));
+ }
self.expect_token(Token::Comma)?;
let sorted = self.parse_map_sorted()?;
self.expect_token(Token::RParen)?;
@@ -611,12 +636,23 @@ impl<'a> Parser<'a> {
);
let (run_ends, values) = if verbose {
- let run_ends = self.parse_ree_verbose_field()?;
+ let run_ends = self.parse_field()?;
+ if run_ends.is_nullable() {
+ return Err(make_error(
+ self.val,
+ "RunEndEncoded run_ends field cannot be nullable",
+ ));
+ }
self.expect_token(Token::Comma)?;
- let values = self.parse_ree_verbose_field()?;
- (run_ends.with_nullable(false), values)
+ let values = self.parse_field()?;
+ (run_ends, values)
} else {
- self.parse_opt_nullable(); // run_ends is always non-null; consume
the token if present
+ if self.parse_opt_nullable() {
+ return Err(make_error(
+ self.val,
+ "RunEndEncoded run_ends field cannot be nullable",
+ ));
+ }
let re_type = self.parse_next_type()?;
self.expect_token(Token::Comma)?;
let v_nullable = self.parse_opt_nullable();
@@ -634,15 +670,6 @@ impl<'a> Parser<'a> {
))
}
- /// Parses `"name": [non-null] Type` used in the verbose REE form.
- fn parse_ree_verbose_field(&mut self) -> ArrowResult<Field> {
- let name = self.parse_double_quoted_string("RunEndEncoded field")?;
- self.expect_token(Token::Colon)?;
- let nullable = self.parse_opt_nullable();
- let data_type = self.parse_next_type()?;
- Ok(Field::new(name, data_type, nullable))
- }
-
/// consume the next token and return `false` if the field is `nonnull`.
fn parse_opt_nullable(&mut self) -> bool {
let tok = self
@@ -1235,19 +1262,6 @@ mod test {
UnionFields::try_new(Vec::<i8>::new(),
Vec::<Field>::new()).unwrap(),
UnionMode::Sparse,
),
- DataType::Map(Arc::new(Field::new("Int64", DataType::Int64,
true)), true),
- DataType::Map(Arc::new(Field::new("Int64", DataType::Int64,
true)), false),
- DataType::Map(
- Arc::new(Field::new_map(
- "nested_map",
- Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
- Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME,
DataType::Utf8, false),
- Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME,
DataType::Int32, true),
- false,
- true,
- )),
- true,
- ),
DataType::RunEndEncoded(
Arc::new(Field::new(
Field::REE_RUN_ENDS_FIELD_DEFAULT_NAME,
@@ -1698,6 +1712,33 @@ mod test {
"Decimal256(0, 0)",
"Error Decimal256 precision must be in range [1, 76], got '0'",
),
+ // REE run_ends cannot be nullable
+ (
+ r#"RunEndEncoded("re": nullable Int32, "v": non-null Utf8)"#,
+ "RunEndEncoded run_ends field cannot be nullable",
+ ),
+ (
+ r#"RunEndEncoded("re": Int32, "v": non-null Utf8)"#,
+ "RunEndEncoded run_ends field cannot be nullable",
+ ),
+ (
+ "RunEndEncoded(nullable Int32, non-null Utf8)",
+ "RunEndEncoded run_ends field cannot be nullable",
+ ),
+ // Map entries field cannot be nullable
+ (
+ r#"Map("entries": Struct("key": non-null Utf8, "value":
nullable Int32), unsorted)"#,
+ "Map entries field cannot be nullable",
+ ),
+ // Map key cannot be nullable
+ (
+ r#"Map("entries": non-null Struct("key": nullable Utf8,
"value": nullable Int32), unsorted)"#,
+ "Map key field cannot be nullable",
+ ),
+ (
+ r#"Map("entries": non-null Struct("key": Utf8, "value":
nullable Int32), unsorted)"#,
+ "Map key field cannot be nullable",
+ ),
];
for (data_type_string, expected_message) in cases {