CASE Expression Constant Folding Causes Incorrect MIN(BINARY(...)) Result When Aggregation Is Pushed Down to TiKV
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
Please answer these questions before submitting your issue. Thanks!
### 1. Minimal reproduce step (Required)
```sql
DROP DATABASE IF EXISTS repro_tidb_old_17;
CREATE DATABASE repro_tidb_old_17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
USE repro_tidb_old_17;
CREATE TABLE src(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c0 DECIMAL(65,30) NULL
);
INSERT INTO src(c0) VALUES (0), (1);
CREATE TABLE l(id BIGINT NOT NULL PRIMARY KEY);
CREATE TABLE r(id BIGINT NOT NULL PRIMARY KEY, c0 DECIMAL(65,30) NULL);
INSERT INTO l SELECT id FROM src;
INSERT INTO r SELECT id, c0 FROM src;
CREATE VIEW v AS
SELECT l.id AS id, r.c0 AS c0
FROM l JOIN r ON l.id = r.id;
-- Single‑table source query (returns wrong MIN value)
SELECT
HEX(MIN(BINARY(
CASE 'ꉈP/<3|9,-'
WHEN c0 THEN '2020-11-24'
WHEN c0 THEN id
ELSE ']'
END
))) AS source_hex,
MIN(BINARY(
CASE 'ꉈP/<3|9,-'
WHEN c0 THEN '2020-11-24'
WHEN c0 THEN id
ELSE ']'
END
)) AS source_val
FROM src;
-- Split/join query via view (returns correct MIN value)
SELECT
HEX(MIN(BINARY(
CASE 'ꉈP/<3|9,-'
WHEN c0 THEN '2020-11-24'
WHEN c0 THEN id
ELSE ']'
END
))) AS split_hex,
MIN(BINARY(
CASE 'ꉈP/<3|9,-'
WHEN c0 THEN '2020-11-24'
WHEN c0 THEN id
ELSE ']'
END
)) AS split_val
FROM v;
```
### 2. What did you expect to see? (Required)
The `CASE` expression is evaluated as follows:
- The string constant `'ꉈP/<3|9,-'` is implicitly cast to `DECIMAL` for comparison, yielding `0` (no leading numeric characters).
- The `CASE` thus becomes `CASE 0 WHEN c0 THEN ... WHEN c0 THEN ... ELSE ']' END`.
- For `id=1`, `c0=0`: the first `WHEN` matches, returning `'2020-11-24'`.
- For `id=2`, `c0=1`: no match, returning `']'`.
The `MIN(BINARY(...))` should compare the two result strings as binary:
- `'2020-11-24'` → `0x32303230...` (starts with `0x32`)
- `']'` → `0x5D`
Since `0x32 < 0x5D`, the minimum should be `'2020-11-24'` (HEX `323032302D31312D3234`).
Both the single‑table and the view query are expected to return this value.
### 3. What did you see instead (Required)
- Single‑table query (`source`): returns `']'` (HEX `5D`) — **wrong**
- View query (`split`): returns `'2020-11-24'` (HEX `323032302D31312D3234`) — **correct**
### 4. What is your TiDB version? (Required)
I tested such a case in TiDB-v8.5.6. and TiDB-v9.0.0. Maybe it is an issue that exists in all the versions.
### 5. Root Cause
The mismatch arises from overly aggressive constant folding and redundant `WHEN` elimination by the TiDB optimizer.
**Constant folding of the `CASE` operand:**
The string `'ꉈP/<3|9,-'` is implicitly cast to `DECIMAL(65,30)` to match `c0`'s type, resulting in `0`. The `CASE` is transformed into `CASE 0 WHEN c0 THEN ... WHEN c0 THEN ... ELSE ']' END`.
**Redundant `WHEN` elimination (single‑table path):**
The two `WHEN c0` branches are identical. On the base table (`src`), the optimizer might try to pre‑evaluate the condition `0 = c0` for the `DECIMAL(65,30)` column. Due to precision/type handling during constant folding, the optimizer may incorrectly conclude that the comparison always returns `FALSE` or `UNKNOWN`, and thus eliminates both `WHEN` branches, folding the entire `CASE` to the constant `']'`. This folded expression is then pushed down to TiKV, which computes `MIN(BINARY(']'))` = `']'`.
**Safe execution in the join/view path:**
When the query goes through the view (which wraps a join), the optimizer cannot statically determine the values of `r.c0` and therefore does not perform the dangerous constant folding. The `CASE` expression remains intact and is evaluated at runtime in the TiDB root layer. At runtime, `0 = c0` correctly matches the row with `c0=0`, and `MIN(BINARY(...))` returns `'2020-11-24'`.
Contributor guide
Assessment
This issue has not been assessed yet.