This is an automated email from the ASF dual-hosted git repository.
jackylee-ch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 0376299642 [GLUTEN-13201][CORE] Fall back on RANGE frame bounds that
cannot be offloaded (#13202)
0376299642 is described below
commit 03762996427315e5217be5976a40dd8ac3a2d731
Author: YangJie <[email protected]>
AuthorDate: Sun Oct 11 22:37:56 2026 -0400
[GLUTEN-13201][CORE] Fall back on RANGE frame bounds that cannot be
offloaded (#13202)
---
.../gluten/backendsapi/bolt/BoltBackend.scala | 1 +
.../execution/BoltWindowExpressionSuite.scala | 37 +++++++
.../gluten/backendsapi/clickhouse/CHBackend.scala | 9 ++
.../GlutenClickHouseTPCHSaltNullParquetSuite.scala | 24 +++++
.../gluten/backendsapi/velox/VeloxBackend.scala | 1 +
.../execution/VeloxWindowExpressionSuite.scala | 37 +++++++
.../substrait/expression/WindowFunctionNode.java | 22 ++++-
.../gluten/expression/WindowFunctionsBuilder.scala | 22 +++++
.../apache/gluten/utils/PullOutProjectHelper.scala | 36 ++++---
.../expression/WindowFunctionNodeTest.java | 109 +++++++++++++++++++++
10 files changed, 282 insertions(+), 16 deletions(-)
diff --git
a/backends-bolt/src/main/scala/org/apache/gluten/backendsapi/bolt/BoltBackend.scala
b/backends-bolt/src/main/scala/org/apache/gluten/backendsapi/bolt/BoltBackend.scala
index 70d6ada972..e0654764ed 100644
---
a/backends-bolt/src/main/scala/org/apache/gluten/backendsapi/bolt/BoltBackend.scala
+++
b/backends-bolt/src/main/scala/org/apache/gluten/backendsapi/bolt/BoltBackend.scala
@@ -462,6 +462,7 @@ object BoltBackendSettings extends BackendSettingsApi {
"Only integral type & date type are" +
" supported for sort key when literal bound type
is used!")
})
+ WindowFunctionsBuilder.checkRangeFrameLiteralBound(e)
case _ =>
}
}
diff --git
a/backends-bolt/src/test/scala/org/apache/gluten/execution/BoltWindowExpressionSuite.scala
b/backends-bolt/src/test/scala/org/apache/gluten/execution/BoltWindowExpressionSuite.scala
index f0cadada76..ced3136297 100644
---
a/backends-bolt/src/test/scala/org/apache/gluten/execution/BoltWindowExpressionSuite.scala
+++
b/backends-bolt/src/test/scala/org/apache/gluten/execution/BoltWindowExpressionSuite.scala
@@ -20,6 +20,7 @@ import org.apache.gluten.config.BoltConfig
import org.apache.spark.SparkConf
import org.apache.spark.sql.Row
+import org.apache.spark.sql.execution.window.WindowExec
import org.apache.spark.sql.types._
class BoltWindowExpressionSuite extends WholeStageTransformerSuite {
@@ -171,4 +172,40 @@ class BoltWindowExpressionSuite extends
WholeStageTransformerSuite {
}
}
}
+
+ testWithMinSparkVersion("range frame with a day interval bound on a date key
falls back", "4.0") {
+ // Spark 4.0 also accepts a day-time interval bound on a date key.
+ runQueryAndCompare(
+ "select l_commitdate, count(*) over (order by l_commitdate " +
+ "RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND CURRENT ROW) from
lineitem",
+ noFallBack = false
+ ) {
+ checkSparkPlan[WindowExec]
+ }
+ }
+
+ test("range frame with an interval bound on a date key falls back") {
+ Seq(
+ "RANGE BETWEEN INTERVAL '1' MONTH PRECEDING AND CURRENT ROW",
+ "RANGE BETWEEN CURRENT ROW AND INTERVAL '1' YEAR FOLLOWING"
+ ).foreach {
+ frame =>
+ runQueryAndCompare(
+ s"select l_commitdate, count(*) over (order by l_commitdate $frame)
from lineitem",
+ noFallBack = false) {
+ checkSparkPlan[WindowExec]
+ }
+ }
+ // The integral bound triggers the pre-compute rewrite for the whole
operator; the interval
+ // bound in the same operator must still make it fall back.
+ runQueryAndCompare(
+ "select l_commitdate, " +
+ "count(*) over (order by l_commitdate RANGE BETWEEN 1 PRECEDING AND
CURRENT ROW), " +
+ "count(*) over (order by l_commitdate " +
+ "RANGE BETWEEN INTERVAL '1' MONTH PRECEDING AND CURRENT ROW) from
lineitem",
+ noFallBack = false
+ ) {
+ checkSparkPlan[WindowExec]
+ }
+ }
}
diff --git
a/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHBackend.scala
b/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHBackend.scala
index 395ed6fd17..7e14f44b0d 100644
---
a/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHBackend.scala
+++
b/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHBackend.scala
@@ -354,6 +354,15 @@ object CHBackendSettings extends BackendSettingsApi with
Logging {
allSupported = false
break
}
+
+ wExpression.windowSpec.frameSpecification match {
+ case swf: SpecifiedWindowFrame
+ if !Seq(swf.lower,
swf.upper).forall(WindowFunctionsBuilder.isLongOffset) =>
+ logDebug(s"Not support window frame with a non-long offset:
$swf")
+ allSupported = false
+ break
+ case _ =>
+ }
})
}
allSupported
diff --git
a/backends-clickhouse/src/test/scala/org/apache/gluten/execution/tpch/GlutenClickHouseTPCHSaltNullParquetSuite.scala
b/backends-clickhouse/src/test/scala/org/apache/gluten/execution/tpch/GlutenClickHouseTPCHSaltNullParquetSuite.scala
index 8c4e55311b..6ba47dc901 100644
---
a/backends-clickhouse/src/test/scala/org/apache/gluten/execution/tpch/GlutenClickHouseTPCHSaltNullParquetSuite.scala
+++
b/backends-clickhouse/src/test/scala/org/apache/gluten/execution/tpch/GlutenClickHouseTPCHSaltNullParquetSuite.scala
@@ -25,6 +25,7 @@ import org.apache.spark.{SparkConf, SparkEnv, SparkException}
import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.catalyst.optimizer.{BuildLeft, ConstantFolding,
NullPropagation}
import org.apache.spark.sql.execution.{ColumnarToRowExec, ReusedSubqueryExec,
SubqueryExec}
+import org.apache.spark.sql.execution.window.WindowExec
import org.apache.spark.sql.functions.{col, rand, when}
import org.apache.spark.sql.internal.SQLConf
@@ -642,6 +643,29 @@ class GlutenClickHouseTPCHSaltNullParquetSuite
compareResultsAgainstVanillaSpark(sql, true, { _ => })
}
+ test("window range with a non-integral bound falls back") {
+ // Spark casts the bound to the decimal key type, so both arrive as
non-integral literals. Check
+ // with and without native validation, which this suite turns off by
default.
+ Seq("true", "false").foreach {
+ nativeValidation =>
+ withSQLConf(GlutenConfig.NATIVE_VALIDATION_ENABLED.key ->
nativeValidation) {
+ Seq("1.5 preceding", "10 preceding").foreach {
+ bound =>
+ val sql =
+ s"""
+ |select n_nationkey, n_regionkey,
+ | count(*) over (partition by n_regionkey
+ | order by cast(n_nationkey as decimal(10, 2))
+ | range between $bound and current row) as cnt
+ |from nation
+ |order by n_regionkey, n_nationkey
+ |""".stripMargin
+ runQueryAndCompare(sql, noFallBack =
false)(checkSparkPlan[WindowExec])
+ }
+ }
+ }
+ }
+
test("windows") {
val sql =
"""
diff --git
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
index 12bf14d09f..5426471f37 100644
---
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
+++
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
@@ -448,6 +448,7 @@ object VeloxBackendSettings extends BackendSettingsApi {
"Only integral type & date type are" +
" supported for sort key when literal bound type
is used!")
})
+ WindowFunctionsBuilder.checkRangeFrameLiteralBound(e)
case _ =>
}
}
diff --git
a/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxWindowExpressionSuite.scala
b/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxWindowExpressionSuite.scala
index 9cd2e0f8d9..9e7cac7695 100644
---
a/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxWindowExpressionSuite.scala
+++
b/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxWindowExpressionSuite.scala
@@ -20,6 +20,7 @@ import org.apache.gluten.config.VeloxConfig
import org.apache.spark.SparkConf
import org.apache.spark.sql.Row
+import org.apache.spark.sql.execution.window.WindowExec
import org.apache.spark.sql.types._
class VeloxWindowExpressionSuite extends WholeStageTransformerSuite {
@@ -171,4 +172,40 @@ class VeloxWindowExpressionSuite extends
WholeStageTransformerSuite {
}
}
}
+
+ testWithMinSparkVersion("range frame with a day interval bound on a date key
falls back", "4.0") {
+ // Spark 4.0 also accepts a day-time interval bound on a date key.
+ runQueryAndCompare(
+ "select l_commitdate, count(*) over (order by l_commitdate " +
+ "RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND CURRENT ROW) from
lineitem",
+ noFallBack = false
+ ) {
+ checkSparkPlan[WindowExec]
+ }
+ }
+
+ test("range frame with an interval bound on a date key falls back") {
+ Seq(
+ "RANGE BETWEEN INTERVAL '1' MONTH PRECEDING AND CURRENT ROW",
+ "RANGE BETWEEN CURRENT ROW AND INTERVAL '1' YEAR FOLLOWING"
+ ).foreach {
+ frame =>
+ runQueryAndCompare(
+ s"select l_commitdate, count(*) over (order by l_commitdate $frame)
from lineitem",
+ noFallBack = false) {
+ checkSparkPlan[WindowExec]
+ }
+ }
+ // The integral bound triggers the pre-compute rewrite for the whole
operator; the interval
+ // bound in the same operator must still make it fall back.
+ runQueryAndCompare(
+ "select l_commitdate, " +
+ "count(*) over (order by l_commitdate RANGE BETWEEN 1 PRECEDING AND
CURRENT ROW), " +
+ "count(*) over (order by l_commitdate " +
+ "RANGE BETWEEN INTERVAL '1' MONTH PRECEDING AND CURRENT ROW) from
lineitem",
+ noFallBack = false
+ ) {
+ checkSparkPlan[WindowExec]
+ }
+ }
}
diff --git
a/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/WindowFunctionNode.java
b/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/WindowFunctionNode.java
index d4be6c6ba2..94a194cca1 100644
---
a/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/WindowFunctionNode.java
+++
b/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/WindowFunctionNode.java
@@ -17,6 +17,7 @@
package org.apache.gluten.substrait.expression;
import org.apache.gluten.exception.GlutenException;
+import org.apache.gluten.exception.GlutenNotSupportException;
import org.apache.gluten.expression.ExpressionConverter;
import org.apache.gluten.substrait.SubstraitContext;
import org.apache.gluten.substrait.type.TypeNode;
@@ -72,6 +73,23 @@ public class WindowFunctionNode implements Serializable {
this.originalInputAttributes = originalInputAttributes;
}
+ // A bound that does not parse as a long, such as the -1.50 of RANGE BETWEEN
1.5 PRECEDING on a
+ // decimal key, throws GlutenNotSupportException so the window falls back; a
+ // NumberFormatException would fail the query instead.
+ private static Long
parseOffset(org.apache.spark.sql.catalyst.expressions.Expression boundType) {
+ String raw = String.valueOf(boundType.eval(null));
+ try {
+ return Long.parseLong(raw);
+ } catch (NumberFormatException e) {
+ throw new GlutenNotSupportException(
+ "Window frame bound is not a long offset: "
+ + raw
+ + " ("
+ + boundType.dataType().simpleString()
+ + ")");
+ }
+ }
+
private Expression.WindowFunction.Bound.Builder setBound(
Expression.WindowFunction.Bound.Builder builder,
org.apache.spark.sql.catalyst.expressions.Expression boundType) {
@@ -102,7 +120,7 @@ public class WindowFunctionNode implements Serializable {
.asScala()
.toSeq())
.doTransform(new SubstraitContext());
- Long offset = Long.valueOf(boundType.eval(null).toString());
+ Long offset = parseOffset(boundType);
if (offset < 0) {
Expression.WindowFunction.Bound.Preceding.Builder
refPrecedingBuilder =
Expression.WindowFunction.Bound.Preceding.newBuilder();
@@ -118,7 +136,7 @@ public class WindowFunctionNode implements Serializable {
// Used when
// 1. Velox backend and frame type is ROW
// 2. Clickhouse backend
- Long offset = Long.valueOf(boundType.eval(null).toString());
+ Long offset = parseOffset(boundType);
if (offset < 0) {
Expression.WindowFunction.Bound.Preceding.Builder
offsetPrecedingBuilder =
Expression.WindowFunction.Bound.Preceding.newBuilder();
diff --git
a/gluten-substrait/src/main/scala/org/apache/gluten/expression/WindowFunctionsBuilder.scala
b/gluten-substrait/src/main/scala/org/apache/gluten/expression/WindowFunctionsBuilder.scala
index 5873c29f20..0d601f5508 100644
---
a/gluten-substrait/src/main/scala/org/apache/gluten/expression/WindowFunctionsBuilder.scala
+++
b/gluten-substrait/src/main/scala/org/apache/gluten/expression/WindowFunctionsBuilder.scala
@@ -22,7 +22,10 @@ import org.apache.gluten.expression.ExpressionNames.{LAG,
LEAD}
import org.apache.gluten.substrait.SubstraitContext
import org.apache.spark.sql.catalyst.expressions.{EmptyRow, Expression, Lag,
Lead, WindowExpression, WindowFunction}
+import org.apache.spark.sql.catalyst.expressions.SpecialFrameBoundary
+import org.apache.spark.sql.types.{ByteType, IntegerType, LongType, ShortType}
+import scala.util.Try
import scala.util.control.Breaks.{break, breakable}
object WindowFunctionsBuilder {
@@ -63,4 +66,23 @@ object WindowFunctionsBuilder {
w
}
}
+
+ // Whether WindowFunctionNode can convert the frame bound: a special or
non-foldable bound, or
+ // a literal that parses as the long offset it sends to the native engine.
+ def isLongOffset(bound: Expression): Boolean = bound match {
+ case _: SpecialFrameBoundary => true
+ case b if !b.foldable => true
+ case b =>
Try(java.lang.Long.parseLong(String.valueOf(b.eval(EmptyRow)))).isSuccess
+ }
+
+ // A literal RANGE frame bound is offloaded as a pre-computed boundary
column, which only works
+ // for an integral bound that converts to an offset; any other literal bound
makes the window
+ // fall back.
+ def checkRangeFrameLiteralBound(bound: Expression): Unit = bound.dataType
match {
+ case ByteType | ShortType | IntegerType | LongType if isLongOffset(bound)
=>
+ case other =>
+ throw new GlutenNotSupportException(
+ s"Only integral literal bounds are supported for a RANGE frame, got
${bound.sql}: " +
+ other.simpleString)
+ }
}
diff --git
a/gluten-substrait/src/main/scala/org/apache/gluten/utils/PullOutProjectHelper.scala
b/gluten-substrait/src/main/scala/org/apache/gluten/utils/PullOutProjectHelper.scala
index a4f8d2cbf4..d2c5486611 100644
---
a/gluten-substrait/src/main/scala/org/apache/gluten/utils/PullOutProjectHelper.scala
+++
b/gluten-substrait/src/main/scala/org/apache/gluten/utils/PullOutProjectHelper.scala
@@ -23,7 +23,7 @@ import org.apache.spark.sql.catalyst.expressions._
import
org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression,
AggregateFunction, Complete, Partial}
import org.apache.spark.sql.execution.aggregate._
import org.apache.spark.sql.execution.window.WindowExec
-import org.apache.spark.sql.types.{ByteType, DateType, IntegerType, LongType,
ShortType}
+import org.apache.spark.sql.types.{ByteType, DataType, DateType, IntegerType,
LongType, ShortType}
import java.sql.Date
import java.util.concurrent.atomic.AtomicInteger
@@ -194,10 +194,8 @@ trait PullOutProjectHelper {
w.windowExpression.exists(_.find {
case we: WindowExpression =>
we.windowSpec.frameSpecification match {
- case swf: SpecifiedWindowFrame
- if needPreComputeRangeFrame(swf) && supportPreComputeRangeFrame(
- we.windowSpec.orderSpec) =>
- true
+ case swf: SpecifiedWindowFrame =>
+ canPreComputeRangeFrame(swf, we.windowSpec.orderSpec)
case _ => false
}
case _ => false
@@ -209,16 +207,26 @@ trait PullOutProjectHelper {
(needPreComputeRangeFrameBoundary(swf.lower) ||
needPreComputeRangeFrameBoundary(swf.upper))
}
- protected def supportPreComputeRangeFrame(sortOrders: Seq[SortOrder]):
Boolean = {
- sortOrders.forall {
- _.dataType match {
- case ByteType | ShortType | IntegerType | LongType | DateType => true
- // Only integral type & date type are supported for sort key with
Range Frame
- case _ => false
- }
- }
+ // The pre-computed boundary is the order key plus the bound, so the bound
has to be of an
+ // integral type too. A date key with an interval bound, for one, stays as
it is and falls back.
+ private def canPreComputeRangeFrame(
+ swf: SpecifiedWindowFrame,
+ sortOrders: Seq[SortOrder]): Boolean = {
+ def integralBound(bound: Expression): Boolean =
+ !needPreComputeRangeFrameBoundary(bound) ||
isIntegralType(bound.dataType)
+ needPreComputeRangeFrame(swf) && supportPreComputeRangeFrame(sortOrders) &&
+ integralBound(swf.lower) && integralBound(swf.upper)
+ }
+
+ private def isIntegralType(dataType: DataType): Boolean = dataType match {
+ case ByteType | ShortType | IntegerType | LongType => true
+ case _ => false
}
+ // Only integral and date sort keys are supported for a range frame.
+ protected def supportPreComputeRangeFrame(sortOrders: Seq[SortOrder]):
Boolean =
+ sortOrders.forall(o => isIntegralType(o.dataType) || o.dataType ==
DateType)
+
/**
* Convert DateType to IntType for orderSpec if needPreComputeRangeFrame,
because spark's frame
* type does not support DateType. It does not affect the correctness of
sort.
@@ -265,7 +273,7 @@ trait PullOutProjectHelper {
}
val newWindowSpec = we.windowSpec.frameSpecification match {
- case swf: SpecifiedWindowFrame if needPreComputeRangeFrame(swf) =>
+ case swf: SpecifiedWindowFrame if canPreComputeRangeFrame(swf,
orderSpecs) =>
val orderSpec = orderSpecs.head
val lowerFrameCol = preComputeRangeFrameBoundary(swf.lower, orderSpec,
expressionMap)
val upperFrameCol = preComputeRangeFrameBoundary(swf.upper, orderSpec,
expressionMap)
diff --git
a/gluten-substrait/src/test/java/org/apache/gluten/substrait/expression/WindowFunctionNodeTest.java
b/gluten-substrait/src/test/java/org/apache/gluten/substrait/expression/WindowFunctionNodeTest.java
new file mode 100644
index 0000000000..7ff5064cdc
--- /dev/null
+++
b/gluten-substrait/src/test/java/org/apache/gluten/substrait/expression/WindowFunctionNodeTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.gluten.substrait.expression;
+
+import org.apache.gluten.exception.GlutenNotSupportException;
+import org.apache.gluten.expression.WindowFunctionsBuilder;
+import org.apache.gluten.substrait.type.TypeBuilder;
+
+import org.apache.spark.sql.catalyst.expressions.CurrentRow$;
+import org.apache.spark.sql.catalyst.expressions.Literal;
+import org.apache.spark.sql.types.DataTypes;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class WindowFunctionNodeTest {
+
+ private WindowFunctionNode nodeWithLowerBound(Literal lowerBound) {
+ return new WindowFunctionNode(
+ 0,
+ Collections.emptyList(),
+ "w",
+ TypeBuilder.makeFP64(false),
+ Literal.create(1L, DataTypes.LongType),
+ lowerBound,
+ "RANGE",
+ false,
+ Collections.emptyList());
+ }
+
+ @Test
+ public void nonIntegralBoundFailsValidation() {
+ // Spark casts a RANGE bound to the order key type, so on a decimal key
even 10 PRECEDING
+ // arrives as -10.00.
+ Literal[] bounds = {
+ Literal.create(new BigDecimal("-1.50"), DataTypes.createDecimalType(10,
2)),
+ Literal.create(new BigDecimal("-10.00"), DataTypes.createDecimalType(10,
2)),
+ Literal.create(-0.5d, DataTypes.DoubleType)
+ };
+ for (Literal bound : bounds) {
+ WindowFunctionNode node = nodeWithLowerBound(bound);
+ GlutenNotSupportException e =
assertThrows(GlutenNotSupportException.class, node::toProtobuf);
+ String expected = bound.value() + " (" + bound.dataType().simpleString()
+ ")";
+ assertTrue(e.getMessage(), e.getMessage().endsWith(expected));
+ }
+ }
+
+ @Test
+ public void nullBoundFailsValidation() {
+ WindowFunctionNode node = nodeWithLowerBound(Literal.create(null,
DataTypes.LongType));
+ GlutenNotSupportException e =
assertThrows(GlutenNotSupportException.class, node::toProtobuf);
+ assertTrue(e.getMessage(), e.getMessage().endsWith("null (bigint)"));
+ }
+
+ @Test
+ public void longOffsetCheckMatchesConversion() {
+ assertFalse(
+ WindowFunctionsBuilder.isLongOffset(
+ Literal.create(new BigDecimal("-10.00"),
DataTypes.createDecimalType(10, 2))));
+ assertFalse(WindowFunctionsBuilder.isLongOffset(Literal.create(null,
DataTypes.LongType)));
+ assertTrue(
+ WindowFunctionsBuilder.isLongOffset(
+ Literal.create(new BigDecimal("10"),
DataTypes.createDecimalType(10, 0))));
+ assertTrue(WindowFunctionsBuilder.isLongOffset(Literal.create(-3L,
DataTypes.LongType)));
+ assertTrue(WindowFunctionsBuilder.isLongOffset(CurrentRow$.MODULE$));
+ }
+
+ @Test
+ public void rangeLiteralBoundCheckRejectsWhatCannotConvert() {
+ // The Velox and Bolt check accepts an integral bound only if it converts,
so a null one falls
+ // back even with native validation off.
+ assertThrows(
+ GlutenNotSupportException.class,
+ () ->
+ WindowFunctionsBuilder.checkRangeFrameLiteralBound(
+ Literal.create(null, DataTypes.LongType)));
+ assertThrows(
+ GlutenNotSupportException.class,
+ () ->
+ WindowFunctionsBuilder.checkRangeFrameLiteralBound(
+ Literal.create(new BigDecimal("-10"),
DataTypes.createDecimalType(10, 0))));
+ WindowFunctionsBuilder.checkRangeFrameLiteralBound(Literal.create(-3L,
DataTypes.LongType));
+ }
+
+ @Test
+ public void integralBoundConverts() {
+ assertNotNull(nodeWithLowerBound(Literal.create(-3L,
DataTypes.LongType)).toProtobuf());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]