Inconsistent NULL‑Safe Equal (`<=>`) Result with Zero Date Due to TiKV Ignoring `NO_ZERO_DATE` Mode
- 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_tidb617_db8_min;
CREATE DATABASE repro_tidb617_db8_min;
USE repro_tidb617_db8_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 BOOLEAN NULL,
c1 DATETIME NULL,
c2 DECIMAL(65,30) NULL,
UNIQUE KEY(c2)
);
INSERT INTO src(c0,c1,c2) VALUES (0,NULL,0);
CREATE TABLE l(
id BIGINT NOT NULL PRIMARY KEY,
c2 DECIMAL(65,30) NULL,
c0 BOOLEAN NULL
);
CREATE TABLE r(
id BIGINT NOT NULL PRIMARY KEY,
c1 DATETIME NULL
);
INSERT INTO l SELECT id,c2,c0 FROM src;
INSERT INTO r SELECT id,c1 FROM src;
-- Single‑table query (returns empty set – wrong)
SELECT
'single' AS q,
AVG(id NOT REGEXP ((((377946620)&c1) IS NOT NULL))) AS a,
id,
c0
FROM src
WHERE c1 <=> (
CASE 0.0670996200630315
WHEN (('850389211')=('2019-04-17 06:48:12')) THEN
CASE ''
WHEN c0 THEN -1819383790
WHEN '2025-07-21' THEN c0
ELSE c0
END
ELSE CAST(c2 AS DATETIME)
END
)
GROUP BY id,c0;
-- Split/join query (returns 1 row – correct)
SELECT
'split' AS q,
AVG(id NOT REGEXP ((((377946620)&c1) IS NOT NULL))) AS a,
id,
c0
FROM (
SELECT l.id, l.c0, r.c1, l.c2
FROM l RIGHT JOIN r ON l.id = r.id
) v
WHERE c1 <=> (
CASE 0.0670996200630315
WHEN (('850389211')=('2019-04-17 06:48:12')) THEN
CASE ''
WHEN c0 THEN -1819383790
WHEN '2025-07-21' THEN c0
ELSE c0
END
ELSE CAST(c2 AS DATETIME)
END
)
GROUP BY id,c0;
```
### 2. What Did You Expect to See? (Required)
The `CASE` expression always evaluates to `CAST(c2 AS DATETIME)`. For the only row (`c2=0`, `c1=NULL`), under `NO_ZERO_DATE` mode the cast should produce `NULL` (an invalid zero date). Consequently `c1 <=> NULL` is a NULL‑safe comparison with both sides `NULL`, which is `TRUE`. The `WHERE` condition should pass, and both queries should return one row.
### 3. What Did You See Instead? (Required)
- Single‑table query: returns `Empty set` (wrong).
- Split/join query: returns `1 row` (correct).
### 4. What Is Your TiDB Version? (Required)
Version: TiDB‑v9.0.0
### 5. Execution Plan Differences
**Single‑table (wrong)** — predicate pushed down to TiKV coprocessor:
```
Selection_8 [cop[tikv]]
nulleq(
src.c1,
cast(cast(cast(src.c2, datetime BINARY), var_string(19)), datetime(6) BINARY)
)
```
The entire `CAST(c2 AS DATETIME)` chain is evaluated inside TiKV.
**Split/join (correct)** — predicate stays at TiDB root:
```
Selection_14 [root]
nulleq(
r.c1,
cast(cast(cast(l.c2, datetime BINARY), var_string(19)), datetime(6) BINARY)
)
```
The same expression is evaluated at the TiDB root layer after the join.
### 6. Root Cause
The condition reduces to `c1 <=> CAST(c2 AS DATETIME)` with `c1 = NULL` and `c2 = 0`.
- In TiDB's root evaluator, `CAST(0 AS DATETIME)` respects `NO_ZERO_DATE` and returns `NULL`. Therefore `NULL <=> NULL` is `TRUE`, and the row passes the filter.
- In TiKV's coprocessor, `CAST(0 AS DATETIME)` does not respect `NO_ZERO_DATE` and returns the zero‑date value `'0000-00-00 00:00:00'` (non‑`NULL`). The NULL‑safe comparison then becomes `NULL <=> '0000-00-00 ...'`, which is `FALSE`, and the row is filtered out.
Because the single‑table query allows the optimizer to push the predicate down to TiKV, the wrong evaluation is used. The join query prevents the push‑down (the predicate must be applied after the join), so TiDB's own evaluator is used, yielding the correct result.
Contributor guide
Research direction
Start with the provided SQL reproduction and compare the TiDB root evaluator with the TiKV coprocessor when the predicate is pushed down. Trace how CAST to DATETIME handles zero dates under NO_ZERO_DATE in both layers, then verify that the single-table and split/join queries return consistent results and that the execution plans remain correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100