alpaylan opened a new issue, #26431:
URL: https://github.com/apache/datafusion/issues/26431

   ### Describe the bug
   
   A `SortExec` with `fetch = Some(0)` over unsorted input builds a `TopK` with 
`k = 0`, and `TopKHeap::new` panics on `assert!(k > 0)` instead of returning an 
empty result.
   
   With the default configuration, `eliminate_limit` turns `LIMIT 0` into an 
empty relation first, so this is not reached. But it is reachable from plain 
SQL by setting `datafusion.optimizer.max_passes = 0`. The physical limit 
pushdown then puts `fetch=0` on the sort. Embedders that remove or replace the 
logical optimizer rules, or build `SortExec::with_fetch(Some(0))` directly, hit 
it too.
   
   ### To Reproduce
   
   
   `datafusion-cli` built from `main` 
(63a2801c1b0b766cc206c2bbebde9c52dac66f60):
   
   ```sql
   SET datafusion.optimizer.max_passes = 0;
   CREATE TABLE t0 (c0 BIGINT) AS VALUES (3), (1), (2);
   SELECT c0 FROM t0 ORDER BY c0 LIMIT 0;
   ```
   
   ```
   thread 'main' panicked at datafusion/physical-plan/src/topk/mod.rs:946:9:
   assertion failed: k > 0
   ```
   
   `EXPLAIN` shows the plan is `SortExec: TopK(fetch=0)` over the scan:
   
   ```sql
   > EXPLAIN SELECT c0 FROM t0 ORDER BY c0 LIMIT 0;
   +---------------+-------------------------------+
   | plan_type     | plan                          |
   +---------------+-------------------------------+
   | physical_plan | ┌───────────────────────────┐ |
   |               | │       SortExec(TopK)      │ |
   |               | │    --------------------   │ |
   |               | │    c0@0 ASC NULLS LAST    │ |
   |               | │                           │ |
   |               | │          limit: 0         │ |
   |               | └─────────────┬─────────────┘ |
   |               | ┌─────────────┴─────────────┐ |
   |               | │       DataSourceExec      │ |
   |               | │    --------------------   │ |
   |               | │         bytes: 128        │ |
   |               | │       format: memory      │ |
   |               | │          rows: 3          │ |
   |               | └───────────────────────────┘ |
   |               |                               |
   +---------------+-------------------------------+
   ```
   
   ### Expected behavior
   
   An empty result (0 rows), as without the `SET`. `LIMIT 0` is valid, and the 
SQL reference says the count must be a
   non-negative integer.
   
   ### Additional context
   
   In `SortExec::execute` (`datafusion/physical-plan/src/sorts/sort.rs`), the 
`(false, Some(fetch))` arm passes `fetch`
   to `TopK::try_new` without checking for 0. The `(true, Some(0))` arm is 
fine, because it uses `LimitStream`.
   
   Code references (at 63a2801c1):
   
   - The panic: `TopKHeap::new` asserts `k > 0`,
     
[`topk/mod.rs#L944-L946`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/physical-plan/src/topk/mod.rs#L944-L946).
     It is called with the fetch as `k` from `TopK::try_new`,
     
[`topk/mod.rs#L408`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/physical-plan/src/topk/mod.rs#L408).
   - The missing check: the `match (sort_satisfied, self.fetch)` in 
`SortExec::execute`,
     
[`sorts/sort.rs#L1609-L1629`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/physical-plan/src/sorts/sort.rs#L1609-L1629).
     `(true, Some(fetch))` uses `LimitStream` and handles 0. `(false, 
Some(fetch))`
     
([L1617-L1629](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/physical-plan/src/sorts/sort.rs#L1617-L1629))
     goes straight to `TopK::try_new`.
   - Why default settings don't hit it: `EliminateLimit` rewrites a literal 
`LIMIT 0` to an `EmptyRelation`,
     
[`eliminate_limit.rs#L64-L72`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/optimizer/src/eliminate_limit.rs#L64-L72).
   - Why `max_passes = 0` hits it: the logical optimizer loop runs `while i < 
max_passes`,
     
[`optimizer.rs#L621`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/optimizer/src/optimizer.rs#L621),
     so no logical rule runs
     ([config doc, 
`config.rs#L1841-L1844`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/common/src/config.rs#L1841-L1844)).
     The physical `LimitPushdown` then folds the limit into the sort through 
`with_fetch`,
     
[`limit_pushdown.rs#L276`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/physical-optimizer/src/limit_pushdown.rs#L276),
     which gives `SortExec: TopK(fetch=0)`.
   - The dynamic filter a fix must complete: `mark_topk_emitted`,
     
[`topk/mod.rs#L327`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/datafusion/physical-plan/src/topk/mod.rs#L327).
   - `LIMIT 0` is valid SQL:
     
[`select.md#L900`](https://github.com/apache/datafusion/blob/63a2801c1b0b766cc206c2bbebde9c52dac66f60/docs/source/user-guide/sql/select.md?plain=1#L900).
   
   This is the same failure as #24404 (`PartitionedTopK requires k > 0`), which 
#24405 fixed by skipping the WindowTopN rewrite when the fetch is 0. This one 
is in the plain `TopK` path.
   
   A possible fix is a `(false, Some(0))` arm that returns an empty stream 
without polling the input. It must also mark
   the TopK dynamic filter as complete, so a scan waiting on it does not hang. 
I have a patch with a unit test and can
   open a PR.
   
   (Found with an automated differential-testing tool; I reproduced it by hand 
on current `main`.)
   
   ### Willingness to contribute
   
   I would like to contribute a fix for this bug, but request guidance from the 
DataFusion community


-- 
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