jordepic commented on code in PR #5361:
URL: https://github.com/apache/datafusion-comet/pull/5361#discussion_r3917002498


##########
spark/src/main/scala/org/apache/comet/serde/operator/CometIcebergNativeWrite.scala:
##########
@@ -263,6 +275,15 @@ object CometIcebergNativeWrite extends 
CometOperatorSerde[IcebergWriteExec] with
       .map(_ => s"$key=true (variant shredding changes the parquet schema)")
   }
 
+  // iceberg-java throws NumberFormatException at write time for a non-integer 
level, while the
+  // native translation would silently substitute the codec default. Fall back 
so the failure
+  // behaviour matches the stock path.
+  private val requireParseableCompressionLevel: TriggerRule = ctx =>
+    ctx.properties
+      .get(PropertyKeys.ParquetCompressionLevel)
+      .filter(v => scala.util.Try(java.lang.Integer.parseInt(v)).isFailure)

Review Comment:
   Confirmed against the pinned parquet-rs 58 source: `CompressionLevel` bounds 
are exactly zstd `1..=22`, gzip `0..=9`, brotli `0..=11`, and 
`build_writer_properties` errors outside them — while, as you dug out, 
iceberg-java keeps the level as a raw string with no validation at all. The 
rule is now `requireNativeSupportedCompressionLevel`, delegating to a new 
`IcebergWriteProtoTranslation.compressionLevelRejection` that resolves the 
effective codec via `resolveCompression` and declines any level the 
corresponding parquet-rs type would reject (codecs without a native level 
concept — snappy/lz4/none — ignore the property on both sides, so any int 
passes). All five of your boundary cases are pinned in the detection suite next 
to the existing `fast` test, plus zstd `22` staying Compatible so the gate is 
not over-broad, and the range matrix (rejected and accepted boundaries per 
codec) is unit-tested in `IcebergWriteProtoTranslationSuite`.



##########
spark/src/main/scala/org/apache/spark/sql/comet/CometIcebergWriteExec.scala:
##########
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.spark.sql.comet
+
+import org.apache.spark.TaskContext
+import org.apache.spark.rdd.RDD
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.expressions.{Attribute, 
AttributeReference}
+import org.apache.spark.sql.catalyst.expressions.UnsafeProjection
+import org.apache.spark.sql.comet.execution.arrow.CometArrowStream
+import org.apache.spark.sql.comet.util.{Utils => CometUtils}
+import org.apache.spark.sql.connector.write.{BatchWrite, WriterCommitMessage}
+import org.apache.spark.sql.execution.{ColumnarToRowTransition, SparkPlan, 
UnaryExecNode}
+import org.apache.spark.sql.execution.metric.{SQLMetric, SQLMetrics}
+import org.apache.spark.sql.types.BinaryType
+import org.apache.spark.sql.vectorized.ColumnarBatch
+
+import com.google.protobuf.CodedOutputStream
+
+import org.apache.comet.CometExecIterator
+import org.apache.comet.iceberg.IcebergReflection
+import org.apache.comet.serde.OperatorOuterClass.Operator
+
+/**
+ * Native variant of [[IcebergWriteExec]]. Drives the iceberg-rust writer 
stack via Comet's native
+ * execution pipeline; the JVM side decodes the per-task Avro-encoded 
`DataFile` blob the native
+ * operator emits and packages it as a [[WriterCommitMessage]] so the outer 
[[IcebergCommitExec]]
+ * consumes it unchanged.
+ *
+ * Selected by [[org.apache.comet.serde.operator.CometIcebergNativeWrite]] 
when the table's
+ * properties allow it (parquet, V2, no encryption, ...) and the child plan is 
fully Comet-native;
+ * otherwise the JVM path's [[IcebergWriteExec]] runs instead.
+ *
+ * @param nativeOp
+ *   Template operator carrying the `IcebergWrite` proto. Per-task 
`partition_id` /
+ *   `task_attempt_id` get stamped on a copy at execution time.
+ * @param child
+ *   Comet native child (must be a [[CometNativeExec]] so columnar batches 
flow through FFI).
+ * @param batchWrite
+ *   Shared with the outer [[IcebergCommitExec]] -- the same instance the 
strategy materialised
+ *   via `write.toBatch`. Used here only to provide the `dataLocation` / 
partition spec needed by
+ *   the native side; never invoked for commit.
+ * @param partitionSpecId
+ *   Output partition spec id (from `SparkWrite.outputSpecId`). Decoded 
`DataFile`s are stamped
+ *   with this spec id; required because iceberg-rust's `DataFile` is 
spec-agnostic at the wire.
+ */
+case class CometIcebergWriteExec(
+    nativeOp: Operator,
+    child: SparkPlan,
+    @transient batchWrite: BatchWrite,
+    @transient table: AnyRef,
+    partitionSpecId: Int)
+    extends CometNativeExec
+    with UnaryExecNode
+    // We consume Arrow batches (via FFI) and emit row-shaped commit messages, 
so we are a
+    // columnar-to-row transition. Without this trait Spark's
+    // `ApplyColumnarRulesAndInsertTransitions` wedges a 
`CometNativeColumnarToRowExec` between
+    // us and the Comet-native child, which would then fail 
`child.executeColumnar()` in
+    // `doExecuteColumnar`.
+    with ColumnarToRowTransition {
+
+  override def originalPlan: SparkPlan = child
+
+  // Same output schema as IcebergWriteExec so the outer IcebergCommitExec 
consumes the
+  // commit messages identically regardless of which inner exec emitted them.
+  override def output: Seq[Attribute] = Seq(
+    AttributeReference(IcebergWriteExec.CommitMessageColumn, BinaryType, 
nullable = false)())
+
+  // Native exec emits a single Binary column; the surrounding command 
framework expects rows, so
+  // the outer commit exec calls executeCollect on us. supportsColumnar = 
false keeps Spark from
+  // inserting a ColumnarToRow that would clash with our (Nil-output-like) row 
contract.
+  override def supportsColumnar: Boolean = false
+
+  override def executeCollect(): Array[InternalRow] = {
+    val rdd = doExecute()
+    // SparkPlan.executeCollect defaults to byteArrayRdd which goes through 
UnsafeRow encoding;
+    // doExecute already projects each row through UnsafeProjection (see the 
per-task closure
+    // below) so a plain `collect()` is safe.
+    rdd.collect()
+  }
+
+  override def serializedPlanOpt: SerializedPlan = {
+    val size = nativeOp.getSerializedSize
+    val bytes = new Array[Byte](size)
+    val codedOutput = CodedOutputStream.newInstance(bytes)
+    nativeOp.writeTo(codedOutput)
+    codedOutput.checkNoSpaceLeft()
+    SerializedPlan(Some(bytes))
+  }
+
+  override def withNewChildInternal(newChild: SparkPlan): SparkPlan = 
copy(child = newChild)
+
+  override def nodeName: String = "CometIcebergWrite"

Review Comment:
   Yikes — right, and this became load-bearing the moment the fs.s3a merge 
landed. Added the `stringArgs` override modeled on 
`CometIcebergNativeScanExec`: `Iterator(output, s"$dataLocation, 
$writerMode")`, so explain()/UI/event-log render a short descriptor instead of 
the protobuf TextFormat dump. Pinned exactly where you suggested: the fs.s3a 
forwarding test now also asserts `simpleString(Int.MaxValue)` does not contain 
the injected access key (and does contain the data location, so the node stays 
readable).



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