Repository navigation
[VL] Enable guarded decimal ROUND with cross-version coverage #13103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
d66fbf9
1f5c19a
28d77cb
80a1019
df9c01f
d8b0c39
b3f7bb0
bb36d43
994e4e3
577df0a
442e7f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| /* | ||
| * 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.backendsapi.velox | ||
|
|
||
| import org.apache.gluten.config.GlutenConfig | ||
| import org.apache.gluten.expression.{DecimalRoundTransformer, LiteralTransformer} | ||
| import org.apache.gluten.substrait.SubstraitContext | ||
|
|
||
| import org.apache.spark.sql.catalyst.expressions.{Add, BoundReference, Cast, Literal, Round} | ||
| import org.apache.spark.sql.internal.SQLConf | ||
| import org.apache.spark.sql.types._ | ||
|
|
||
| import org.scalatest.funsuite.AnyFunSuite | ||
|
|
||
| class VeloxDecimalRoundSuite extends AnyFunSuite { | ||
| test("decimal round preserves a typed NULL scale") { | ||
| val value = Literal.create(Decimal(new java.math.BigDecimal("2.5")), DecimalType(3, 1)) | ||
| val scale = Literal.create(null, IntegerType) | ||
| val original = Round(value, scale) | ||
| val transformer = DecimalRoundTransformer("round", LiteralTransformer(value), original) | ||
| val scalar = transformer.doTransform(new SubstraitContext).toProtobuf.getScalarFunction | ||
| assert(scalar.getArgumentsCount == 2) | ||
| assert(scalar.getArguments(1).getValue.getLiteral.hasNull) | ||
| assert(transformer.dataType == DecimalType(3, 0)) | ||
| } | ||
|
|
||
| test("decimal round scale guards remain active without general native validation") { | ||
| val conf = new SQLConf | ||
| conf.setConfString(GlutenConfig.NATIVE_VALIDATION_ENABLED.key, "false") | ||
| SQLConf.withExistingConf(conf) { | ||
| val validator = new VeloxValidatorApi | ||
| val value = BoundReference(0, DecimalType(38, 38), nullable = true) | ||
| Seq(-400, -39, -1, 0, 38, 400).foreach { | ||
| scale => assert(validator.doExprValidate("round", Round(value, Literal(scale)))) | ||
| } | ||
| Seq(Int.MinValue, -401, 401, Int.MaxValue).foreach { | ||
| scale => assert(!validator.doExprValidate("round", Round(value, Literal(scale)))) | ||
| } | ||
| assert(validator.doExprValidate("round", Round(value, Literal.create(null, IntegerType)))) | ||
| assert(!validator.doExprValidate( | ||
| "round", | ||
| Round(value, BoundReference(1, IntegerType, nullable = false)))) | ||
| } | ||
| } | ||
|
|
||
| test("decimal round accepts bounded foldable INTEGER scales without prior constant folding") { | ||
| val conf = new SQLConf | ||
| conf.setConfString(GlutenConfig.NATIVE_VALIDATION_ENABLED.key, "false") | ||
| conf.setConfString(SQLConf.ANSI_ENABLED.key, "true") | ||
| SQLConf.withExistingConf(conf) { | ||
| val validator = new VeloxValidatorApi | ||
| val value = BoundReference(0, DecimalType(38, 38), nullable = true) | ||
| Seq(-400, -1, 0, 400).foreach { | ||
| scale => | ||
| assert(validator.doExprValidate( | ||
| "round", | ||
| Round(value, Add(Literal(scale), Literal(0))))) | ||
| } | ||
| assert(validator.doExprValidate( | ||
| "round", | ||
| Round(value, Cast(Literal.create(null, NullType), IntegerType)))) | ||
| Seq(Add(Literal(-400), Literal(-1)), Add(Literal(400), Literal(1))).foreach { | ||
| scale => assert(!validator.doExprValidate("round", Round(value, scale))) | ||
| } | ||
| assert(!validator.doExprValidate( | ||
| "round", | ||
| Round(value, Cast(Literal.create(null, NullType), LongType)))) | ||
| val error = intercept[Exception] { | ||
| validator.doExprValidate("round", Round(value, Cast(Literal("invalid-scale"), IntegerType))) | ||
| } | ||
| assert(error.getMessage.contains("invalid-scale")) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ RUN_SETUP_SCRIPT=ON | |
| ENABLE_ENHANCED_FEATURES=OFF | ||
|
|
||
| # Developer use only for testing Velox PR. | ||
| UPSTREAM_VELOX_PR_ID="" | ||
| UPSTREAM_VELOX_PR_ID="19141" | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed that this must not become the normal merged dependency. This revision is intentionally a dependency-blocked validation companion: Rui explicitly requested exercising the upstream PR through the existing developer hook at facebookincubator/velox#19141 (comment) . Clearing the hook now would test the stock dependency without the decimal fix. The PR description records the merge gate: land the upstream fix, qualify a normal dependency containing it, then restore the empty hook before merging. Keeping this thread open to track that required dependency step.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @luis4a0 Yes, this is only for development testing, and we could remove this change after the corresponding Velox gets merged.
luis4a0 marked this conversation as resolved.
Resolved
luis4a0 marked this conversation as resolved.
Resolved
|
||
|
|
||
| OS=`uname -s` | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validation might be duplicate, as we've added a native validation in the "SubstraitToVeloxPlanValidator.cc" (which is a preferred way with a unified validate API).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rui-mo, the bounds overlap, but these checks are not equivalent under the supported validation settings.
spark.gluten.sql.enable.native.validation=falseskips the JNI/Substrait plan validator. The Spark-expression eligibility hook still runs while constructing the candidate Substrait plan:gluten/gluten-substrait/src/main/scala/org/apache/gluten/expression/ExpressionConverter.scala
Lines 964 to 978 in d8b0c39
gluten/gluten-substrait/src/main/scala/org/apache/gluten/execution/WholeStageTransformer.scala
Lines 91 to 99 in d8b0c39
Without the JVM eligibility guard, that configuration could offload an unsupported scale and fail in native execution instead of retaining Spark. For example, Spark 4.1.1 successfully evaluates decimal ROUND at scales
401and-401, while our documented native domain rejects them.This uses the existing
ValidatorApi.doExprValidatepath, not an additional full validation pass. A rejected expression becomes a failed offload candidate through the normal validation/fallback path. I am retaining the guard to preserve this behavior independently of the optional native-validation flag.GlutenDecimalRoundSuiteexplicitly exercises this configuration and checks both the fallback plan and Spark-compatible results:gluten/gluten-ut/common/src/test/scala/org/apache/spark/sql/GlutenDecimalRoundSuiteBase.scala
Lines 124 to 162 in d8b0c39
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention is to perform native validation whenever possible, while avoiding introducing native-engine-specific logic into Scala. Instead, we aim to maintain a unified interface between Scala and native validation.
This configuration is primarily intended for debugging. It forces all computation to be offloaded to the native engine without fallback, meaning that unsupported operations will result in runtime exceptions rather than falling back to the original engine.
Does this approach make sense to you? Feel free to update the relevant documentation if anything is unclear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rui-mo, agreed. I removed the decimal-specific
doExprValidatepolicy from Scala and now rely on the existing native Substrait validator as the normal compatibility gate.Commit: bb36d43
The behavior is now:
[-400, 400]fail native validation and the expression falls back to Spark.I also updated the internal configuration description to make that debug-mode behavior explicit.
The direct validator regression now verifies that Scala does not filter or eagerly evaluate invalid literals, nonliteral scales, foldable expressions, or an ANSI-true throwing cast. Integration tests cover normal fallback versus debug-mode native failure, while typed NULL and bounded foldable scales continue to execute natively.
Validation passed: native 3/3, API 2/2, Spark 4.1 SQL 4/4 on JDK 17 and 21, full Spark 4.1 reactor build, active Spotless/Scalastyle, license headers, and packaged native-library hash checks. Fresh upstream CI is still required.