Repository navigation
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #26266 +/- ##
==========================================
+ Coverage 82.80% 82.95% +0.14%
==========================================
Files 1149 1156 +7
Lines 454480 458762 +4282
Branches 454480 458762 +4282
==========================================
+ Hits 376347 380544 +4197
+ Misses 55267 55151 -116
- Partials 22866 23067 +201 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thanks for the PR. I reviewed this by reading the diff and surrounding code. I did not build or run it, so the failure scenarios below are from code reading and not reproduced. Main concerns
Smaller items
I'd treat 1-3 as the ones to address before merging, especially 1 and 3. |
Cap the generated rows per batch, unify integer literals with an INT value in any position, ignore nullability when matching types, check array lengths and cover the `tile` block boundaries.
|
Thanks, pushed
Nullability is ignored when matching types. Array lengths are checked against |
andygrove
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround, @comphead. The integer literal unification and the array length check look good, and the slt cases cover them. Your reasons for keeping the strict cast, tile and the literal-only n make sense to me. For Comet, Spark folds a foldable n to a literal before we see the plan anyway.
There are two things inline, and one of them is my mistake from the last round. Spark's Stack.checkInputDataTypes compares column types with !=, so nullability does matter there. My item 4 was wrong, sorry about that.
Smaller items:
- The cap is per batch, so whether
stack(4096, col)succeeds depends on the batch size. It fails with 8192-row batches and works with 4096. Spark has no limit, and Comet uses 8192-row batches, so anynabove 2048 with a column argument fails there. Could the user-facing changes section mention the limit? TINYINTandSMALLINTliterals are also widened in anINTcolumn, since the check isis_integer(). Spark rejects those. Limiting it toInt64literals would match the comment.- The cap error reads "1 rows of 2000000000 generated rows exceed the limit". Something like "generating 2000000000 rows for each of 1 input rows exceeds the limit of 16777216 rows per batch" would read more clearly.
I checked both inline cases with unit tests against ac51d5844, not end to end.
| return internal_err!("stack: `n` must be a scalar"); | ||
| }; | ||
| let n = parse_n(n)?; | ||
| if values.iter().all(|v| matches!(v, ColumnarValue::Scalar(_))) { |
There was a problem hiding this comment.
The cap is only checked against the real batch size on the array path. When every value is a literal, this calls stack(.., 1) and returns one scalar list, and the caller then expands it to number_rows copies through ScalarValue::to_array_of_size with no check. With n = 2^20 and number_rows = 8192, invoke_with_args returns Ok, and the expanded result would be about 8.6 billion rows against a cap of 16.8 million. Something like SELECT stack(1048576, 1) FROM generate_series(1, 8192) should reach it, and so does constant folding, which turns the call into a literal at plan time.
Could the number_rows * n check run before this shortcut? That covers execution, including Comet. Folded literals would still get through, but at least that gap would be known.
| /// Whether two Arrow types are the same Spark type. Types of one logical type, such | ||
| /// as `Utf8` and `Utf8View`, match, and list elements and map entries match | ||
| /// regardless of their field names, which Spark types do not have. As in Spark's | ||
| /// `sameType`, nullability is ignored. |
There was a problem hiding this comment.
This comes from my last review, and I was wrong about it. Spark's Stack.checkInputDataTypes compares each value's type with the column type using != (in both 3.5.8 and 4.1.1), so containsNull, valueContainsNull and StructField.nullable all count. StackTypeCoercion only retypes a NULL literal.
Ignoring nullability now lets a query plan and then fail. The column takes the first value's type, so when that's List(non-null Int64) and a later value is a List(Int64) with a NULL element, the cast at execution fails with Non-nullable field of ListArray "item" cannot contain nulls. Something like SELECT stack(2, arrow_cast([1], 'List(non-null Int64)'), [2, NULL]) should show it. The existing Nullability does not make types differ case passes only because [2] has no NULL.
I'd go back to matching nullability exactly, as Spark does. If you'd rather stay lenient, the column type's nested fields would need to be nullable whenever the values differ.
Check the generated rows limit before the all-scalar shortcut, match nullability exactly as Spark does, widen only BIGINT literals and clarify the limit error.
|
Thanks, pushed
|
Which issue does this PR close?
datafusion-sparkSpark Compatible Functions #15914Rationale for this change
Spark's
stack(n, expr1, ..., exprk)separates the values intonrows. It is the standard way to unpivot in Spark SQL, and Comet falls back to Spark for it.What changes are included in this PR?
stackindatafusion-spark. A ScalarUDF cannot emit rows, so likejson_tupleit returns thenrows as aList<Struct<col0, col1, ...>>, which is the data type of Spark's Generator. As in Spark, the list is not nullable while its elements and fields are. The caller (Comet, orunnestin SQL) expands it.Stack.checkInputDataTypes, with the same messages.nis coerced toINT. The values of a column must have the same Spark type, and NULLs, Arrow types of one Spark type (such asUtf8andUtf8View, or lists and maps whose field names differ) and integer literals that fit anINTcolumn are cast to it.concatcalls. Other types usetakeover the concatenated values. On the newstackbench this is about 3x to 5x faster thaninterleave.What is the testing strategy for this PR?
spark/generator/stack.sltports the cases of Spark'sGeneratorFunctionSuite,GeneratorExpressionSuiteandTypeCoercionSuite, and of thegenerators,table-valued-functionsandjoin-lateralgolden files. It also covers each kernel path. Unit tests cover an empty batch, the row limit and the type widening, which SQL cannot reach.Are there any user-facing changes?
A new Spark-compatible
stackfunction. Unlike Spark,nmust be an integer literal, so a foldable expression such as3 - 1is rejected.The rows are materialized per batch, so a call that generates more than 16777216 rows (
ntimes the batch size) fails with a resources error. Spark streams the rows and has no limit. With 8192-row batches this rejectsnabove 2048 for a column argument.