felipepessoto commented on code in PR #13502:
URL: https://github.com/apache/gluten/pull/13502#discussion_r4485191447


##########
backends-velox/src-delta4/test/scala/org/apache/gluten/execution/VeloxDelta4WriteSuite.scala:
##########
@@ -121,46 +123,135 @@ class VeloxDelta4WriteSuite extends DeltaSQLCommandTest {
     }
   }
 
-  test("a partitioned table falls back, since Delta writes the partition 
columns") {
-    withTempDir {
-      dir =>
-        val path = dir.getCanonicalPath
-        createTable(path, partitioned = true)
-        writeRows(path)
+  for (feature <- Seq(false, true); property <- Seq(None, Some(false), 
Some(true))) {
+    test(s"native partition materialization: feature=$feature, 
property=$property") {
+      withTempDir {
+        dir =>
+          val path = dir.getCanonicalPath
+          val properties = property.map(v => 
"delta.writePartitionColumnsToParquet" -> v.toString)
+            .toSeq ++ (if (feature) {
+                         Seq("delta.feature.materializePartitionColumns" -> 
"supported")
+                       } else {
+                         Seq.empty
+                       })
+          createTable(path, partitioned = true, properties: _*)
+          writeRows(path)
 
-        assertWrittenBy(path, velox = false, Seq("id", "value", "part"))
-        footers(path).foreach {
-          case (file, _) => assert(parse(file.stats) \ "minValues" \ "part" == 
JNothing)
-        }
-        assertRows(path)
+          val snapshot = DeltaLog.forTable(spark, path).update()
+          
assert(snapshot.protocol.isFeatureSupported(MaterializePartitionColumnsTableFeature)
 ==
+            feature)
+          
assert(snapshot.metadata.configuration.get("delta.writePartitionColumnsToParquet")
 ==
+            property.map(_.toString))
+          val materialize = feature || property.getOrElse(true)
+          val columns = if (materialize) Seq("id", "value", "part") else 
Seq("id", "value")
+          assertWrittenBy(path, velox = true, columns)
+          withSQLConf(GlutenConfig.GLUTEN_ENABLED.key -> "false") {
+            footers(path).foreach {
+              case (file, _) =>
+                val stats = parse(file.stats)
+                for (field <- Seq("minValues", "maxValues", "nullCount")) {
+                  assert(stats \ field \ "part" == JNothing, file.stats)
+                }
+                if (materialize) {
+                  val filePath = DeltaFileOperations.absolutePath(path, 
file.path).toString
+                  val rows = spark.read.option("recursiveFileLookup", "true")
+                    .parquet(filePath).select("part").collect()
+                  assert(rows.nonEmpty)
+                  assert(rows.forall(_.getInt(0).toString == 
file.partitionValues("part")))
+                }
+            }
+          }
+          assertRows(path)
+      }
     }
   }
 
-  test("a partitioned table without partition columns in its files is written 
natively") {
+  test("enabling materialization preserves reads of older files without 
partition columns") {
     withTempDir {
       dir =>
         val path = dir.getCanonicalPath
         createTable(path, partitioned = true, 
"delta.writePartitionColumnsToParquet" -> "false")
         writeRows(path)
+        val oldFiles = footers(path).map(_._1.path).toSet
 
-        assertWrittenBy(path, velox = true, Seq("id", "value"))
-        assertRows(path)
+        sql(s"ALTER TABLE delta.`$path` SET TBLPROPERTIES " +
+          "('delta.feature.materializePartitionColumns' = 'supported')")
+        writeRows(path)
+
+        val files = footers(path)
+        assert(files.exists { case (file, _) => !oldFiles.contains(file.path) 
})
+        files.foreach {
+          case (file, footer) =>
+            assert(writtenByVelox(footer), file.path)
+            val columns = if (oldFiles.contains(file.path)) {
+              Seq("id", "value")
+            } else {
+              Seq("id", "value", "part")
+            }
+            assert(fieldNames(footer) == columns, file.path)
+        }
+        val expected = (0 until 10).flatMap(i => Seq.fill(2)(Row(i.toLong, 
i.toString, i % 2)))
+        for (gluten <- Seq("false", "true")) {
+          withSQLConf(GlutenConfig.GLUTEN_ENABLED.key -> gluten) {
+            
assert(spark.read.format("delta").load(path).orderBy("id").collect().toSeq == 
expected)
+          }
+        }
     }
   }
 
-  test("materializePartitionColumns falls back") {
+  test("native updates and deletes materialize partition columns like vanilla 
Delta") {
     withTempDir {
-      dir =>
-        val path = dir.getCanonicalPath
-        createTable(
-          path,
-          partitioned = true,
-          "delta.writePartitionColumnsToParquet" -> "false",
-          "delta.enableMaterializePartitionColumnsFeature" -> "true")
-        writeRows(path)
-
-        assertWrittenBy(path, velox = false, Seq("id", "value", "part"))
-        assertRows(path)
+      nativeDir =>
+        withTempDir {
+          vanillaDir =>
+            val nativePath = nativeDir.getCanonicalPath
+            val vanillaPath = vanillaDir.getCanonicalPath
+            val paths = Seq(nativePath -> "true", vanillaPath -> "false")
+            paths.foreach {
+              case (path, native) =>
+                withSQLConf(VeloxDeltaConfig.ENABLE_NATIVE_WRITE.key -> 
native) {
+                  createTable(
+                    path,
+                    partitioned = true,
+                    "delta.feature.materializePartitionColumns" -> "supported",
+                    "delta.writePartitionColumnsToParquet" -> "false",
+                    "delta.enableDeletionVectors" -> "false"
+                  )
+                  writeRows(path)
+                }
+            }
+            val commands = Seq(
+              "UPDATE delta.`%s` SET part = part + 2, value = 'updated' WHERE 
id = 0",
+              "DELETE FROM delta.`%s` WHERE id = 1")
+            commands.foreach {
+              command =>
+                val oldFiles = footers(nativePath).map(_._1.path).toSet
+                paths.foreach {
+                  case (path, native) =>
+                    withSQLConf(VeloxDeltaConfig.ENABLE_NATIVE_WRITE.key -> 
native) {
+                      sql(command.format(path)).collect()
+                    }
+                }
+                val newFiles = footers(nativePath).filterNot {
+                  case (file, _) => oldFiles.contains(file.path)
+                }
+                assert(newFiles.nonEmpty)
+                newFiles.foreach {
+                  case (file, footer) =>
+                    assert(writtenByVelox(footer), file.path)
+                    assert(fieldNames(footer) == Seq("id", "value", "part"), 
file.path)
+                    val stats = parse(file.stats)
+                    for (field <- Seq("minValues", "maxValues", "nullCount")) {
+                      assert(stats \ field \ "part" == JNothing, file.stats)
+                    }
+                }
+                withSQLConf(GlutenConfig.GLUTEN_ENABLED.key -> "false") {

Review Comment:
   Added direct Parquet reads in e57998b8c, with `recursiveFileLookup=true` and 
Gluten disabled for the independent read. Each rewritten file's stored `part` 
values are compared with `AddFile.partitionValues`, and all active physical 
rows are checked against explicit expected rows after UPDATE and DELETE, for 
both native and vanilla writes. This now checks the changed partition value 
itself, not just values reconstructed by Delta. The suite passes on Spark 4.1.



##########
backends-velox/src-delta4/main/scala/org/apache/spark/sql/delta/GlutenDeltaNativeWriteGate.scala:
##########
@@ -122,6 +120,18 @@ object GlutenDeltaNativeWriteGate {
   private val cryptoFactoryClass = 
EncryptionPropertiesFactory.CRYPTO_FACTORY_CLASS_PROPERTY_NAME
   private val parquetV1 = WriterVersion.PARQUET_1_0
 
+  private def unsupportedPartitionColumns(
+      partitionColumns: Seq[Attribute],
+      materialize: Boolean): Option[String] =
+    partitionColumns.collectFirst {
+      case column if column.dataType == FloatType || column.dataType == 
DoubleType =>
+        s"${column.dataType.sql} partition columns are not supported by the 
native Delta writer"
+      // PartitionIdGenerator cannot represent sub-millisecond timestamps. 
Keep the existing
+      // materialized-timestamp fallback until splitting preserves their full 
precision.
+      case column if materialize && column.dataType == TimestampType =>

Review Comment:
   Removed the materialization condition in e57998b8c. TIMESTAMP partition 
columns now fall back in both modes; timestamp data columns keep their existing 
encoding checks. The docs call out that this is conservative even for 
millisecond-only partition values. Added a physical-file regression with four 
distinct microsecond partition values for both `writePartitionColumnsToParquet` 
settings; both pass on Spark 4.1.



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