pingcap / pingcap/tidb

TiKV Ignores `NO_ZERO_DATE` in Implicit String-to-`DATETIME` Cast Leading to Wrong `!=` Comparison

Open
#69,484 2 comments 0 reactions 0 assignees View on GitHub
component/tikv contribution may-affects-7.5 may-affects-8.1 may-affects-8.5 severity/critical 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_tidb626_db6;
CREATE DATABASE repro_tidb626_db6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
USE repro_tidb626_db6;

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';

CREATE TABLE src (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c0 DOUBLE NULL,
c1 DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

INSERT INTO src(c0,c1) VALUES
(0.5586655139923096, '2018-03-25 15:15:44'),
(0.17665702104568481, '2020-05-09 18:17:26'),
(0.3596702814102173, '2020-10-15 12:02:25');

CREATE TABLE l (id BIGINT NOT NULL PRIMARY KEY, c0 DOUBLE NULL) ENGINE=InnoDB;
CREATE TABLE r (id BIGINT NOT NULL PRIMARY KEY, c1 DATETIME NULL) ENGINE=InnoDB;

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

-- Single‑table query: incorrectly returns 3 rows
SELECT id, c1
FROM src
WHERE c1 != (
CASE (id LIKE c0)
WHEN '0.5556779972246342' THEN -361629223
ELSE INET_NTOA(0.1529189515040421)
END
);

-- Split/join query (via correlated subquery): correctly returns empty set
SELECT v.id, v.c1
FROM (
SELECT l.id, l.c0,
(SELECT r.c1 FROM r WHERE r.id = l.id) AS c1
FROM l
WHERE l.id IN (SELECT r.id FROM r)
) v
WHERE v.c1 != (
CASE (v.id LIKE v.c0)
WHEN '0.5556779972246342' THEN -361629223
ELSE INET_NTOA(0.1529189515040421)
END
);
```

**Supplementary diagnostic** (confirms root semantics):

```sql
SELECT id,
INET_NTOA(0.1529189515040421) AS rhs,
CAST(INET_NTOA(0.1529189515040421) AS DATETIME) AS rhs_dt,
c1 != INET_NTOA(0.1529189515040421) AS pred
FROM src;
-- Shows rhs='0.0.0.0', rhs_dt=NULL, pred=NULL → all rows should be filtered
```

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

Under `NO_ZERO_DATE` mode, the string `'0.0.0.0'` cannot be converted to a valid `DATETIME`. The implicit cast in the comparison `c1 != '0.0.0.0'` therefore yields `NULL`. In SQL's three‑valued logic, `NULL != anything` evaluates to `NULL` (unknown), which is not `TRUE`, so the row is filtered out. Consequently, the `WHERE` clause should reject all rows, and the result should be an empty set for both queries.

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

- Single‑table query: returns `3 rows` (wrong).
- Join/rewrite query: returns `Empty set` (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 [cop[tikv]] ne(src.c1, cast(case(..., "0.0.0.0"), datetime(6) BINARY))
```

The comparison `c1 != CAST('0.0.0.0' AS DATETIME)` is evaluated inside TiKV.

**Join query (correct)** — predicate remains at TiDB root as `other cond`:

```
MergeJoin [root] other cond: ne(r.c1, cast(case(..., "0.0.0.0"), datetime(6) BINARY))
```

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

### 6. Root Cause

The `CASE` expression evaluates to `INET_NTOA(0.1529189515040421)`, which returns the string `'0.0.0.0'`. The `WHERE` clause then performs `c1 != '0.0.0.0'`. Since `c1` is `DATETIME`, the string must be implicitly cast to `DATETIME` for the comparison.

- In TiDB root evaluation (used when the predicate cannot be pushed down, e.g. after a join), `CAST('0.0.0.0' AS DATETIME)` respects the `NO_ZERO_DATE` SQL mode and returns `NULL`. Therefore `c1 != NULL` evaluates to `NULL` (unknown), and the row is discarded.
- In TiKV coprocessor evaluation (used when the predicate is pushed down for single‑table scan), `CAST('0.0.0.0' AS DATETIME)` does not respect `NO_ZERO_DATE`; it incorrectly converts the string to the zero date `'0000-00-00 00:00:00'`. The comparison then becomes `c1 != '0000-00-00 00:00:00'`, which is `TRUE` for all rows, and they are incorrectly returned.

Contributor guide

Open the contributing guide

Research direction

Start by running the provided SQL reproduction under the stated sql_mode and compare TiDB root evaluation with the TiKV coprocessor path. Trace implicit string-to-DATETIME casting for the pushed-down predicate, then verify that both the single-table and join queries return an empty set without changing the intended NO_ZERO_DATE behavior.

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
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.