andygrove opened a new issue, #6632:
URL: https://github.com/apache/datafusion-comet/issues/6632
Part of #6615.
## What / Why
This issue moves window function coverage from `exec/CometWindowExecSuite`
to Comet SQL tests under `sql-tests/windows/` and
`sql-tests/expressions/window/`. It also covers the floating-point sort-key
tests in `CometExpressionSuite`, which target the same order-key fixtures.
Static analysis on `main` at 3bc2faa934 found the following:
| Suite | Covered | Convert | Blocked | Split | Keep | Total |
|---|---|---|---|---|---|---|
| `CometExpressionSuite` | 0 | 5 | 0 | 0 | 2 | 7 |
| `CometWindowExecSuite` | 25 | 30 | 0 | 2 | 0 | 57 |
| **Total** | **25** | **35** | **0** | **2** | **2** | **64** |
Unlike most areas, 25 of the window tests are **already duplicated** by
`windows/window_functions.sql` and `expressions/window/lag_lead.sql`. They can
be deleted as soon as this issue starts; the table cites the covering fixture
section for each. The rest convert, with two framework facts to know first.
1. **Write `N PRECEDING` as `-N FOLLOWING` in ROWS frames.**
- Spark parses `N PRECEDING` as `UnaryMinus(Literal(N))`, and
`WindowEvaluatorFactoryBase.createBoundOrdering` accepts only an
`IntegerLiteral` as a ROWS bound. The Scala tests get `Literal(-N)` only
because ConstantFolding runs there. In a fixture, the Spark side of the
comparison throws "Unhandled bound in windows expressions".
- `-N FOLLOWING` parses straight to `Literal(-N)`, the same bound the
Scala run ends up with. CometWindowExec reads ROWS bounds with `e.eval()`, so
the native frame is identical.
- RANGE frames are not affected.
- This is a static conclusion. **Confirm it with one fixture first.** If
it fails, L590, L904, L924 and L1497 wait on #6618.
2. **PERCENT_RANK and CUME_DIST are compared only under tolerance today**,
in `window_functions.sql` 1.6, 5.2 and 5.3. Those tolerance queries have no
ORDER BY, so they depend on both engines collecting rows in the same order
(#6616). The Scala tests compare exactly and pass, so use an exact `query`.
## Work
Line numbers refer to `CometWindowExecSuite` unless marked.
**`windows/window_functions.sql` (existing): 18 tests**
- **New small tables:**
- L389: `(value int)` = 1,2,4,3,2,1 with `SUM(value) OVER (ORDER BY value
DESC RANGE BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING)`.
- L408: the same table, with `RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED
FOLLOWING` as `expect_fallback(Unsupported RANGE frame lower offset)`.
- L443: `(k int, v boolean)` with `every(v) OVER (PARTITION BY k ORDER BY
v)`.
- L459: the nine testData rows including NULL keys, with a `count(val)`
over a `ROWS CURRENT ROW` frame.
- L482: `count(key)` over `RANGE BETWEEN CURRENT ROW AND 2147483648
FOLLOWING` and over `RANGE BETWEEN 2147483649 PRECEDING AND CURRENT ROW`.
- **New queries on `emp`:**
- L639: `SUM(salary) OVER (PARTITION BY dept)`. Today no native-mode `OVER
(PARTITION BY x)` without ORDER BY is tested; sections 6.3-6.6 all expect
fallback.
- L852, L944, L964: COUNT(*) and MAX over ROWS frames.
- L1208: `FIRST_VALUE` with the default frame. Order by `id`: every
partition has tied `b` values with different `c`, so the Scala query is
nondeterministic.
- L1229: `LAST_VALUE` with RESPECT NULLS.
- L1250: `NTH_VALUE(salary, 2)`.
- L1044, L1270, L1517: one exact query with ROW_NUMBER, RANK, DENSE_RANK,
PERCENT_RANK, CUME_DIST and NTILE(3) in one spec.
- **PRECEDING frames:** L904, L924, L1497, written with `-N FOLLOWING`. Also
correct the section 1.9 comment (L197-205), which says these frames are covered
in Scala "via the DataFrame API".
**New fixtures**
- `windows/window_decimal_aggregates.sql` + `_ansi.sql` (L499, L672, L704,
L742, L778)
- Covers decimal SUM/AVG over windows: OVER () on decimal(2,1), and
running AVG/SUM on decimal(10,2) and (8,2).
- The curated running AVG at (9,1), (9,4), (10,2), (11,3) and (11,6)
includes half-up ties at scale s+4.
- Add decimal(38,38) 0.6, 0.6, -0.6, where SUM and `try_sum` give 0.6,
NULL, 0.6. In the ANSI file, `try_sum` is the native sentinel and SUM gets
`expect_error(NUMERIC_VALUE_OUT_OF_RANGE)`, which matches the error classes on
both sides of 4.0.
- Today these shapes are only in `window_filter.sql`, which is gated to
4.2+.
- `windows/window_basic_frames.sql` (L590)
- `-- ConfigMatrix: spark.sql.adaptive.enabled=true,false`.
- `t1(_1, _2) = (i, 10-i)` written as **one file**, because a ROWS frame
without ORDER BY depends on input order.
- 35 queries: COUNT(_1), COUNT(*), MAX, MIN and SUM, each over 7 frame
shapes.
- `windows/window_aggregate_types.sql` (L528)
- A curated replacement for the random `makeParquetFile` columns: tinyint
through bigint, float, double, decimal(5,2)/(18,10)/(38,37), timestamp and date.
- Include whole-row NULLs, plus low- and high-cardinality variants.
- Add `spark.comet.batchSize` and `outputTimestampType` matrices.
- Use exact `query` except float/double SUM, which needs tolerance with a
total ORDER BY.
- `windows/window_literal_partition_order.sql` (L425)
- `sum(product + 1) OVER (PARTITION BY 1 ORDER BY 2)` over Parquet, with a
shuffle-mode matrix.
- This is stronger than the Scala LocalTableScan source, where
`ConvertToLocalRelation` folds `product + 1`.
- `windows/window_repeated_shuffle.sql` (L508)
- `spark.sql.requireAllClusterKeysForDistribution=true`, native shuffle,
and an AQE matrix.
- `lead(...)` over a `REPARTITION(key1)` subquery.
- `window/lag_lead_offset_beyond_rows.sql` (L368): jvm shuffle and
localTableScan on, with both of the test's queries verbatim.
- **Floating-point keys:**
- `windows/window_group_limit_float_peers.sql` (L126, `MinSparkVersion:
3.5`): signed-zero and sign-bit-NaN peers at the group-limit cutoff, written
with `IF(neg, -x, x)`, with a strictFloatingPoint matrix.
- `windows/nested_float_order_keys.sql` / `_strict.sql` (existing; L213,
and `CometExpressionSuite` L282 and L290)
- RANK/DENSE_RANK over `array(f)`, `array(d)` and `named_struct(...)`
keys, with signed zeros at the `rnk <= 1` cutoff.
- Add array and struct float columns read from Parquet, including
multi-element, empty and NULL arrays.
- `windows/scalar_float_order_keys_strict.sql` (L276, and
`CometExpressionSuite` L96, L158 (16 instances) and L177)
- strictFloatingPoint=true with the SortOrder opt-in off.
- Curated ±0.0, NaN, ±Inf, NULL, Min and Max values with a unique id.
- Cover ORDER BY, TopK, and NULLS FIRST/LAST.
- `windows/window_float_partition_keys.sql` +
`window_group_limit_float_partition_keys.sql` (L315, the latter gated 3.5):
keys 1.0, 2.0, NULL, 0.0, -0.0, NaN and a sign-bit NaN.
## Stays in Scala
- **NaN payload bits.** Parquet writes floats with `floatToIntBits` /
`doubleToLongBits`, dropping the NaN sign and payload, and the SQL comparator
canonicalizes NaN.
- The kept half of L126: the payload peers and `assertRawBits`.
- `CometExpressionSuite` L198 and L301.
- **The ANSI SUM half of L778**, which uses `checkSparkError` (#6617).
## Notes
- **No-op config.** The suite-wide
`spark.comet.operator.WindowExec.allowIncompatible=true` (L51) and the same key
in `window/lag_lead.sql` do nothing. CometWindowExec does not override
`getSupportLevel`, so the operator is always Compatible.
- **Misnamed tests.**
- L443 ("fall back to Spark when ...") actually asserts a fully native
plan. It also creates a view named `test_agg` inside `withTempView("test")`, so
the view is never dropped.
- L499 ("Unsupported window expression should fall back to Spark") never
compares with Spark or checks for a fallback.
- L1208 ("FIRST_VALUE with default ignore nulls") runs with the default,
which is RESPECT NULLS.
- **Redundant loops.** L528 makes twelve runs that cover four distinct cases.
- **Internal duplicates.** L1517 subsumes L984, L1004, L1024, L1044 and
L1270.
- **Stale fixture header.** `window_group_limit_rank_dense_rank.sql` says
its `rk = 0` query uses `spark_answer_only`, but it is a plain `query`.
## Done when
- Every test in the table below is accounted for. Either a fixture covers it
(the same inputs, configs and assertion strength, or stronger), the fixture
that already covers it is cited, or it stays in Scala for the reason given
above.
- Each converted Scala test is deleted in the PR that adds its coverage. A
suite left empty is deleted and removed from the suite lists in
`.github/workflows/pr_build_linux.yml` and `pr_build_macos.yml`.
- The new and changed fixtures pass on every Spark profile. Apply
`run-all-spark-profiles`, because pull requests run only the default profile.
<details><summary>Every Scala test in scope, with its verdict and target
fixture</summary>
| Suite | Line | Test | Verdict | Target |
|---|---|---|---|---|
| `CometExpressionSuite` | 96 | sort floating point with negative zero |
convert | ../windows/scalar_float_order_keys_strict.sql (new) |
| `CometExpressionSuite` | 158 | strict floating point: scalar sort on
$label (loop: col f\|d x ASC\|DESC x NULLS FIRST\|NULLS LAST x compound
false\|true = 16 instances) | convert |
../windows/scalar_float_order_keys_strict.sql (new) |
| `CometExpressionSuite` | 177 | strict floating point: scalar TopK sort key
| convert | ../windows/scalar_float_order_keys_strict.sql (new) |
| `CometExpressionSuite` | 198 | strict floating point: scalar sort keeps
NaN payloads and zero signs unchanged | keep | |
| `CometExpressionSuite` | 282 | sort array of floating point with negative
zero | convert | ../windows/nested_float_order_keys_strict.sql +
../windows/nested_float_order_keys.sql |
| `CometExpressionSuite` | 290 | sort struct containing floating point with
negative zero | convert | ../windows/nested_float_order_keys_strict.sql +
../windows/nested_float_order_keys.sql |
| `CometExpressionSuite` | 301 | strict floating point: nested sort keeps
NaN payloads and zero signs unchanged | keep | |
| `CometWindowExecSuite` | 126 | window group limit: $orderColumn NaN and
signed zero peers at the cutoff (strictFloatingPoint=$strictFloatingPoint)
[loop: orderColumn f\|d x strictFloatingPoint false\|true = 4 instances] |
split (rest: #6618) | ../windows/window_group_limit_float_peers.sql (new) |
| `CometWindowExecSuite` | 213 | window group limit: floating-point values
nested in the order key | convert | ../windows/nested_float_order_keys.sql +
../windows/nested_float_order_keys_strict.sql |
| `CometWindowExecSuite` | 276 | window: scalar floating-point order key
under strict floating point ($orderColumn) [loop: f\|d = 2 instances] | convert
| ../windows/scalar_float_order_keys_strict.sql (new) |
| `CometWindowExecSuite` | 315 | window: floating partition keys
($partitionColumn, $function, group limit=$groupLimit) [loop: f\|d x {RANK()
false, PERCENT_RANK() false, NTILE(2) false, RANK() true} = 8 instances] |
convert | ../windows/window_float_partition_keys.sql (new) +
../windows/window_group_limit_float_partition_keys.sql (new) |
| `CometWindowExecSuite` | 368 | lead/lag should return the default value if
the offset row does not exist | convert |
window/lag_lead_offset_beyond_rows.sql (new) |
| `CometWindowExecSuite` | 389 | window query with rangeBetween | convert |
../windows/window_functions.sql |
| `CometWindowExecSuite` | 408 | window query with rangeBetween FOLLOWING
lower bound falls back to Spark | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 425 | window function: partition and order
expressions | convert | ../windows/window_literal_partition_order.sql (new) |
| `CometWindowExecSuite` | 443 | fall back to Spark when the partition spec
and order spec are not the same for window function | convert |
../windows/window_functions.sql |
| `CometWindowExecSuite` | 459 | Native window operator should be
CometUnaryExec | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 482 | Window range frame with long boundary
should not fail | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 499 | Unsupported window expression should fall
back to Spark | convert | ../windows/window_decimal_aggregates.sql (new) |
| `CometWindowExecSuite` | 508 | Repeated shuffle exchange don't fail |
convert | ../windows/window_repeated_shuffle.sql (new) |
| `CometWindowExecSuite` | 528 | aggregate window function for all types |
convert | ../windows/window_aggregate_types.sql (new) |
| `CometWindowExecSuite` | 590 | Windows support | convert |
../windows/window_basic_frames.sql (new) |
| `CometWindowExecSuite` | 623 | window: simple COUNT(*) without frame |
covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 639 | window: simple SUM with PARTITION BY |
convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 655 | window: AVG with PARTITION BY and ORDER BY
| covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 672 | window: decimal AVG with PARTITION BY and
ORDER BY | convert | ../windows/window_decimal_aggregates.sql (new) |
| `CometWindowExecSuite` | 704 | window: decimal AVG fuzz with PARTITION BY
and ORDER BY [loop over (precision, scale) in (9,1),(9,4),(10,2),(11,3),(11,6)
inside one test] | convert | ../windows/window_decimal_aggregates.sql (new) |
| `CometWindowExecSuite` | 742 | window: decimal SUM with PARTITION BY and
ORDER BY | convert | ../windows/window_decimal_aggregates.sql (new) |
| `CometWindowExecSuite` | 778 | window: decimal SUM recovers from an
intermediate overflow in an expanding frame [loop over ANSI false/true inside
one test] | split (rest: #6617) | ../windows/window_decimal_aggregates.sql
(new) + ../windows/window_decimal_aggregates_ansi.sql (new) |
| `CometWindowExecSuite` | 831 | window: MIN and MAX with ORDER BY | covered
| ../windows/window_functions.sql |
| `CometWindowExecSuite` | 852 | window: COUNT with ROWS BETWEEN UNBOUNDED
PRECEDING AND CURRENT ROW | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 884 | window: SUM with ROWS BETWEEN CURRENT ROW
AND UNBOUNDED FOLLOWING | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 904 | window: AVG with ROWS BETWEEN 1 PRECEDING
AND 1 FOLLOWING | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 924 | window: SUM with ROWS BETWEEN 2 PRECEDING
AND CURRENT ROW | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 944 | window: COUNT with ROWS BETWEEN CURRENT ROW
AND 2 FOLLOWING | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 964 | window: MAX with ROWS BETWEEN UNBOUNDED
PRECEDING AND UNBOUNDED FOLLOWING | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 984 | window: ROW_NUMBER with PARTITION BY and
ORDER BY | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1004 | window: RANK with PARTITION BY and ORDER
BY | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1024 | window: DENSE_RANK with PARTITION BY and
ORDER BY | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1044 | window: PERCENT_RANK with PARTITION BY and
ORDER BY | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1064 | window: NTILE with PARTITION BY and ORDER
BY | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1091 | window: LAG with default offset | covered
| ../windows/window_functions.sql + window/lag_lead.sql |
| `CometWindowExecSuite` | 1111 | window: LAG with offset 2 and default
value | covered | ../windows/window_functions.sql + window/lag_lead.sql |
| `CometWindowExecSuite` | 1131 | window: LAG with IGNORE NULLS | covered |
../windows/window_functions.sql + window/lag_lead.sql |
| `CometWindowExecSuite` | 1149 | window: LEAD with default offset | covered
| ../windows/window_functions.sql + window/lag_lead.sql |
| `CometWindowExecSuite` | 1169 | window: LEAD with offset 2 and default
value | covered | ../windows/window_functions.sql + window/lag_lead.sql |
| `CometWindowExecSuite` | 1189 | window: LEAD with IGNORE NULLS | covered |
../windows/window_functions.sql + window/lag_lead.sql |
| `CometWindowExecSuite` | 1208 | window: FIRST_VALUE with default ignore
nulls | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1229 | window: LAST_VALUE with ROWS frame |
convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1250 | window: NTH_VALUE with position 2 |
convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1270 | window: CUME_DIST with PARTITION BY and
ORDER BY | convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1290 | window: multiple window functions in
single query | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1313 | window: different window specifications in
single query | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1335 | window: ORDER BY DESC with aggregation |
covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1355 | window: multiple PARTITION BY columns |
covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1375 | window: multiple ORDER BY columns |
covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1395 | window: RANGE BETWEEN with numeric ORDER
BY | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1415 | window: RANGE BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1435 | window: complex expression in window
function | covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1455 | window: window function with WHERE clause
| covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1476 | window: window function with GROUP BY |
covered | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1497 | window: ROWS BETWEEN with negative offset
| convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1517 | window: all ranking functions together |
convert | ../windows/window_functions.sql |
| `CometWindowExecSuite` | 1542 | window: FIRST_VALUE/LAST_VALUE/NTH_VALUE
IGNORE NULLS with leading nulls | covered | ../windows/window_functions.sql |
</details>
--
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]