JingsongLi commented on code in PR #1072:
URL: https://github.com/apache/paimon-rust/pull/1072#discussion_r4215471937


##########
crates/paimon/src/arrow/format/parquet.rs:
##########
@@ -445,7 +445,66 @@ fn parquet_writer_properties(
             );
         }
     }
-    Ok(props.build())
+    Ok(parquet_sizing_properties(props, options)?.build())
+}
+
+/// Java's parquet writer sizing keys; `file.block-size` overrides 
`parquet.block.size` as in
+/// Java's `ParquetFileFormat`.
+fn parquet_sizing_properties(
+    mut props: parquet::file::properties::WriterPropertiesBuilder,
+    options: &HashMap<String, String>,
+) -> crate::Result<parquet::file::properties::WriterPropertiesBuilder> {
+    const BLOCK_SIZE: &str = "parquet.block.size";
+    const PAGE_SIZE: &str = "parquet.page.size";
+    const DICTIONARY_PAGE_SIZE: &str = "parquet.dictionary.page.size";
+    const PAGE_ROW_COUNT_LIMIT: &str = "parquet.page.row.count.limit";
+    const PAGE_SIZE_ROW_CHECK_MIN: &str = "parquet.page.size.row.check.min";
+    let positive = |key: &str| -> crate::Result<Option<usize>> {
+        options
+            .get(key)
+            .map(|value| {
+                value
+                    .trim()
+                    .parse::<usize>()
+                    .ok()
+                    .filter(|parsed| *parsed > 0)
+                    .ok_or_else(|| crate::Error::ConfigInvalid {
+                        message: format!("Option '{key}' must be a positive 
integer, got: {value}"),
+                    })
+            })
+            .transpose()
+    };
+    let file_block_size = CoreOptions::new(options)
+        .file_block_size()?
+        .map(|bytes| {
+            usize::try_from(bytes)
+                .ok()
+                .filter(|bytes| *bytes > 0)
+                .ok_or_else(|| crate::Error::ConfigInvalid {
+                    message: format!("Option 'file.block-size' must be 
positive, got: {bytes}"),
+                })
+        })
+        .transpose()?;
+    if let Some(bytes) = file_block_size.or(positive(BLOCK_SIZE)?) {
+        props = props.set_max_row_group_bytes(Some(bytes));

Review Comment:
   [P2] Bound the initial Arrow batch before relying on the byte limit
   
   parquet-rs 58.4 only estimates/splits by bytes when the current row group 
already contains rows; an empty group accepts the entire first RecordBatch and 
flushes afterwards. The production table writer forwards that batch unchanged. 
I reproduced with an actual filesystem append table: parquet.block.size=16384, 
dictionary disabled, compression=none, and one 65,536-row two-INT batch produce 
a single 559,708-byte row group. The PK buffering path also produces one group 
for 4,000 rows with file.block-size=16 kb. Thus the intended point-read 
granularity still depends on caller/buffer batch size, especially with wide 
rows. Please feed bounded slices and check/flush between them (or fix the 
dependency behavior), and test a large initial batch through TableWrite rather 
than only 100-row format-writer chunks.



##########
crates/paimon/src/arrow/format/parquet.rs:
##########
@@ -445,7 +445,66 @@ fn parquet_writer_properties(
             );
         }
     }
-    Ok(props.build())
+    Ok(parquet_sizing_properties(props, options)?.build())
+}
+
+/// Java's parquet writer sizing keys; `file.block-size` overrides 
`parquet.block.size` as in
+/// Java's `ParquetFileFormat`.
+fn parquet_sizing_properties(
+    mut props: parquet::file::properties::WriterPropertiesBuilder,
+    options: &HashMap<String, String>,
+) -> crate::Result<parquet::file::properties::WriterPropertiesBuilder> {
+    const BLOCK_SIZE: &str = "parquet.block.size";
+    const PAGE_SIZE: &str = "parquet.page.size";
+    const DICTIONARY_PAGE_SIZE: &str = "parquet.dictionary.page.size";
+    const PAGE_ROW_COUNT_LIMIT: &str = "parquet.page.row.count.limit";
+    const PAGE_SIZE_ROW_CHECK_MIN: &str = "parquet.page.size.row.check.min";
+    let positive = |key: &str| -> crate::Result<Option<usize>> {
+        options
+            .get(key)
+            .map(|value| {
+                value
+                    .trim()
+                    .parse::<usize>()
+                    .ok()
+                    .filter(|parsed| *parsed > 0)
+                    .ok_or_else(|| crate::Error::ConfigInvalid {
+                        message: format!("Option '{key}' must be a positive 
integer, got: {value}"),
+                    })
+            })
+            .transpose()
+    };
+    let file_block_size = CoreOptions::new(options)
+        .file_block_size()?
+        .map(|bytes| {
+            usize::try_from(bytes)
+                .ok()
+                .filter(|bytes| *bytes > 0)
+                .ok_or_else(|| crate::Error::ConfigInvalid {
+                    message: format!("Option 'file.block-size' must be 
positive, got: {bytes}"),
+                })
+        })
+        .transpose()?;
+    if let Some(bytes) = file_block_size.or(positive(BLOCK_SIZE)?) {

Review Comment:
   [P2] Parse parquet.block.size only when file.block-size is absent
   
   Option::or eagerly evaluates positive(BLOCK_SIZE)?, even when 
file_block_size is Some. An existing table with file.block-size=16 kb and a 
shadowed parquet.block.size=128mb therefore fails an actual native write with 
ConfigInvalid, although the effective sizing value is valid. Java's 
getParquetConfiguration overwrites BLOCK_SIZE with file.block-size before 
parsing it, so the dormant value does not prevent writes there. Such options 
pass schema validation and previously worked in the native writer. Please make 
this fallback lazy and add a write/commit/read regression with a malformed 
shadowed parquet.block.size.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to