jayzhan211 commented on issue #26138:
URL: https://github.com/apache/datafusion/issues/26138#issuecomment-6497193637

   @mkleen slt is right for the cases we already know: each fix PR should pin 
its rows from the table in the description in slt. The differential test is for 
the cases nobody has found yet, so it can't rely on hand-written expected 
output.
   
   The rule: changing a `-0.0` to `0.0` must not change any result, apart from 
how `-0.0` prints. So the expected result comes from DataFusion itself, run on 
a copy where every `-0.0` is `0.0`.
   
   1. Generate tables whose float columns (flat, list, struct) draw from a 
small pool (`0.0`, `1.0`, `-1.0`, `inf`, one NaN), so keys tie often. Add a 
unique `id`.
   2. Make a second copy by flipping the sign of some zeros, both in the data 
and in the zero literals of the query, since constant folding is a separate 
path (today `0.0 IN (-0.0)` is false while `-0.0 = 0.0` is true).
   3. Run the query on both copies under the same settings, print `-0.0` as 
`0.0`, and compare. Repeat under several settings: hash vs sort-merge join, 
batch size 1, 1 vs 4 partitions, a memory limit that forces spilling, and both 
copies read from Parquet with small row groups.
   
   This also covers code with only one implementation (IN, CASE, `nullif`, 
`array_*`), where comparing hash join with sort-merge join can't help. It would 
replace the "operators that must agree" test in the description; I'll update 
the description if you agree.
   
   I tried a quick SQL version on main: 25 queries over 4 rows, queries below. 
It flags every case in the table, plus some new ones, which I've added to the 
table:
   - `count(DISTINCT a)` counts `-0.0` and `0.0` separately when there is a 
second DISTINCT aggregate, or when it is a window function. On its own it 
passes, because it is planned as a GROUP BY.
   - `array_remove`, and `array_has(k, 0.0)` on a list column. With a column 
`a`, `array_has(make_array(a), 0.0)` passes, but only because it is rewritten 
to `a = 0.0` and never reaches the `array_has` kernel.
   - Float64 `z IN (0.0, 1.0)` now returns false too, not only Float32.
   - With `prefer_hash_join = false`, a join on a `List(Float64)` key returns 3 
of 6 pairs, and two of the dropped pairs contain no `-0.0` (#26115).
   
   GROUP BY and the flat equi-join pass.
   
   For the real test I'd suggest Rust in `datafusion/core/tests/fuzz_cases/`. 
`aggregation_fuzzer` shows how to run a query under random settings, but its 
check sorts the rows, which would hide the ORDER BY bugs here. Some rules:
   - A fixed list of query templates, not random SQL: ORDER BY (several 
columns, list keys, with LIMIT), window PARTITION BY / rank / RANGE frames, 
GROUP BY / DISTINCT / DISTINCT ON, `count(DISTINCT)`, equi and range joins, set 
operations, IN, CASE, `nullif`, `array_*`, `min`/`max` on lists.
   - Each query must have one correct answer. An ORDER BY that picks or numbers 
rows (LIMIT, `first_value`, DISTINCT ON, `row_number`, `lag`, ROWS frames) ends 
with `id`. `rank` and RANGE frames don't, because their ties are what we test. 
Compare ORDER BY output in order and everything else as a multiset.
   - No arithmetic on the float columns (`-x`, `x * y`), since it can create 
`-0.0` in the zero-only copy. No functions that can see the sign (`1 / x`, 
`atan2`, `cot`, casts to string).
   - A list of (query, setting) pairs that are still wrong, each with its 
issue. Random data can only check that unlisted pairs pass. A fixed dataset 
where each listed pair is known to fail also checks that listed pairs still 
fail, so each fix PR has to remove its entries.
   - Once #26091 is decided, add NaN payloads to the pool and to step 2.
   
   Does this match what you had in mind? If so, this test could be the next PR, 
before the sort-order change, and I'm happy to review it.
   
   <details><summary>Queries (each runs on <code>raw</code> and on 
<code>canon</code> in place of <code>{t}</code>)</summary>
   
   ```sql
   CREATE TABLE raw AS SELECT
     column1 AS id, column2 AS a, arrow_cast(column2, 'Float32') AS a32,
     column3 AS b, 1 AS g, column4 AS k
   FROM (VALUES
     (1, arrow_cast(-0.0, 'Float64'), 1, make_array(arrow_cast(-0.0, 
'Float64'), 1.0)),
     (2, arrow_cast(0.0, 'Float64'), 0, make_array(0.0, 0.0)),
     (3, arrow_cast(1.0, 'Float64'), 2, make_array(1.0)),
     (4, arrow_cast(-0.0, 'Float64'), 3, make_array(0.0, 1.0))
   );
   CREATE TABLE canon AS SELECT
     column1 AS id, column2 AS a, arrow_cast(column2, 'Float32') AS a32,
     column3 AS b, 1 AS g, column4 AS k
   FROM (VALUES
     (1, arrow_cast(0.0, 'Float64'), 1, make_array(0.0, 1.0)),
     (2, arrow_cast(0.0, 'Float64'), 0, make_array(0.0, 0.0)),
     (3, arrow_cast(1.0, 'Float64'), 2, make_array(1.0)),
     (4, arrow_cast(0.0, 'Float64'), 3, make_array(0.0, 1.0))
   );
   SELECT a, b FROM {t} WHERE id <= 2 ORDER BY a, b;
   SELECT id, rank() OVER (ORDER BY a) FROM {t} ORDER BY id;
   SELECT id, sum(b) OVER (ORDER BY a RANGE BETWEEN CURRENT ROW AND CURRENT 
ROW) FROM {t} ORDER BY id;
   SELECT max(rn) FROM (SELECT row_number() OVER (PARTITION BY a) rn FROM {t} 
WHERE id <= 2);
   SELECT first_value(b ORDER BY a, b) FROM {t} WHERE id <= 2;
   SELECT DISTINCT ON (g) g, b FROM {t} ORDER BY g, a, b;
   SELECT id FROM {t} WHERE id <= 2 ORDER BY k;
   SELECT min(k), max(k) FROM {t} WHERE id <= 2;
   SELECT id, a32 IN (0.0, 1.0) FROM {t} ORDER BY id;
   SELECT id, a IN 
(0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0)
 FROM {t} ORDER BY id;
   SELECT id, array_has(make_array(a), 0.0), array_has_any(make_array(a), 
make_array(0.0)), array_has_all(make_array(a), make_array(0.0)) FROM {t} ORDER 
BY id;
   SELECT id, array_position(k, 0.0) FROM {t} ORDER BY id;
   SELECT id, array_replace(k, 0.0, 9.0) FROM {t} ORDER BY id;
   SELECT id, nullif(a, 0.0) FROM {t} ORDER BY id;
   SELECT id, CASE a WHEN 0.0 THEN 'eq' ELSE 'ne' END FROM {t} ORDER BY id;
   SELECT count(*) c FROM {t} GROUP BY a ORDER BY c;
   SELECT count(*) FROM {t} x JOIN {t} y ON x.a = y.a;
   SELECT count(DISTINCT a) FROM {t};
   SELECT count(*) FROM {t} x JOIN {t} y ON x.k = y.k;
   SELECT a, b FROM {t} WHERE id <= 2 ORDER BY a, b LIMIT 1;
   SELECT id, array_has(k, 0.0) FROM {t} ORDER BY id;
   SELECT id, a IN (0.0, 1.0) FROM {t} ORDER BY id;
   SELECT count(DISTINCT a), count(DISTINCT b) FROM {t};
   SELECT DISTINCT count(DISTINCT a) OVER () FROM {t};
   SELECT id, array_remove(k, 0.0) FROM {t} ORDER BY id;
   ```
   
   </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]

Reply via email to