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

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 009d6100 fix(spec): accept Java's long memory-size units and reject 
overflow (#621)
009d6100 is described below

commit 009d610023be43e494faedb77a2d1ace6f33fcfd
Author: jackylee <[email protected]>
AuthorDate: Tue Jul 28 23:13:05 2026 +0800

    fix(spec): accept Java's long memory-size units and reject overflow (#621)
---
 crates/paimon/src/spec/core_options.rs | 83 ++++++++++++++++++++++++++++------
 1 file changed, 70 insertions(+), 13 deletions(-)

diff --git a/crates/paimon/src/spec/core_options.rs 
b/crates/paimon/src/spec/core_options.rs
index ec23ad69..1e814aec 100644
--- a/crates/paimon/src/spec/core_options.rs
+++ b/crates/paimon/src/spec/core_options.rs
@@ -1228,14 +1228,15 @@ impl<'a> CoreOptions<'a> {
     }
 }
 
-/// Parse a memory size string to bytes using binary (1024-based) semantics.
+/// Parse a memory size string to bytes using binary (1024-based) semantics,
+/// mirroring Java Paimon's `MemorySize.parseBytes`.
 ///
-/// Supports formats like `128 mb`, `128mb`, `4 gb`, `1024` (plain bytes).
-/// Uses binary units: `kb` = 1024, `mb` = 1024², `gb` = 1024³, matching Java 
Paimon's `MemorySize`.
-///
-/// NOTE: Java Paimon's `MemorySize` also accepts long unit names such as 
`bytes`,
-/// `kibibytes`, `mebibytes`, `gibibytes`, and `tebibytes`. This implementation
-/// only supports short units (`b`, `kb`, `mb`, `gb`, `tb`), which covers all 
practical usage.
+/// Accepts every unit spelling Java accepts — short (`b`, `k`, `kb`, `m`, 
`mb`,
+/// `g`, `gb`, `t`, `tb`) and long (`bytes`, `kibibytes`, `mebibytes`,
+/// `gibibytes`, `tebibytes`) — plus a bare number, which is interpreted as
+/// bytes. Returns `None` for an empty string, a missing or non-numeric number,
+/// an unrecognized unit, or a value that would overflow `i64`, matching the
+/// inputs on which Java throws.
 fn parse_memory_size(value: &str) -> Option<i64> {
     let value = value.trim();
     if value.is_empty() {
@@ -1248,14 +1249,14 @@ fn parse_memory_size(value: &str) -> Option<i64> {
     let (num_str, unit_str) = value.split_at(pos);
     let num: i64 = num_str.trim().parse().ok()?;
     let multiplier = match unit_str.trim().to_ascii_lowercase().as_str() {
-        "" | "b" => 1,
-        "kb" | "k" => 1024,
-        "mb" | "m" => 1024 * 1024,
-        "gb" | "g" => 1024 * 1024 * 1024,
-        "tb" | "t" => 1024 * 1024 * 1024 * 1024,
+        "" | "b" | "bytes" => 1,
+        "k" | "kb" | "kibibytes" => 1024,
+        "m" | "mb" | "mebibytes" => 1024 * 1024,
+        "g" | "gb" | "gibibytes" => 1024 * 1024 * 1024,
+        "t" | "tb" | "tebibytes" => 1024 * 1024 * 1024 * 1024,
         _ => return None,
     };
-    Some(num * multiplier)
+    num.checked_mul(multiplier)
 }
 
 #[cfg(test)]
@@ -1531,6 +1532,62 @@ mod tests {
         assert_eq!(parse_memory_size("abc"), None);
     }
 
+    #[test]
+    fn test_parse_memory_size_accepts_every_java_unit_spelling() {
+        // Every alias in Java `MemorySize.MemoryUnit`, which lists three
+        // spellings per unit (e.g. `m`, `mb`, `mebibytes`).
+        for (unit, multiplier) in [
+            ("b", 1_i64),
+            ("bytes", 1),
+            ("k", 1024),
+            ("kb", 1024),
+            ("kibibytes", 1024),
+            ("m", 1024 * 1024),
+            ("mb", 1024 * 1024),
+            ("mebibytes", 1024 * 1024),
+            ("g", 1024 * 1024 * 1024),
+            ("gb", 1024 * 1024 * 1024),
+            ("gibibytes", 1024 * 1024 * 1024),
+            ("t", 1024_i64 * 1024 * 1024 * 1024),
+            ("tb", 1024_i64 * 1024 * 1024 * 1024),
+            ("tebibytes", 1024_i64 * 1024 * 1024 * 1024),
+        ] {
+            assert_eq!(
+                parse_memory_size(&format!("3{unit}")),
+                Some(3 * multiplier),
+                "unit '{unit}' should parse as {multiplier} bytes"
+            );
+            // Java lower-cases the unit and trims the gap, so these are equal.
+            assert_eq!(
+                parse_memory_size(&format!("3 {}", unit.to_uppercase())),
+                Some(3 * multiplier),
+                "unit '{unit}' should parse case-insensitively with a space"
+            );
+        }
+    }
+
+    #[test]
+    fn test_parse_memory_size_rejects_overflow_instead_of_wrapping() {
+        // Java raises "numeric overflow" for these; returning `None` lets the
+        // callers fall back to their default rather than wrap to a negative
+        // size (a debug build would panic on the multiplication).
+        assert_eq!(parse_memory_size("9007199254740993 kb"), None);
+        assert_eq!(parse_memory_size("9223372036854775807 tb"), None);
+        // The largest representable value still parses.
+        assert_eq!(
+            parse_memory_size("8589934591 gb"),
+            Some(8589934591 * 1024 * 1024 * 1024)
+        );
+    }
+
+    #[test]
+    fn test_parse_memory_size_rejects_unknown_and_malformed_units() {
+        assert_eq!(parse_memory_size("128 zb"), None);
+        assert_eq!(parse_memory_size("128 megabytes"), None);
+        assert_eq!(parse_memory_size("mb"), None);
+        assert_eq!(parse_memory_size("-1"), None);
+    }
+
     #[test]
     fn test_partition_options_defaults() {
         let options = HashMap::new();

Reply via email to