[spark] Fix V2 write with Hive dynamic partition column order#8414
Conversation
9459c61 to
4505435
Compare
There was a problem hiding this comment.
Pull request overview
This PR addresses incorrect handling of Hive-style dynamic partition column ordering during Spark V2 writes, ensuring Paimon can accept queries whose partition columns appear in Hive’s expected positions without breaking non-Paimon inserts.
Changes:
- Adds a parser/analyzer rule that marks INSERT statements containing Hive-style dynamic partitions.
- Extends Paimon write analysis to strip the marker and resolve output columns correctly for dynamic partition overwrites (including Hive ordering).
- Adds regression tests covering both non-Paimon inserts and Paimon V2 dynamic overwrite scenarios.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/InsertOverwriteTableTestBase.scala | Adds regression tests for non-Paimon dynamic partition inserts and Paimon V2 dynamic overwrite with Hive partition column order. |
| paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/AbstractPaimonSparkSqlExtensionsParser.scala | Introduces a rule to tag Hive dynamic partition INSERTs by wrapping the query plan. |
| paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/plans/logical/PaimonHiveDynamicPartitionQuery.scala | Adds a lightweight logical wrapper node to carry dynamic partition column metadata through analysis. |
| paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/PaimonAnalysis.scala | Updates V2 write resolution to detect/strip the wrapper and apply Hive-style output ordering when needed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private def withNewQuery(insert: InsertIntoStatement, query: LogicalPlan): InsertIntoStatement = { | ||
| insert.getClass | ||
| .getMethod("withNewChildInternal", classOf[LogicalPlan]) | ||
| .invoke(insert, query) | ||
| .asInstanceOf[InsertIntoStatement] | ||
| } |
| } | ||
| } | ||
|
|
||
| test("Paimon parser: dynamic partition marker is removed for non-Paimon inserts") { |
There was a problem hiding this comment.
maybe “Paimon Insert: overwrite format(parquet) table in static mode”?
| query.transformDown { case PaimonHiveDynamicPartitionQuery(_, child) => child } | ||
| } | ||
|
|
||
| private def resolveHiveDynamicPartitionWrite( |
There was a problem hiding this comment.
resolveHiveDynamicPartitionWrite and resolveDynamicPartitionOverwrite are near duplicates. Both methods share the same shape: check hiveStyleDynamicPartitionOutput, and on match do "by-position → by-name", otherwise a single by-name resolve. Only the way dynamicPartitionColumns is obtained differs (from the marker vs. from table.partitionKeys()).
| table: DataSourceV2Relation, | ||
| dynamicPartitionColumns: Seq[String]): Option[Seq[Attribute]] = { | ||
| val partitionKeys = table.table.asInstanceOf[SparkTable].getTable.partitionKeys().asScala.toSeq | ||
| val dynamicPartitionSet = dynamicPartitionColumns |
There was a problem hiding this comment.
It is a misleading no-op alias.
|
@kerwin-zk left some comments. |
4505435 to
c2ee502
Compare
|
@YannByron done |
|
+1 |
Purpose
Fix V2 write with Hive dynamic partition column order
Tests
CI