sunchao commented on code in PR #5724:
URL: https://github.com/apache/datafusion-comet/pull/5724#discussion_r4104515495


##########
spark/src/main/scala/org/apache/comet/serde/operator/CometIcebergNativeWrite.scala:
##########
@@ -283,6 +284,180 @@ object CometIcebergNativeWrite extends 
CometOperatorSerde[IcebergWriteExec] {
   private val requireNativeSupportedCompressionLevel: TriggerRule = ctx =>
     IcebergWriteProtoTranslation.compressionLevelRejection(ctx.properties)
 
+  // These are Apache Parquet Java BlockSplitBloomFilter implementation 
bounds, not Iceberg
+  // TableProperties constants, so they cannot be obtained through 
IcebergReflection:
+  // scalastyle:off line.size.limit
+  // 
https://github.com/apache/parquet-java/blob/78a8d3230eb4769db93de5f2f2e18363c04cae81/parquet-column/src/main/java/org/apache/parquet/column/values/bloomfilter/BlockSplitBloomFilter.java#L40-L50
+  // scalastyle:on line.size.limit
+  private[operator] val MinBloomFilterBytes = 32
+  private[operator] val MaxBloomFilterBytes = 128 * 1024 * 1024
+  private val BloomFilterHashProbes = 8
+  private val MaxNonOverflowingBloomFilterNdv = Long.MaxValue / 
BloomFilterHashProbes
+
+  /**
+   * Keep only Bloom shape properties interpreted by the Iceberg runtime on 
the classpath. Older
+   * Iceberg releases leave these table properties untouched but do not pass 
them to parquet-mr.
+   * Ignoring them here preserves that version's JVM-writer behavior while 
allowing the remaining
+   * supported Bloom configuration to execute natively.
+   */
+  private def interpretedBloomFilterProperties(
+      properties: Map[String, String]): Map[String, String] = {
+    val unsupportedPrefixes = Seq(
+      PropertyKeys.ParquetBloomFilterColumnFppPrefix ->
+        "PARQUET_BLOOM_FILTER_COLUMN_FPP_PREFIX",
+      PropertyKeys.ParquetBloomFilterColumnNdvPrefix ->
+        "PARQUET_BLOOM_FILTER_COLUMN_NDV_PREFIX").collect {
+      case (prefix, constant) if 
IcebergReflection.tablePropertyConstantOpt(constant).isEmpty =>
+        prefix
+    }
+    properties.filterNot { case (key, _) => 
unsupportedPrefixes.exists(key.startsWith) }
+  }
+
+  /**
+   * parquet-rs 59.3.0 initially represents Bloom filters as a power-of-two 
number of bytes and
+   * may fold that allocation after values are inserted. parquet-mr accepts 
arbitrary caps and,
+   * when one binds, serializes that exact length. Keep those writes on the 
classic path instead
+   * of silently changing the number of usable Bloom blocks.
+   */
+  private val requireNativeSupportedBloomFilterProperties: TriggerRule = ctx 
=> {
+    val properties = interpretedBloomFilterProperties(ctx.properties)
+    val configuredMaxBytes = 
properties.get(PropertyKeys.ParquetBloomFilterMaxBytes).map { raw =>
+      raw -> scala.util.Try(java.lang.Integer.parseInt(raw)).toOption
+    }
+    val malformedMaxRejection = configuredMaxBytes.collect { case (raw, None) 
=>
+      s"${PropertyKeys.ParquetBloomFilterMaxBytes}=$raw is not a Java int"
+    }
+    // Iceberg visits every enabled-prefix entry and applies enabled, FPP, 
then NDV. With no such
+    // entry, a parseable maximum cannot affect the file and must not force an 
otherwise-compatible
+    // write onto the classic path. Keep rejecting malformed values because 
protobuf translation
+    // parses this property before native execution.
+    val configured = properties.iterator.collect {
+      case (key, _) if 
key.startsWith(PropertyKeys.BloomFilterColumnEnabledPrefix) =>
+        key.substring(PropertyKeys.BloomFilterColumnEnabledPrefix.length)
+    }.toSeq
+    if (configured.isEmpty) {
+      malformedMaxRejection

Review Comment:
   [P2] Preserve positive-value validation when the Bloom cap is unused. For an 
otherwise eligible table with `write.parquet.bloom-filter-max-bytes='0'` or 
`'-1'` and no enabled-column properties, inserting a row now passes this gate. 
Translation preserves the value, and Rust skips validation because its 
enabled-column loop is empty. Iceberg 1.5.2, 1.8.1, 1.10.0 and 1.11.0 
unconditionally reject these settings with `IllegalArgumentException: bloom 
Filter Max Bytes must be > 0`. This changes a rejected write into a permitted 
native write and hides invalid configuration. Could this branch reject parsed 
values `<= 0` while still allowing positive unused caps such as `100`, with a 
JVM/native error-parity test?
   
   Evidence: Bounded component reproduction in `/tmp/comet5724-review`: the 
extracted exact-head Scala gate returned `None` for `0`, `-1` and `-2147483648` 
under all four runtime capability sets. The extracted native property builder, 
linked to parquet-rs 59.3.0, successfully wrote two INT32 rows into a 220-byte 
Parquet file for each value. The identical validation block extracted from all 
four Iceberg `Parquet.java` versions rejected each value with the expected 
IllegalArgumentException. Controls accepted `100` and rejected malformed text. 
Integration constants, error types and compression selection were stubbed. This 
was component validation, not a full Spark execution.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to