From 8f8010e494860bac0c766474e9dc285708033f74 Mon Sep 17 00:00:00 2001 From: shaoyijie Date: Wed, 20 May 2026 04:56:16 -0700 Subject: [PATCH 1/2] fix: cap VarCharType/VarBinaryType MAX_LENGTH at i32::MAX `isize::MAX as u32` overflows on 64-bit targets and produces `u32::MAX = 4294967295`. The Display impl then serializes `VarCharType::string_type()` as `VARCHAR(4294967295)`, which Java's `DataTypeJsonParser` (used by Bennett to deserialize REST `CreateTableRequest` payloads) rejects via `Integer.parseInt`: Could not parse type at position N: ... Input type string: VARCHAR(4294967295) (through reference chain: org.apache.paimon.rest.requests.CreateTableRequest["schema"] ->org.apache.paimon.schema.Schema["fields"] ->java.util.ArrayList[1]) The wire-format length cap is a protocol constant, not a function of host pointer width. Pin both `MAX_LENGTH` constants to `i32::MAX` (2147483647), matching Java `Integer.MAX_VALUE` exactly. Java-to-Java traffic did not surface this bug because Java's `asSQLString` short-circuits length==MAX to the bare `STRING` alias, while paimon-rust's Display always writes the numeric form. Co-Authored-By: Claude Opus 4.7 --- crates/paimon/src/spec/types.rs | 41 +++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/paimon/src/spec/types.rs b/crates/paimon/src/spec/types.rs index 3abb0bea..23dfc449 100644 --- a/crates/paimon/src/spec/types.rs +++ b/crates/paimon/src/spec/types.rs @@ -1235,7 +1235,9 @@ impl FromStr for VarBinaryType { impl VarBinaryType { pub const MIN_LENGTH: u32 = 1; - pub const MAX_LENGTH: u32 = isize::MAX as u32; + // Aligned with Java `VarBinaryType.MAX_LENGTH = Integer.MAX_VALUE`; see + // `VarCharType::MAX_LENGTH` for the parser overflow this avoids. + pub const MAX_LENGTH: u32 = i32::MAX as u32; pub const DEFAULT_LENGTH: u32 = 1; @@ -1329,7 +1331,11 @@ impl FromStr for VarCharType { impl VarCharType { pub const MIN_LENGTH: u32 = 1; - pub const MAX_LENGTH: u32 = isize::MAX as u32; + // Aligned with Java `VarCharType.MAX_LENGTH = Integer.MAX_VALUE`. Casting + // `isize::MAX` here overflows on 64-bit targets and produces `u32::MAX` + // (4294967295), which Java's `DataTypeJsonParser` then fails to parse via + // `Integer.parseInt` when reading a `CreateTableRequest`. + pub const MAX_LENGTH: u32 = i32::MAX as u32; pub const DEFAULT_LENGTH: u32 = 1; @@ -2239,4 +2245,35 @@ mod tests { assert_eq!(actual, expect, "test data type deserialize for {name}") } } + + /// Regression: `MAX_LENGTH` for `VarCharType`/`VarBinaryType` must fit in a + /// Java `int`, otherwise `DataTypeJsonParser` rejects the `CreateTableRequest` + /// REST payload with `NumberFormatException` on `Integer.parseInt`. + #[test] + fn test_max_length_fits_java_integer() { + const JAVA_INTEGER_MAX_VALUE: u32 = i32::MAX as u32; + + assert_eq!(VarCharType::MAX_LENGTH, JAVA_INTEGER_MAX_VALUE); + assert_eq!(VarBinaryType::MAX_LENGTH, JAVA_INTEGER_MAX_VALUE); + + let varchar = VarCharType::string_type().to_string(); + let length_token = varchar + .strip_prefix("VARCHAR(") + .and_then(|s| s.split(')').next()) + .expect("VARCHAR display format"); + length_token + .parse::() + .expect("VARCHAR length must parse as Java int"); + + let varbinary = VarBinaryType::with_nullable(true, VarBinaryType::MAX_LENGTH) + .unwrap() + .to_string(); + let length_token = varbinary + .strip_prefix("VARBINARY(") + .and_then(|s| s.split(')').next()) + .expect("VARBINARY display format"); + length_token + .parse::() + .expect("VARBINARY length must parse as Java int"); + } } From b1411d1eeed6c347b0895350a191d347d1b05255 Mon Sep 17 00:00:00 2001 From: shaoyijie Date: Wed, 20 May 2026 05:13:32 -0700 Subject: [PATCH 2/2] fix: use VarBinaryType::try_new in regression test VarBinaryType exposes `try_new(nullable, length)` (not `with_nullable`, which only exists on VarCharType). Use the right constructor so the new regression test compiles. Co-Authored-By: Claude Opus 4.7 --- crates/paimon/src/spec/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/paimon/src/spec/types.rs b/crates/paimon/src/spec/types.rs index 23dfc449..c7a945b3 100644 --- a/crates/paimon/src/spec/types.rs +++ b/crates/paimon/src/spec/types.rs @@ -2265,7 +2265,7 @@ mod tests { .parse::() .expect("VARCHAR length must parse as Java int"); - let varbinary = VarBinaryType::with_nullable(true, VarBinaryType::MAX_LENGTH) + let varbinary = VarBinaryType::try_new(true, VarBinaryType::MAX_LENGTH) .unwrap() .to_string(); let length_token = varbinary