pingcap / pingcap/tidb

WHERE` Expression with `CASE`/`DATE_FORMAT`/`CAST` Evaluates Differently When Pushed to TiKV vs. Evaluated as MergeJoin `other cond` at Root

Open
#69,295 2 comments 0 reactions 0 assignees View on GitHub
contribution may-affects-7.5 may-affects-8.1 may-affects-8.5 severity/critical sig/execution type/bug
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_tidb615_db7_min;
CREATE DATABASE repro_tidb615_db7_min;
USE repro_tidb615_db7_min;

SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
SET time_zone='+00:00';

CREATE TABLE src(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c0 DOUBLE NULL
);

INSERT INTO src(c0) VALUES (0.25);

CREATE TABLE l(
id BIGINT NOT NULL PRIMARY KEY,
c0 DOUBLE NULL
);

CREATE TABLE r(
id BIGINT NOT NULL PRIMARY KEY,
c0 DOUBLE NULL
);

INSERT INTO l SELECT id, c0 FROM src;
INSERT INTO r SELECT id, c0 FROM src;

-- Single-table: returns 1 row
SELECT 1 AS ok
FROM src
WHERE DATE_FORMAT(
CASE 0
WHEN CAST(id AS CHAR) THEN NULL
WHEN TRUE THEN c0
ELSE CAST(c0 AS DATE)
END,
(c0 OR 1) NOT LIKE TRUE
) | id;

-- Split/join via derived table: incorrectly returns empty set
SELECT 1 AS ok
FROM (
SELECT l.id, r.c0
FROM l JOIN r ON l.id = r.id
) v
WHERE DATE_FORMAT(
CASE 0
WHEN CAST(v.id AS CHAR) THEN NULL
WHEN TRUE THEN v.c0
ELSE CAST(v.c0 AS DATE)
END,
(v.c0 OR 1) NOT LIKE TRUE
) | v.id;
```

### 2. What Did You Expect to See? (Required)

Both queries operate on the same single row (`id=1, c0=0.25`). The `WHERE` condition should evaluate identically:

- `CASE 0 WHEN CAST(id AS CHAR) THEN NULL WHEN TRUE THEN c0 ELSE CAST(c0 AS DATE) END` → all branches fail, so `ELSE CAST(c0 AS DATE)` is returned.
- `(c0 OR 1) NOT LIKE TRUE` → `(0.25 OR 1)` = `1`, `1 NOT LIKE TRUE` → `0` (format string `'0'`).
- `DATE_FORMAT(cast_result, '0')` → returns the string `'0'` if the date is non‑NULL.
- `'0' | id` → bitwise OR yields a non‑zero number, which is truthy.

Thus the condition should be `TRUE` and both queries should return `1 row`.

### 3. What Did You See Instead? (Required)

- Single‑table query: returns `1 row` (correct).
- Join query: returns `Empty set` (wrong).

### 4. What Is Your TiDB Version? (Required)

Version: TiDB‑v9.0.0

### 5. Execution Plan Differences

**Single‑table (correct)** — entire `WHERE` clause pushed down to TiKV as a coprocessor selection:

```
Selection_6 [cop[tikv]] bitor(cast(date_format(cast(case(... src.c0 ...), datetime(6)), ...), bigint), src.id)
```

All expression evaluation (including `CAST(c0 AS DATE)` under `NO_ZERO_DATE` mode) happens inside TiKV.

**Join query (wrong)** — `WHERE` becomes `MergeJoin` `other cond`, evaluated at TiDB root:

```
MergeJoin_10 [root] other cond: bitor(cast(date_format(cast(case(... r.c0 ...), datetime(6)), ...), bigint), l.id)
```

The identical expression is now evaluated at the TiDB root layer after the join.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.