[SPARK-58931][SQL] Reject negative randstr length during analysis - #58200
[SPARK-58931][SQL] Reject negative randstr length during analysis#58200uros-b wants to merge 3 commits into
Conversation
uros-b
left a comment
There was a problem hiding this comment.
@cloud-fan Please review.
|
Thanks for working on this. The direction looks right to me: 1.
|
| # | Severity | Item |
|---|---|---|
| 1 | Blocker | Throwing SparkRuntimeException during analysis; return DataTypeMismatch instead (plus null guard, plus regenerate results/random.sql.out) |
| 2 | Minor | Side-effect-only call with a discarded result |
| 3 | Minor | No test asserting the analysis-time failure |
| 4 | Minor | PR description overstates consistency with existing checks and understates user impact |
I reviewed this statically against the codebase conventions and did not build or run the tests.
cloud-fan
left a comment
There was a problem hiding this comment.
0 blocking, 1 non-blocking, 0 nits.
The implementation is sound and well-covered; one non-blocking public-documentation update remains.
Suggestions (1)
- Non-blocking: sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/randomExpressions.scala:431: Document
lengthas non-negative inrandstr's public SQL description so the new analysis-time range is discoverable. -- see inline
Verification
Static verification: traced the iterative CheckAnalysis and single-pass ExpressionResolver paths to the same RandStr.checkInputDataTypes implementation; compared the foldable-range pattern with RegExpReplace; and confirmed the negative and NULL cases in the DataFrame test and SQL goldens. The Spark test suite was not run.
PR metadata suggestions
- Replace the cited
RegExpInStrprecedent withRegExpReplace; the latter is the expression whosecheckInputDataTypesevaluates the foldable integer and returnsVALUE_OUT_OF_RANGE.
|
Thank you @vladimirg-db @dongjoon-hyun @cloud-fan for review! |
|
Merge Summary:
Posted by |
### What changes were proposed in this pull request?
`randstr(length[, seed])` requires a non-negative `length`. Today that guard lives only on the execution paths - `RandStr.lengthInteger()`, called from `evalInternal` (interpreted) and `doGenCode` (codegen), so a negative constant `length` is not rejected until the query executes.
This PR moves the guard into Catalyst analysis. `RandStr.checkInputDataTypes()` already requires `length` to be a foldable integer; once those checks pass, it now evaluates the (constant) `length` and, if it is negative, returns a failed `TypeCheckResult` - `DataTypeMismatch("VALUE_OUT_OF_RANGE")` - rather than throwing. A `null` `length` is left untouched (`randstr(NULL, 0)` remains valid and returns an empty string). Because `checkInputDataTypes()` runs during analysis, `randstr` with a negative constant `length` is now rejected at analysis time as an `AnalysisException` carrying the query context, like the other `randstr` input checks.
This follows the precedent of other expressions that validate a foldable constant during analysis - `TimeBucket` (`datetimeExpressions.scala`) and `RegExpReplace` (`regexpExpressions.scala`) - which `eval()` the constant and return `DataTypeMismatch("VALUE_OUT_OF_RANGE")`.
### Why are the changes needed?
`randstr`'s `length` must be a foldable constant, which `checkInputDataTypes()` already enforces, so a negative `length` can be detected during analysis rather than only at execution. Performing the check in `checkInputDataTypes()`:
- rejects an invalid `randstr(-1, ...)` during analysis (fail-fast), rather than only once the expression is evaluated at execution;
- surfaces the failure as an `AnalysisException` with a `QueryContext`, like every other `randstr` input check, instead of a `SparkRuntimeException` escaping from analysis;
- keeps the validation with the rest of `randstr`'s input checking.
### Does this PR introduce _any_ user-facing change?
Yes. `randstr` with a negative constant `length` now fails during analysis instead of at execution, and the error class changes.
- Before: analysis succeeds; the query fails at execution with a `SparkRuntimeException`, error class `INVALID_PARAMETER_VALUE.LENGTH`.
- After: the query fails during analysis with an `AnalysisException`, error class `DATATYPE_MISMATCH.VALUE_OUT_OF_RANGE`.
Because the failure now happens during analysis, it affects queries that never reach execution:
- `EXPLAIN SELECT randstr(-1, 0)` previously printed a plan; it now fails.
- `spark.sql("SELECT randstr(-1, 0)")` now throws immediately (before any action), so the returned `DataFrame`'s `schema` is unreachable.
- A `randstr(-1, ...)` in a branch the optimizer would have pruned away now fails rather than being eliminated.
Failing fast on an invalid constant `length` is the intent of this change. Queries with a valid (non-negative or `NULL`) `length` are unaffected.
### How was this patch tested?
- Added a negative-length case to `DataFrameFunctionsSuite`'s `test("randstr function")` asserting that `df.select(randstr(lit(-1), lit(0)))` fails during analysis (on `select` alone, without an action) with `DATATYPE_MISMATCH.VALUE_OUT_OF_RANGE`.
- The golden SQL test `random.sql` covers `SELECT randstr(-1, 0)`; regenerated both `analyzer-results/random.sql.out` and `results/random.sql.out` and reviewed the diff:
```
SPARK_GENERATE_GOLDEN_FILES=1 build/sbt "sql/testOnly org.apache.spark.sql.SQLQueryTestSuite -- -z random.sql"
```
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)
Closes #58200 from uros-b/randstr-neg-length-analysis.
Authored-by: Uros <221401595+uros-b@users.noreply.github.com>
Signed-off-by: Uros Bojanic <221401595+uros-b@users.noreply.github.com>
(cherry picked from commit d01d020)
Signed-off-by: Uros Bojanic <221401595+uros-b@users.noreply.github.com>
### What changes were proposed in this pull request?
`randstr(length[, seed])` requires a non-negative `length`. Today that guard lives only on the execution paths - `RandStr.lengthInteger()`, called from `evalInternal` (interpreted) and `doGenCode` (codegen), so a negative constant `length` is not rejected until the query executes.
This PR moves the guard into Catalyst analysis. `RandStr.checkInputDataTypes()` already requires `length` to be a foldable integer; once those checks pass, it now evaluates the (constant) `length` and, if it is negative, returns a failed `TypeCheckResult` - `DataTypeMismatch("VALUE_OUT_OF_RANGE")` - rather than throwing. A `null` `length` is left untouched (`randstr(NULL, 0)` remains valid and returns an empty string). Because `checkInputDataTypes()` runs during analysis, `randstr` with a negative constant `length` is now rejected at analysis time as an `AnalysisException` carrying the query context, like the other `randstr` input checks.
This follows the precedent of other expressions that validate a foldable constant during analysis - `TimeBucket` (`datetimeExpressions.scala`) and `RegExpReplace` (`regexpExpressions.scala`) - which `eval()` the constant and return `DataTypeMismatch("VALUE_OUT_OF_RANGE")`.
### Why are the changes needed?
`randstr`'s `length` must be a foldable constant, which `checkInputDataTypes()` already enforces, so a negative `length` can be detected during analysis rather than only at execution. Performing the check in `checkInputDataTypes()`:
- rejects an invalid `randstr(-1, ...)` during analysis (fail-fast), rather than only once the expression is evaluated at execution;
- surfaces the failure as an `AnalysisException` with a `QueryContext`, like every other `randstr` input check, instead of a `SparkRuntimeException` escaping from analysis;
- keeps the validation with the rest of `randstr`'s input checking.
### Does this PR introduce _any_ user-facing change?
Yes. `randstr` with a negative constant `length` now fails during analysis instead of at execution, and the error class changes.
- Before: analysis succeeds; the query fails at execution with a `SparkRuntimeException`, error class `INVALID_PARAMETER_VALUE.LENGTH`.
- After: the query fails during analysis with an `AnalysisException`, error class `DATATYPE_MISMATCH.VALUE_OUT_OF_RANGE`.
Because the failure now happens during analysis, it affects queries that never reach execution:
- `EXPLAIN SELECT randstr(-1, 0)` previously printed a plan; it now fails.
- `spark.sql("SELECT randstr(-1, 0)")` now throws immediately (before any action), so the returned `DataFrame`'s `schema` is unreachable.
- A `randstr(-1, ...)` in a branch the optimizer would have pruned away now fails rather than being eliminated.
Failing fast on an invalid constant `length` is the intent of this change. Queries with a valid (non-negative or `NULL`) `length` are unaffected.
### How was this patch tested?
- Added a negative-length case to `DataFrameFunctionsSuite`'s `test("randstr function")` asserting that `df.select(randstr(lit(-1), lit(0)))` fails during analysis (on `select` alone, without an action) with `DATATYPE_MISMATCH.VALUE_OUT_OF_RANGE`.
- The golden SQL test `random.sql` covers `SELECT randstr(-1, 0)`; regenerated both `analyzer-results/random.sql.out` and `results/random.sql.out` and reviewed the diff:
```
SPARK_GENERATE_GOLDEN_FILES=1 build/sbt "sql/testOnly org.apache.spark.sql.SQLQueryTestSuite -- -z random.sql"
```
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)
Closes #58200 from uros-b/randstr-neg-length-analysis.
Authored-by: Uros <221401595+uros-b@users.noreply.github.com>
Signed-off-by: Uros Bojanic <221401595+uros-b@users.noreply.github.com>
(cherry picked from commit d01d020)
Signed-off-by: Uros Bojanic <221401595+uros-b@users.noreply.github.com>
What changes were proposed in this pull request?
randstr(length[, seed])requires a non-negativelength. Today that guard lives only on the execution paths -RandStr.lengthInteger(), called fromevalInternal(interpreted) anddoGenCode(codegen), so a negative constantlengthis not rejected until the query executes.This PR moves the guard into Catalyst analysis.
RandStr.checkInputDataTypes()already requireslengthto be a foldable integer; once those checks pass, it now evaluates the (constant)lengthand, if it is negative, returns a failedTypeCheckResult-DataTypeMismatch("VALUE_OUT_OF_RANGE")- rather than throwing. Anulllengthis left untouched (randstr(NULL, 0)remains valid and returns an empty string). BecausecheckInputDataTypes()runs during analysis,randstrwith a negative constantlengthis now rejected at analysis time as anAnalysisExceptioncarrying the query context, like the otherrandstrinput checks.This follows the precedent of other expressions that validate a foldable constant during analysis -
TimeBucket(datetimeExpressions.scala) andRegExpReplace(regexpExpressions.scala) - whicheval()the constant and returnDataTypeMismatch("VALUE_OUT_OF_RANGE").Why are the changes needed?
randstr'slengthmust be a foldable constant, whichcheckInputDataTypes()already enforces, so a negativelengthcan be detected during analysis rather than only at execution. Performing the check incheckInputDataTypes():randstr(-1, ...)during analysis (fail-fast), rather than only once the expression is evaluated at execution;AnalysisExceptionwith aQueryContext, like every otherrandstrinput check, instead of aSparkRuntimeExceptionescaping from analysis;randstr's input checking.Does this PR introduce any user-facing change?
Yes.
randstrwith a negative constantlengthnow fails during analysis instead of at execution, and the error class changes.SparkRuntimeException, error classINVALID_PARAMETER_VALUE.LENGTH.AnalysisException, error classDATATYPE_MISMATCH.VALUE_OUT_OF_RANGE.Because the failure now happens during analysis, it affects queries that never reach execution:
EXPLAIN SELECT randstr(-1, 0)previously printed a plan; it now fails.spark.sql("SELECT randstr(-1, 0)")now throws immediately (before any action), so the returnedDataFrame'sschemais unreachable.randstr(-1, ...)in a branch the optimizer would have pruned away now fails rather than being eliminated.Failing fast on an invalid constant
lengthis the intent of this change. Queries with a valid (non-negative orNULL)lengthare unaffected.How was this patch tested?
DataFrameFunctionsSuite'stest("randstr function")asserting thatdf.select(randstr(lit(-1), lit(0)))fails during analysis (onselectalone, without an action) withDATATYPE_MISMATCH.VALUE_OUT_OF_RANGE.random.sqlcoversSELECT randstr(-1, 0); regenerated bothanalyzer-results/random.sql.outandresults/random.sql.outand reviewed the diff:Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)