executor: rebuildIndexRanges assertion fires for a prefix index with a correlated equality
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
`rebuildIndexRanges` asserts that the range detacher leaves no residual conditions, but a prefix
index combined with `prefix_col = correlated_col` does leave one. The assertion's own comment lists
prefix indexes as a case it expects to be safe, so the guard false-positives and any query of this
shape fails in builds where assertions are enabled (`intest` / `enableassert`), including unit and
integration test runs.
### 1. Minimal reproduce step (Required)
```sql
CREATE TABLE tp (name varchar(64) NOT NULL, id int NOT NULL, KEY ip (name(5), id));
INSERT INTO tp VALUES ('abcdefg',1),('abcdeXY',2),('zzz',3);
SELECT (SELECT /*+ NO_DECORRELATE() */ MIN(t2.id)
FROM tp t2 USE INDEX (ip)
WHERE t2.name = t1.name) FROM tp t1;
```
`NO_DECORRELATE()` only keeps the subquery correlated; the requirement is that
`prefix_col = correlated_col` becomes an index access condition, which
`AccessPath.SplitCorColAccessCondFromFilters` does for prefix columns.
### 2. What did you expect to see? (Required)
The query returns one row per row of `tp`. The plan already retains the predicate as a `Selection`
above the scan, so rebuilding only the access ranges is safe here:
```
└─IndexLookUp
├─IndexRangeScan(Build) range: decided by [eq(test.tp.name, test.tp.name)]
└─TopN(Probe)
└─Selection eq(test.tp.name, test.tp.name) <- residual kept by the planner
└─TableRowIDScan
```
### 3. What did you see instead (Required)
```
assert failed, rebuildIndexRanges: detacher returned residuals on correlated-access path
github.com/pingcap/tidb/pkg/util.GetRecoverError
pkg/util/util.go:298
github.com/pingcap/tidb/pkg/executor/internal/exec.Open.func1
pkg/executor/internal/exec/executor.go:613
```
The assertion is `pkg/executor/distsql.go:185`:
```go
ranges, _, remainedConds, err = ranger.DetachSimpleCondAndBuildRangeForIndex(rctx, access, idxCols, colLens, 0)
...
// - For other shouldReserve cases (prefix indexes, range predicates), the planner
// retains the original predicate in path.TableFilters, which becomes a parent
// Selection / table-side filter; rebuilding only the access ranges here is safe.
// The assert below is a regression guard in case a future planner change introduces a
// shouldReserve case that isn't covered by one of these two mechanisms.
intest.Assert(len(remainedConds) == 0, "rebuildIndexRanges: detacher returned residuals on correlated-access path")
```
The comment names prefix indexes as a case that is safe and covered, and the plan above confirms the
predicate is retained as a `Selection`. The detacher nonetheless returns it in `remainedConds`
(`shouldReserve` is true for a prefix column), so the condition being asserted does not hold for a
case the comment considers expected.
Impact is limited to builds with assertions enabled, since `intest.Assert` is behind
`//go:build intest || enableassert` (`pkg/util/intest/assert.go`). In release builds the call is
compiled out and the retained `Selection` still filters the rows, so query results should be
unaffected — this was not executed in a release build to confirm, because the assertion fires in
exactly the builds the test harness requires.
### 4. What is your TiDB version? (Required)
master at 93f713cf55 (`Release Version: v9.0.0-beta.2.pre`). Reproduced on a clean checkout.
Contributor guide
Research direction
Start with the assertion and surrounding logic in pkg/executor/distsql.go:185, then trace AccessPath.SplitCorColAccessCondFromFilters and the ranger detacher. Reproduce the issue with the SQL example under intest or enableassert and inspect the retained Selection. Done means the correlated prefix-index query runs without the false-positive assertion while preserving the expected filtering behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 62/100