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


##########
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:
   The splitter fails on sub-millisecond timestamps whether or not the column 
is materialized, so the non-materialized case still breaks. Drop `materialize 
&&`, or reword the reason?



##########
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:
   This compares rows read through Delta, so it never reads the physical `part` 
column. Could we check it from the files too?



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