BINARY expression in WHERE predicate leaks binary charset to SELECT list, causing hex display instead of readable string
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
**Bug Description**
Two logically equivalent queries return the same rows but with different column display formats:
- Direct WHERE: CONCAT result is displayed as hexadecimal
- Derived-table WHERE: CONCAT result is displayed as readable string
The WHERE predicate contains a CASE expression with ELSE (BINARY (true)). This BINARY attribute appears to leak into the outer query's type inference, causing the SELECT list to be treated as VARBINARY instead of VARCHAR.
Please answer these questions before submitting your issue. Thanks!
### 1. Minimal reproduce step (Required)
```sql
CREATE TABLE t1(c0 CHAR);
INSERT IGNORE INTO t1 VALUES ('0');
REPLACE INTO t1(c0) VALUES ('袦');
-- Query A: direct WHERE
-- Result displayed in HEX (0x302330, 0xE8A2A623A6A2E8)
SELECT CONCAT(IFNULL(t1.c0, '__NULL__'), '#', IFNULL(REVERSE(t1.c0), '__NULL__'))
FROM t1
WHERE (
(
(NOT ((t1.c0) REGEXP ((('5\'⹇q(') > (567919934)))))
OR
(NOT ((t1.c0) LIKE ((NOT (((t1.c0) AND ('d~zo*⭎')))))))
)
OR
(
CASE false
WHEN 'mU*' THEN t1.c0
WHEN t1.c0 THEN (CHAR(t1.c0) = ((0.5220625144218005) >> (t1.c0)))
ELSE (BINARY (true))
END
)
);
-- Query B: same predicate in derived table WHERE
-- Result displayed as readable string (0#0, 袦#袦)
SELECT ref0
FROM (
SELECT
CONCAT(IFNULL(t1.c0, '__NULL__'), '#', IFNULL(REVERSE(t1.c0), '__NULL__')) AS ref0,
(
(
(NOT ((t1.c0) REGEXP ((('5\'⹇q(') > (567919934)))))
OR
(NOT ((t1.c0) LIKE ((NOT (((t1.c0) AND ('d~zo*⭎')))))))
)
OR
(
CASE false
WHEN 'mU*' THEN t1.c0
WHEN t1.c0 THEN (CHAR(t1.c0) = ((0.5220625144218005) >> (t1.c0)))
ELSE (BINARY (true))
END
)
) AS ref1
FROM t1
) AS s
WHERE ref1;
```
### 2. What did you expect to see? (Required)
Both queries should return identical result sets with identical column types and display formats. The CONCAT result is a string expression, so it should be displayed as a readable string in both cases.
### 3. What did you see instead (Required)
```shell
mysql> -- Query A: direct WHERE
mysql> -- Result displayed in HEX (0x302330, 0xE8A2A623A6A2E8)
mysql> SELECT CONCAT(IFNULL(t1.c0, '__NULL__'), '#', IFNULL(REVERSE(t1.c0), '__NULL__'))
-> FROM t1
-> WHERE (
-> (
-> (NOT ((t1.c0) REGEXP ((('5\'⹇q(') > (567919934)))))
-> OR
-> (NOT ((t1.c0) LIKE ((NOT (((t1.c0) AND ('d~zo*⭎')))))))
-> )
-> OR
-> (
-> CASE false
-> WHEN 'mU*' THEN t1.c0
-> WHEN t1.c0 THEN (CHAR(t1.c0) = ((0.5220625144218005) >> (t1.c0)))
-> ELSE (BINARY (true))
-> END
-> )
-> );
+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| CONCAT(IFNULL(t1.c0, '__NULL__'), '#', IFNULL(REVERSE(t1.c0), '__NULL__')) |
+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0x302330 |
| 0xE8A2A623A6A2E8 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set, 3 warnings (0.00 sec)
mysql>
mysql> -- Query B: same predicate in derived table WHERE
mysql> -- Result displayed as readable string (0#0, 袦#袦)
mysql> SELECT ref0
-> FROM (
-> SELECT
-> CONCAT(IFNULL(t1.c0, '__NULL__'), '#', IFNULL(REVERSE(t1.c0), '__NULL__')) AS ref0,
-> (
-> (
-> (NOT ((t1.c0) REGEXP ((('5\'⹇q(') > (567919934)))))
-> OR
-> (NOT ((t1.c0) LIKE ((NOT (((t1.c0) AND ('d~zo*⭎')))))))
-> )
-> OR
-> (
-> CASE false
-> WHEN 'mU*' THEN t1.c0
-> WHEN t1.c0 THEN (CHAR(t1.c0) = ((0.5220625144218005) >> (t1.c0)))
-> ELSE (BINARY (true))
-> END
-> )
-> ) AS ref1
-> FROM t1
-> ) AS s
-> WHERE ref1;
+---------+
| ref0 |
+---------+
| 0#0 |
| 袦#袦 |
+---------+
2 rows in set, 3 warnings (0.00 sec)
```
### 4. What is your TiDB version? (Required)
Two versions: v8.5.5 and v8.5.6.
```shell
mysql> select tidb_version();
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tidb_version() |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Release Version: v8.5.6
Edition: Community
Git Commit Hash: ae18096e023780bb56bfce33698abec0d4640d0a
Git Branch: HEAD
UTC Build Time: 2026-04-24 09:13:10
GoVersion: go1.25.8
Race Enabled: false
Check Table Before Drop: false
Store: unistore |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select tidb_version();
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tidb_version() |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Release Version: v8.5.5
Edition: Community
Git Commit Hash: 1fa258b833ff113883beeba40bc130be7ce66610
Git Branch: HEAD
UTC Build Time: 2026-01-14 22:20:57
GoVersion: go1.25.5
Race Enabled: false
Check Table Before Drop: false
Store: unistore |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```
### Root Cause Analysis by agent
The WHERE predicate contains:
```sql
CASE false
WHEN 'mU*' THEN t1.c0
WHEN t1.c0 THEN (CHAR(t1.c0) = (...))
ELSE (BINARY (true))
END
```
The ELSE branch returns a BINARY string. In MySQL/TiDB, CASE expression type resolution aggregates the types of all branches. When a BINARY branch is present, the entire CASE may be inferred as VARBINARY.
In the direct WHERE path, this BINARY attribute appears to leak into the outer query context, causing the Projection (CONCAT(...)) to inherit a binary charset and be displayed as hex.
In the derived-table WHERE path, the derived table boundary isolates ref1 (the CASE expression) from ref0 (the CONCAT expression), so ref0 retains its normal character set.
Contributor guide
Assessment
This issue has not been assessed yet.