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

huaxingao 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 c9af3f41 feat: Support sum in window function (#802)
c9af3f41 is described below

commit c9af3f4127ba9f8da8edbb5d6e196fd1359d4f9e
Author: Huaxin Gao <[email protected]>
AuthorDate: Sat Aug 17 10:10:13 2024 -0700

    feat: Support sum in window function (#802)
    
    * feat: Support sum in window function
    
    * remove extra line
    
    * remove unused import
    
    * Trigger Build
    
    * Trigger Build
    
    * address comments
---
 native/core/src/execution/datafusion/planner.rs    | 67 ++++++++--------
 .../org/apache/comet/serde/QueryPlanSerde.scala    | 29 ++++++-
 .../apache/comet/exec/CometAggregateSuite.scala    | 88 ----------------------
 .../org/apache/comet/exec/CometExecSuite.scala     | 60 ++++++++++++++-
 .../scala/org/apache/spark/sql/CometTestBase.scala | 87 +++++++++++++++++++++
 5 files changed, 210 insertions(+), 121 deletions(-)

diff --git a/native/core/src/execution/datafusion/planner.rs 
b/native/core/src/execution/datafusion/planner.rs
index 24ea4142..fe6ef9f7 100644
--- a/native/core/src/execution/datafusion/planner.rs
+++ b/native/core/src/execution/datafusion/planner.rs
@@ -1538,12 +1538,17 @@ impl PhysicalPlanner {
         partition_by: &[Arc<dyn PhysicalExpr>],
         sort_exprs: &[PhysicalSortExpr],
     ) -> Result<Arc<dyn WindowExpr>, ExecutionError> {
-        let (mut window_func_name, mut window_func_args) = (String::new(), 
Vec::new());
+        let window_func_name: String;
+        let window_args: Vec<Arc<dyn PhysicalExpr>>;
         if let Some(func) = &spark_expr.built_in_window_function {
             match &func.expr_struct {
                 Some(ExprStruct::ScalarFunc(f)) => {
-                    window_func_name.clone_from(&f.func);
-                    window_func_args.clone_from(&f.args);
+                    window_func_name = f.func.clone();
+                    window_args = f
+                        .args
+                        .iter()
+                        .map(|expr| self.create_expr(expr, 
input_schema.clone()))
+                        .collect::<Result<Vec<_>, ExecutionError>>()?;
                 }
                 other => {
                     return Err(ExecutionError::GeneralError(format!(
@@ -1552,9 +1557,9 @@ impl PhysicalPlanner {
                 }
             };
         } else if let Some(agg_func) = &spark_expr.agg_func {
-            let result = Self::process_agg_func(agg_func)?;
+            let result = self.process_agg_func(agg_func, 
input_schema.clone())?;
             window_func_name = result.0;
-            window_func_args = result.1;
+            window_args = result.1;
         } else {
             return Err(ExecutionError::GeneralError(
                 "Both func and agg_func are not set".to_string(),
@@ -1570,11 +1575,6 @@ impl PhysicalPlanner {
             }
         };
 
-        let window_args = window_func_args
-            .iter()
-            .map(|expr| self.create_expr(expr, input_schema.clone()))
-            .collect::<Result<Vec<_>, ExecutionError>>()?;
-
         let spark_window_frame = match spark_expr
             .spec
             .as_ref()
@@ -1644,32 +1644,39 @@ impl PhysicalPlanner {
         .map_err(|e| ExecutionError::DataFusionError(e.to_string()))
     }
 
-    fn process_agg_func(agg_func: &AggExpr) -> Result<(String, Vec<Expr>), 
ExecutionError> {
-        fn optional_expr_to_vec(expr_option: &Option<Expr>) -> Vec<Expr> {
-            expr_option
-                .as_ref()
-                .cloned()
-                .map_or_else(Vec::new, |e| vec![e])
-        }
-
-        fn int_to_stats_type(value: i32) -> Option<StatsType> {
-            match value {
-                0 => Some(StatsType::Sample),
-                1 => Some(StatsType::Population),
-                _ => None,
-            }
-        }
-
+    fn process_agg_func(
+        &self,
+        agg_func: &AggExpr,
+        schema: SchemaRef,
+    ) -> Result<(String, Vec<Arc<dyn PhysicalExpr>>), ExecutionError> {
         match &agg_func.expr_struct {
             Some(AggExprStruct::Count(expr)) => {
-                let args = &expr.children;
-                Ok(("count".to_string(), args.to_vec()))
+                let children = expr
+                    .children
+                    .iter()
+                    .map(|child| self.create_expr(child, schema.clone()))
+                    .collect::<Result<Vec<_>, _>>()?;
+                Ok(("count".to_string(), children))
             }
             Some(AggExprStruct::Min(expr)) => {
-                Ok(("min".to_string(), optional_expr_to_vec(&expr.child)))
+                let child = self.create_expr(expr.child.as_ref().unwrap(), 
schema.clone())?;
+                Ok(("min".to_string(), vec![child]))
             }
             Some(AggExprStruct::Max(expr)) => {
-                Ok(("max".to_string(), optional_expr_to_vec(&expr.child)))
+                let child = self.create_expr(expr.child.as_ref().unwrap(), 
schema.clone())?;
+                Ok(("max".to_string(), vec![child]))
+            }
+            Some(AggExprStruct::Sum(expr)) => {
+                let child = self.create_expr(expr.child.as_ref().unwrap(), 
schema.clone())?;
+                let arrow_type = 
to_arrow_datatype(expr.datatype.as_ref().unwrap());
+                let datatype = child.data_type(&schema)?;
+
+                let child = if datatype != arrow_type {
+                    Arc::new(CastExpr::new(child, arrow_type.clone(), None))
+                } else {
+                    child
+                };
+                Ok(("sum".to_string(), vec![child]))
             }
             other => Err(ExecutionError::GeneralError(format!(
                 "{other:?} not supported for window function"
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 ca17f6f0..caeda516 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -211,10 +211,35 @@ object QueryPlanSerde extends Logging with 
ShimQueryPlanSerde with CometExprShim
       expr match {
         case agg: AggregateExpression =>
           agg.aggregateFunction match {
-            case _: Min | _: Max | _: Count =>
+            case _: Count =>
               Some(agg)
+            case min: Min =>
+              if (minMaxDataTypeSupported(min.dataType)) {
+                Some(agg)
+              } else {
+                withInfo(windowExpr, s"datatype ${min.dataType} is not 
supported", expr)
+                None
+              }
+            case max: Max =>
+              if (minMaxDataTypeSupported(max.dataType)) {
+                Some(agg)
+              } else {
+                withInfo(windowExpr, s"datatype ${max.dataType} is not 
supported", expr)
+                None
+              }
+            case s: Sum =>
+              if (sumDataTypeSupported(s.dataType) && 
!s.dataType.isInstanceOf[DecimalType]) {
+                Some(agg)
+              } else {
+                withInfo(windowExpr, s"datatype ${s.dataType} is not 
supported", expr)
+                None
+              }
             case _ =>
-              withInfo(windowExpr, "Unsupported aggregate", expr)
+              withInfo(
+                windowExpr,
+                s"aggregate ${agg.aggregateFunction}" +
+                  " is not supported for window function",
+                expr)
               None
           }
         case _ =>
diff --git 
a/spark/src/test/scala/org/apache/comet/exec/CometAggregateSuite.scala 
b/spark/src/test/scala/org/apache/comet/exec/CometAggregateSuite.scala
index 2acc562b..5467bd44 100644
--- a/spark/src/test/scala/org/apache/comet/exec/CometAggregateSuite.scala
+++ b/spark/src/test/scala/org/apache/comet/exec/CometAggregateSuite.scala
@@ -22,8 +22,6 @@ package org.apache.comet.exec
 import scala.util.Random
 
 import org.apache.hadoop.fs.Path
-import org.apache.parquet.example.data.simple.SimpleGroup
-import org.apache.parquet.schema.MessageTypeParser
 import org.apache.spark.sql.{CometTestBase, DataFrame, Row}
 import org.apache.spark.sql.catalyst.optimizer.EliminateSorts
 import org.apache.spark.sql.comet.CometHashAggregateExec
@@ -1295,90 +1293,4 @@ class CometAggregateSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
     sparkPlan.collect { case s: CometHashAggregateExec => s }.size
   }
 
-  def makeParquetFile(
-      path: Path,
-      total: Int,
-      numGroups: Int,
-      dictionaryEnabled: Boolean): Unit = {
-    val schemaStr =
-      """
-        |message root {
-        |  optional INT32                    _1(INT_8);
-        |  optional INT32                    _2(INT_16);
-        |  optional INT32                    _3;
-        |  optional INT64                    _4;
-        |  optional FLOAT                    _5;
-        |  optional DOUBLE                   _6;
-        |  optional INT32                    _7(DECIMAL(5, 2));
-        |  optional INT64                    _8(DECIMAL(18, 10));
-        |  optional FIXED_LEN_BYTE_ARRAY(16) _9(DECIMAL(38, 37));
-        |  optional INT64                    _10(TIMESTAMP(MILLIS,true));
-        |  optional INT64                    _11(TIMESTAMP(MICROS,true));
-        |  optional INT32                    _12(DATE);
-        |  optional INT32                    _g1(INT_8);
-        |  optional INT32                    _g2(INT_16);
-        |  optional INT32                    _g3;
-        |  optional INT64                    _g4;
-        |  optional FLOAT                    _g5;
-        |  optional DOUBLE                   _g6;
-        |  optional INT32                    _g7(DECIMAL(5, 2));
-        |  optional INT64                    _g8(DECIMAL(18, 10));
-        |  optional FIXED_LEN_BYTE_ARRAY(16) _g9(DECIMAL(38, 37));
-        |  optional INT64                    _g10(TIMESTAMP(MILLIS,true));
-        |  optional INT64                    _g11(TIMESTAMP(MICROS,true));
-        |  optional INT32                    _g12(DATE);
-        |  optional BINARY                   _g13(UTF8);
-        |  optional BINARY                   _g14;
-        |}
-      """.stripMargin
-
-    val schema = MessageTypeParser.parseMessageType(schemaStr)
-    val writer = createParquetWriter(schema, path, dictionaryEnabled = true)
-
-    val rand = scala.util.Random
-    val expected = (0 until total).map { i =>
-      // use a single value for the first page, to make sure dictionary 
encoding kicks in
-      if (rand.nextBoolean()) None
-      else {
-        if (dictionaryEnabled) Some(i % 10) else Some(i)
-      }
-    }
-
-    expected.foreach { opt =>
-      val record = new SimpleGroup(schema)
-      opt match {
-        case Some(i) =>
-          record.add(0, i.toByte)
-          record.add(1, i.toShort)
-          record.add(2, i)
-          record.add(3, i.toLong)
-          record.add(4, rand.nextFloat())
-          record.add(5, rand.nextDouble())
-          record.add(6, i)
-          record.add(7, i.toLong)
-          record.add(8, (i % 10).toString * 16)
-          record.add(9, i.toLong)
-          record.add(10, i.toLong)
-          record.add(11, i)
-          record.add(12, i.toByte % numGroups)
-          record.add(13, i.toShort % numGroups)
-          record.add(14, i % numGroups)
-          record.add(15, i.toLong % numGroups)
-          record.add(16, rand.nextFloat())
-          record.add(17, rand.nextDouble())
-          record.add(18, i)
-          record.add(19, i.toLong)
-          record.add(20, (i % 10).toString * 16)
-          record.add(21, i.toLong)
-          record.add(22, i.toLong)
-          record.add(23, i)
-          record.add(24, (i % 10).toString * 24)
-          record.add(25, (i % 10).toString * 36)
-        case _ =>
-      }
-      writer.write(record)
-    }
-
-    writer.close()
-  }
 }
diff --git a/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala 
b/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala
index 15864a33..ed9f2c34 100644
--- a/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala
+++ b/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala
@@ -1579,6 +1579,58 @@ class CometExecSuite extends CometTestBase {
     }
   }
 
+  test("aggregate window function for all types") {
+    val numValues = 2048
+
+    Seq(1, 100, numValues).foreach { numGroups =>
+      Seq(true, false).foreach { dictionaryEnabled =>
+        withTempPath { dir =>
+          val path = new Path(dir.toURI.toString, "test.parquet")
+          makeParquetFile(path, numValues, numGroups, dictionaryEnabled)
+          withParquetTable(path.toUri.toString, "tbl") {
+            Seq(128, numValues + 100).foreach { batchSize =>
+              withSQLConf(CometConf.COMET_BATCH_SIZE.key -> 
batchSize.toString) {
+                (1 to 11).foreach { col =>
+                  val aggregateFunctions =
+                    List(s"COUNT(_$col)", s"MAX(_$col)", s"MIN(_$col)", 
s"SUM(_$col)")
+                  aggregateFunctions.foreach { function =>
+                    val df1 = sql(s"SELECT $function OVER() FROM tbl")
+                    checkSparkAnswerWithTol(df1, 1e-6)
+
+                    val df2 = sql(s"SELECT $function OVER(order by _2) FROM 
tbl")
+                    checkSparkAnswerWithTol(df2, 1e-6)
+
+                    val df3 = sql(s"SELECT $function OVER(order by _2 desc) 
FROM tbl")
+                    checkSparkAnswerWithTol(df3, 1e-6)
+
+                    val df4 = sql(s"SELECT $function OVER(partition by _2 
order by _2) FROM tbl")
+                    checkSparkAnswerWithTol(df4, 1e-6)
+                  }
+                }
+
+                // SUM doesn't work for Date type. 
org.apache.spark.sql.AnalysisException will be thrown.
+                val aggregateFunctionsWithoutSum = List("COUNT(_12)", 
"MAX(_12)", "MIN(_12)")
+                aggregateFunctionsWithoutSum.foreach { function =>
+                  val df1 = sql(s"SELECT $function OVER() FROM tbl")
+                  checkSparkAnswerWithTol(df1, 1e-6)
+
+                  val df2 = sql(s"SELECT $function OVER(order by _2) FROM tbl")
+                  checkSparkAnswerWithTol(df2, 1e-6)
+
+                  val df3 = sql(s"SELECT $function OVER(order by _2 desc) FROM 
tbl")
+                  checkSparkAnswerWithTol(df3, 1e-6)
+
+                  val df4 = sql(s"SELECT $function OVER(partition by _2 order 
by _2) FROM tbl")
+                  checkSparkAnswerWithTol(df4, 1e-6)
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
   test("Windows support") {
     Seq("true", "false").foreach(aqeEnabled =>
       withSQLConf(
@@ -1586,7 +1638,13 @@ class CometExecSuite extends CometTestBase {
         SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> aqeEnabled) {
         withParquetTable((0 until 10).map(i => (i, 10 - i)), "t1") { // TODO: 
test nulls
           val aggregateFunctions =
-            List("COUNT(_1)", "COUNT(*)", "MAX(_1)", "MIN(_1)") // TODO: Test 
all the aggregates
+            List(
+              "COUNT(_1)",
+              "COUNT(*)",
+              "MAX(_1)",
+              "MIN(_1)",
+              "SUM(_1)"
+            ) // TODO: Test all the aggregates
 
           aggregateFunctions.foreach { function =>
             val queries = Seq(
diff --git a/spark/src/test/scala/org/apache/spark/sql/CometTestBase.scala 
b/spark/src/test/scala/org/apache/spark/sql/CometTestBase.scala
index a88f9f45..c445aae6 100644
--- a/spark/src/test/scala/org/apache/spark/sql/CometTestBase.scala
+++ b/spark/src/test/scala/org/apache/spark/sql/CometTestBase.scala
@@ -816,4 +816,91 @@ abstract class CometTestBase
       vertical: Boolean = false): String = {
     df.showString(_numRows, truncate, vertical)
   }
+
+  def makeParquetFile(
+      path: Path,
+      total: Int,
+      numGroups: Int,
+      dictionaryEnabled: Boolean): Unit = {
+    val schemaStr =
+      """
+        |message root {
+        |  optional INT32                    _1(INT_8);
+        |  optional INT32                    _2(INT_16);
+        |  optional INT32                    _3;
+        |  optional INT64                    _4;
+        |  optional FLOAT                    _5;
+        |  optional DOUBLE                   _6;
+        |  optional INT32                    _7(DECIMAL(5, 2));
+        |  optional INT64                    _8(DECIMAL(18, 10));
+        |  optional FIXED_LEN_BYTE_ARRAY(16) _9(DECIMAL(38, 37));
+        |  optional INT64                    _10(TIMESTAMP(MILLIS,true));
+        |  optional INT64                    _11(TIMESTAMP(MICROS,true));
+        |  optional INT32                    _12(DATE);
+        |  optional INT32                    _g1(INT_8);
+        |  optional INT32                    _g2(INT_16);
+        |  optional INT32                    _g3;
+        |  optional INT64                    _g4;
+        |  optional FLOAT                    _g5;
+        |  optional DOUBLE                   _g6;
+        |  optional INT32                    _g7(DECIMAL(5, 2));
+        |  optional INT64                    _g8(DECIMAL(18, 10));
+        |  optional FIXED_LEN_BYTE_ARRAY(16) _g9(DECIMAL(38, 37));
+        |  optional INT64                    _g10(TIMESTAMP(MILLIS,true));
+        |  optional INT64                    _g11(TIMESTAMP(MICROS,true));
+        |  optional INT32                    _g12(DATE);
+        |  optional BINARY                   _g13(UTF8);
+        |  optional BINARY                   _g14;
+        |}
+      """.stripMargin
+
+    val schema = MessageTypeParser.parseMessageType(schemaStr)
+    val writer = createParquetWriter(schema, path, dictionaryEnabled = true)
+
+    val rand = scala.util.Random
+    val expected = (0 until total).map { i =>
+      // use a single value for the first page, to make sure dictionary 
encoding kicks in
+      if (rand.nextBoolean()) None
+      else {
+        if (dictionaryEnabled) Some(i % 10) else Some(i)
+      }
+    }
+
+    expected.foreach { opt =>
+      val record = new SimpleGroup(schema)
+      opt match {
+        case Some(i) =>
+          record.add(0, i.toByte)
+          record.add(1, i.toShort)
+          record.add(2, i)
+          record.add(3, i.toLong)
+          record.add(4, rand.nextFloat())
+          record.add(5, rand.nextDouble())
+          record.add(6, i)
+          record.add(7, i.toLong)
+          record.add(8, (i % 10).toString * 16)
+          record.add(9, i.toLong)
+          record.add(10, i.toLong)
+          record.add(11, i)
+          record.add(12, i.toByte % numGroups)
+          record.add(13, i.toShort % numGroups)
+          record.add(14, i % numGroups)
+          record.add(15, i.toLong % numGroups)
+          record.add(16, rand.nextFloat())
+          record.add(17, rand.nextDouble())
+          record.add(18, i)
+          record.add(19, i.toLong)
+          record.add(20, (i % 10).toString * 16)
+          record.add(21, i.toLong)
+          record.add(22, i.toLong)
+          record.add(23, i)
+          record.add(24, (i % 10).toString * 24)
+          record.add(25, (i % 10).toString * 36)
+        case _ =>
+      }
+      writer.write(record)
+    }
+
+    writer.close()
+  }
 }


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

Reply via email to