pingcap / pingcap/tidb

executor: rebuildIndexRanges assertion fires for a prefix index with a correlated equality

Open
#70,245 0 comments 0 reactions 0 assignees View on GitHub
component/executor severity/moderate sig/planner type/bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.