This is an automated email from the ASF dual-hosted git repository.

comphead pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new e2d66d3646 feat: implement native empty2null spark inner function 
(#4683)
e2d66d3646 is described below

commit e2d66d3646e046d28d84188ce033c8b0f3d10633
Author: Kazantsev Maksim <[email protected]>
AuthorDate: Wed Jul 8 21:40:41 2026 +0400

    feat: implement native empty2null spark inner function (#4683)
---
 .github/workflows/pr_build_linux.yml               |  1 +
 .github/workflows/pr_build_macos.yml               |  1 +
 .../org/apache/comet/serde/QueryPlanSerde.scala    |  3 +-
 .../scala/org/apache/comet/serde/strings.scala     |  6 +++-
 .../comet/parquet/CometParquetWriterSuite.scala    | 24 ++++++++++++++-
 .../sql/CometSparkInternalFunctionsSuite.scala     | 35 ++++++++++++++++++++++
 .../sql/CometSparkInternalFunctionsSuite.scala     | 35 ++++++++++++++++++++++
 7 files changed, 102 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/pr_build_linux.yml 
b/.github/workflows/pr_build_linux.yml
index 678ce4efcd..1dd99fbe8d 100644
--- a/.github/workflows/pr_build_linux.yml
+++ b/.github/workflows/pr_build_linux.yml
@@ -365,6 +365,7 @@ jobs:
               org.apache.spark.sql.CometCollationSuite
               org.apache.comet.CometFuzzAggregateSuite
               org.apache.spark.sql.comet.execution.arrow.CometArrowStreamSuite
+              org.apache.spark.sql.CometSparkInternalFunctionsSuite
           - name: "expressions"
             value: |
               org.apache.comet.CometExpressionSuite
diff --git a/.github/workflows/pr_build_macos.yml 
b/.github/workflows/pr_build_macos.yml
index 880ebf031d..0fb0062e3f 100644
--- a/.github/workflows/pr_build_macos.yml
+++ b/.github/workflows/pr_build_macos.yml
@@ -181,6 +181,7 @@ jobs:
               org.apache.spark.sql.CometCollationSuite
               org.apache.comet.CometFuzzAggregateSuite
               org.apache.spark.sql.comet.execution.arrow.CometArrowStreamSuite
+              org.apache.spark.sql.CometSparkInternalFunctionsSuite
           - name: "expressions"
             value: |
               org.apache.comet.CometExpressionSuite
diff --git a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala 
b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
index 91a93d633b..1e7aacd402 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -262,7 +262,8 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
       classOf[ToCharacter] -> CometToCharacter,
       classOf[ToNumber] -> CometToNumber,
       classOf[TryToNumber] -> CometTryToNumber,
-      classOf[Mask] -> CometMask)
+      classOf[Mask] -> CometMask,
+      classOf[Empty2Null] -> CometEmpty2Null)
     base ++ sparkVersionSpecificStringExpressions
   }
 
diff --git a/spark/src/main/scala/org/apache/comet/serde/strings.scala 
b/spark/src/main/scala/org/apache/comet/serde/strings.scala
index 00b339a195..4918a7fd74 100644
--- a/spark/src/main/scala/org/apache/comet/serde/strings.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/strings.scala
@@ -19,7 +19,7 @@
 
 package org.apache.comet.serde
 
-import org.apache.spark.sql.catalyst.expressions.{Attribute, Base64, 
BitLength, Cast, Concat, ConcatWs, Elt, Expression, FindInSet, FormatNumber, 
FormatString, GetJsonObject, If, InitCap, IsNull, Left, Length, Levenshtein, 
Like, Literal, Lower, Mask, OctetLength, Overlay, RegExpExtract, 
RegExpExtractAll, RegExpInStr, RegExpReplace, Right, RLike, SoundEx, 
StringLocate, StringLPad, StringRepeat, StringReplace, StringRPad, StringSplit, 
StringTranslate, Substring, SubstringIndex, ToCharacter [...]
+import org.apache.spark.sql.catalyst.expressions.{Attribute, Base64, 
BitLength, Cast, Concat, ConcatWs, Elt, Empty2Null, Expression, FindInSet, 
FormatNumber, FormatString, GetJsonObject, If, InitCap, IsNull, Left, Length, 
Levenshtein, Like, Literal, Lower, Mask, OctetLength, Overlay, RegExpExtract, 
RegExpExtractAll, RegExpInStr, RegExpReplace, Right, RLike, SoundEx, 
StringLocate, StringLPad, StringRepeat, StringReplace, StringRPad, StringSplit, 
StringTranslate, Substring, SubstringIndex, [...]
 import org.apache.spark.sql.types.{BinaryType, DataTypes, LongType, StringType}
 import org.apache.spark.unsafe.types.UTF8String
 
@@ -761,3 +761,7 @@ object CometToNumber extends CometCodegenDispatch[ToNumber]
 object CometTryToNumber extends CometCodegenDispatch[TryToNumber]
 
 object CometMask extends CometCodegenDispatch[Mask]
+
+// A internal function that converts the empty string to null for partition 
values.
+// This function should be only used in V1Writes.
+object CometEmpty2Null extends CometCodegenDispatch[Empty2Null]
diff --git 
a/spark/src/test/scala/org/apache/comet/parquet/CometParquetWriterSuite.scala 
b/spark/src/test/scala/org/apache/comet/parquet/CometParquetWriterSuite.scala
index f6795b91a3..0a33a50450 100644
--- 
a/spark/src/test/scala/org/apache/comet/parquet/CometParquetWriterSuite.scala
+++ 
b/spark/src/test/scala/org/apache/comet/parquet/CometParquetWriterSuite.scala
@@ -21,8 +21,9 @@ package org.apache.comet.parquet
 
 import java.io.File
 
-import scala.util.Random
+import scala.util.{Random, Using}
 
+import org.apache.hadoop.fs.{FileSystem, Path}
 import org.apache.spark.sql.{CometTestBase, DataFrame, Row}
 import org.apache.spark.sql.comet.{CometBatchScanExec, CometNativeScanExec, 
CometNativeWriteExec, CometScanExec}
 import org.apache.spark.sql.execution.{FileSourceScanExec, QueryExecution, 
SparkPlan}
@@ -37,6 +38,27 @@ class CometParquetWriterSuite extends CometTestBase {
 
   import testImplicits._
 
+  test("partitioned write with empty string partition value") {
+    withTempPath { path =>
+      Seq(("", 1), ("a", 2))
+        .toDF("part", "value")
+        .write
+        .partitionBy("part")
+        .parquet(path.toString)
+      Using(FileSystem.get(spark.sparkContext.hadoopConfiguration)) { fs =>
+        val partitions = fs
+          .listStatus(new Path(path.toString))
+          .filter(_.isDirectory)
+          .map(_.getPath.getName)
+          .sorted
+        assert(partitions.contains("part=a"))
+        assert(!partitions.contains("part="))
+        
assert(partitions.count(_.startsWith("part=__HIVE_DEFAULT_PARTITION__")) == 1)
+      }
+      checkAnswer(spark.read.parquet(path.toString), Row(1, null) :: Row(2, 
"a") :: Nil)
+    }
+  }
+
   test("basic parquet write") {
     withTempPath { dir =>
       val outputPath = new File(dir, "output.parquet").getAbsolutePath
diff --git 
a/spark/src/test/spark-3.5/org/apache/spark/sql/CometSparkInternalFunctionsSuite.scala
 
b/spark/src/test/spark-3.5/org/apache/spark/sql/CometSparkInternalFunctionsSuite.scala
new file mode 100644
index 0000000000..6f64574739
--- /dev/null
+++ 
b/spark/src/test/spark-3.5/org/apache/spark/sql/CometSparkInternalFunctionsSuite.scala
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+import org.apache.spark.sql.catalyst.expressions.Empty2Null
+import org.apache.spark.sql.functions.col
+
+class CometSparkInternalFunctionsSuite extends CometTestBase {
+
+  test("empty2null is offloaded to Comet") {
+    withParquetTable(Seq("", "a", null, "b").map(Tuple1(_)), "tbl") {
+      val df = spark
+        .sql("select _1 from tbl")
+        .select(new Column(Empty2Null(col("_1").expr)).as("p"))
+      checkSparkAnswerAndOperator(df)
+    }
+  }
+}
diff --git 
a/spark/src/test/spark-4.x/org/apache/spark/sql/CometSparkInternalFunctionsSuite.scala
 
b/spark/src/test/spark-4.x/org/apache/spark/sql/CometSparkInternalFunctionsSuite.scala
new file mode 100644
index 0000000000..b8b107f2e8
--- /dev/null
+++ 
b/spark/src/test/spark-4.x/org/apache/spark/sql/CometSparkInternalFunctionsSuite.scala
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+import org.apache.spark.sql.catalyst.expressions.Empty2Null
+import org.apache.spark.sql.classic.ExpressionUtils
+import org.apache.spark.sql.functions._
+
+class CometSparkInternalFunctionsSuite extends CometTestBase {
+
+  test("empty2null is offloaded to Comet") {
+    withParquetTable(Seq("", "a", null, "b").map(Tuple1(_)), "tbl") {
+      val df = sql("select _1 from tbl")
+        
.select(ExpressionUtils.column(Empty2Null(ExpressionUtils.expression(col("_1")))).as("p"))
+      checkSparkAnswerAndOperator(df)
+    }
+  }
+}


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

Reply via email to