cloud-fan commented on code in PR #59094:
URL: https://github.com/apache/spark/pull/59094#discussion_r4139328069


##########
sql/core/src/test/scala/org/apache/spark/sql/ApproxTopKSuite.scala:
##########
@@ -549,6 +549,71 @@ class ApproxTopKSuite extends SharedSparkSession {
     )
   }
 
+
+  private val nullSketchState =
+    """CAST(NULL AS STRUCT<sketch: BINARY, maxItemsTracked: INT,
+      |  itemDataType: INT, itemDataTypeDDL: STRING>)""".stripMargin
+
+  test("SPARK-59818: estimate of a foldable NULL state returns NULL") {
+    checkAnswer(sql(s"SELECT approx_top_k_estimate($nullSketchState, 5)"), 
Row(null))
+  }
+
+  test("SPARK-59818: estimate of a non-foldable NULL state returns NULL") {
+    withTempView("estimate_null_state") {
+      sql(
+        s"""SELECT approx_top_k_accumulate(expr) AS state
+           |FROM VALUES 0, 1, 1 AS tab(expr)
+           |UNION ALL
+           |SELECT $nullSketchState AS state""".stripMargin)
+        .createOrReplaceTempView("estimate_null_state")
+      val res = sql("SELECT approx_top_k_estimate(state, 2) FROM 
estimate_null_state")
+      checkAnswer(res, Seq(Row(Seq(Row(1, 2), Row(0, 1))), Row(null)))
+    }
+  }
+
+  test("SPARK-59818: estimate nullability follows the nullability of its 
state") {
+    withTempView("estimate_nullable_state") {
+      sql(s"SELECT $nullSketchState AS state")
+        .createOrReplaceTempView("estimate_nullable_state")
+      val nullableRes =
+        sql("SELECT approx_top_k_estimate(state, 2) FROM 
estimate_nullable_state")
+      assert(nullableRes.schema.fields.head.nullable)
+    }
+    // The common case: a sketch produced by approx_top_k_accumulate is 
non-nullable, so the
+    // estimate over it must stay non-nullable.
+    val nonNullableRes = sql(
+      """SELECT approx_top_k_estimate(approx_top_k_accumulate(expr), 2)
+        |FROM VALUES 0, 1, 1 AS tab(expr)""".stripMargin)
+    assert(!nonNullableRes.schema.fields.head.nullable)
+  }
+
+  test("SPARK-59818: estimate rejects a non-positive k even for a NULL state") 
{
+    Seq(0, -1).foreach { invalidK =>
+      checkError(
+        exception = intercept[SparkRuntimeException] {
+          sql(s"SELECT approx_top_k_estimate($nullSketchState, 
$invalidK)").collect()
+        },
+        condition = "APPROX_TOP_K_NON_POSITIVE_ARG",
+        parameters = Map("argName" -> "`k`", "argValue" -> invalidK.toString)
+      )
+    }
+    withTempView("estimate_invalid_k") {
+      sql(
+        s"""SELECT approx_top_k_accumulate(expr) AS state

Review Comment:
   **Nit (P3):** This fixture includes a populated sketch that can raise 
APPROX_TOP_K_NON_POSITIVE_ARG on its own. If k validation moves behind the 
NULL-state return again, collect() can therefore satisfy this assertion without 
proving that the row-evaluated NULL state rejects k = 0, so the claimed runtime 
NULL-path regression coverage can remain green.
   
   **Verification:**
   - **Regression:** Exercise a non-positive k against a row-evaluated, 
non-foldable NULL sketch state with no alternate row able to throw the expected 
validation error.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/ApproxTopKAggregates.scala:
##########
@@ -905,6 +905,10 @@ case class ApproxTopKCombine(
    */
   override def update(buffer: CombineInternal[Any], input: InternalRow): 
CombineInternal[Any] = {
     val inputState = state.eval(input).asInstanceOf[InternalRow]
+    if (inputState == null) {
+      // A NULL sketch carries no items, so it contributes nothing to the 
combined sketch.
+      return buffer

Review Comment:
   **Non-blocking (P2):** Returning the untouched buffer is not type-neutral 
once this NULL row is the only input to a partial aggregate: serialization 
fills the unset itemDataType from the coercion-widened state schema, and final 
merge can reject it against the real sketch's DDL type. That makes a supported 
combine over the same NULL and non-NULL states fail with 
APPROX_TOP_K_SKETCH_TYPE_NOT_MATCH solely because those rows landed in 
different partial aggregates.
   
   **Verification:**
   - **Regression:** Combine a real sketch with a coercion-widened NULL state 
isolated in a different partial aggregate and verify the result is independent 
of partition placement.
   - **Compatibility:** Verify that empty or NULL-only final results retain 
valid concrete state metadata while incompatible non-NULL sketch types remain 
rejected.



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