Copilot commented on code in PR #13050:
URL: https://github.com/apache/gluten/pull/13050#discussion_r4231586947


##########
backends-velox/src/test/scala/org/apache/gluten/functions/WindowFunctionsValidateSuite.scala:
##########
@@ -92,4 +105,118 @@ class WindowFunctionsValidateSuite extends 
FunctionsValidateSuite {
       }
     }
   }
+
+  test("window partition/order by TIMESTAMP_NTZ goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+
+      runQueryAndCompare(
+        "select k, ts, rank() over (partition by k order by ts) as rnk " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("lag(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, lag(ts, 1) over (partition by k order by v) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }

Review Comment:
   PR description says: “Not covered: window functions taking TIMESTAMP_NTZ 
directly (lag, lead, first_value, etc.) still fall back.” However, this PR adds 
tests asserting these now go native (and the validator whitelists them). Please 
align the PR description with the implemented behavior (or, if the intent is 
still to fallback for NTZ-typed window-function arguments, update the 
validator/tests accordingly).



##########
backends-velox/src/test/scala/org/apache/gluten/functions/WindowFunctionsValidateSuite.scala:
##########
@@ -92,4 +105,118 @@ class WindowFunctionsValidateSuite extends 
FunctionsValidateSuite {
       }
     }
   }
+
+  test("window partition/order by TIMESTAMP_NTZ goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+
+      runQueryAndCompare(
+        "select k, ts, rank() over (partition by k order by ts) as rnk " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("lag(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, lag(ts, 1) over (partition by k order by v) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("lead(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, lead(ts, 1) over (partition by k order by v) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("nth_value(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, nth_value(ts, 1) over (partition by k order by v) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("first_value(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, first_value(ts) over (partition by k order by v) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("last_value(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, last_value(ts) over (partition by k order by v) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("count(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, count(ts) over (partition by k) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("min(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, min(ts) over (partition by k) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("max(TIMESTAMP_NTZ) window function goes native") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+      runQueryAndCompare(
+        "select k, max(ts) over (partition by k) " +
+          "from ntz_window_data") {
+        checkGlutenPlan[WindowExecTransformer]
+      }
+    }
+  }
+
+  test("unaudited window function argument with TIMESTAMP_NTZ falls back") {
+    withTable("ntz_window_data") {
+      createNtzWindowTable()
+
+      val df = spark.sql(
+        "select k, collect_list(ts) over (partition by k order by v) " +
+          "from ntz_window_data")
+      // This plan never goes native at all, so the helper's count - 1 comes
+      // out negative. collect_list isn't whitelisted yet (unaudited for NTZ
+      // support), so this must always fall back.
+      checkFallbackOperators(df, -1)

Review Comment:
   Asserting `checkFallbackOperators(df, -1)` is brittle and encodes an 
internal implementation detail (“count - 1 goes negative”). This can easily 
break if the helper or plan shape changes. Prefer asserting fallback in a way 
that doesn’t rely on negative counts (e.g., assert no `WindowExecTransformer` 
appears in the plan, or add/extend a helper that explicitly verifies the query 
is handled by Spark for this operator).



##########
gluten-substrait/src/main/scala/org/apache/gluten/extension/columnar/validator/Validators.scala:
##########
@@ -299,6 +300,46 @@ object Validators {
                   case _ => false
                 }
             }
+          case w: WindowExec =>
+            w.windowExpression.forall {
+              expr =>
+                expr.collectFirst { case WindowExpression(fn, _) => fn } match 
{
+                  case Some(windowFunction) =>
+                    (!containsNTZ(windowFunction.dataType) &&
+                      !windowFunction.references.exists(a => 
containsNTZ(a.dataType))) ||
+                    windowFunction.exists {
+                      case Lag(input, _, _, _) => containsNTZ(input.dataType)
+                      case Lead(input, _, _, _) => containsNTZ(input.dataType)
+                      case NthValue(input, _, _) => containsNTZ(input.dataType)
+                      case First(child, _) => containsNTZ(child.dataType)
+                      case Last(child, _) => containsNTZ(child.dataType)
+                      case Count(children) => children.exists(c => 
containsNTZ(c.dataType))
+                      case Min(child) => containsNTZ(child.dataType)
+                      case Max(child) => containsNTZ(child.dataType)
+                      // RankLike's children mirror orderSpec; only ever 
compared, not computed.
+                      case _: RankLike => true
+                      case _ => false
+                    }
+                  case None => false
+                }
+            }

Review Comment:
   The new `WindowExec` NTZ validation only inspects `windowExpression` (the 
window function tree). It does not validate `WindowExec.partitionSpec` / 
`orderSpec` directly, which are the actual partition/order-by keys this PR aims 
to permit. If Spark produces a `WindowExec` without a preceding 
`SortExec`/`ShuffleExchangeExec` (e.g., input already satisfies 
distribution/order), this can allow native execution without checking the NTZ 
keys at all. Recommend adding explicit checks for `w.partitionSpec` and 
`w.orderSpec.map(_.child)` (similar to the `SortExec` / `ShuffleExchangeExec` 
rules: allow NTZ only for bare `Attribute` keys, otherwise fallback).



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