Bug #120833 Index Lookup Truncates DOUBLE to BIGINT for IF(NULL, …) and GREATEST(string, double) in IN Conditions
Submitted: 2 Jul 10:32 Modified: 2 Jul 18:45
Reporter: Annie liu Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S1 (Critical)
Version:9.7.1 OS:Any
Assigned to: CPU Architecture:Any

[2 Jul 10:32] Annie liu
Description:
This is a follow‑up on a previously closed issue (https://bugs.mysql.com/bug.php?id=120801&thankws=4). You mentioned that the specific case had been fixed in MySQL 9.7, but the same root cause is still reproducible in the following two scenarios:

IF(NULL, …, double) IN (bigint_column)

GREATEST(string, double) IN (bigint_column) after splitting

When the optimizer performs an index lookup on a BIGINT column with an index, it implicitly converts the DOUBLE constant on the right side to BIGINT (truncating toward zero), thereby incorrectly matching rows where the column value is 0.
In paths without an index (e.g., a full table scan), the comparison uses exact numeric semantics and does not truncate, so the result is correct.

Actual Results and Status

Case	Query	                                    Actual Result      Status
1	Single table (src with index)	            2 rows	       Incorrect
1	Split (joined no index)	                    0 rows	       Correct
2	Single table (src with constant folding)    0 rows	       Correct
2	Split (scalar subquery rewrite)	            1 row	       Incorrect

EXPLAIN Analysis

Case 1 (single table with index):

-> Aggregate: count(0)
    -> Covering index lookup on src using i_src (c0 = if(NULL,0.353,0.433))
The lookup key is IF(NULL, 0.353, 0.433) which evaluates to 0.433... (DOUBLE). The optimizer casts it to BIGINT, truncating to 0, matching the two rows with c0=0.

Case 1 (split, no index):

-> Aggregate: count(0)
    -> Filter: (<cache>(if(NULL,0.353,0.433)) = src.c0)
        -> Table scan on src
Comparison uses numeric semantics; 0.433 ≠ 0, no matches.

Case 2 (single table, constant folding):

-> Zero input rows (Impossible WHERE noticed after reading const tables)
The optimizer evaluates GREATEST and realizes 0.191... does not equal the unique value 0, returns empty.

Case 2 (split, index lookup):

-> Aggregate: count(0)
    -> Index lookup on src using <auto_key0> (c1 = greatest('-1112434914',0.191...))
The lookup key GREATEST(...) yields 0.191..., truncated to 0, matching the row with c1=0.

How to repeat:
Case 1: IF(NULL, …, double) IN (bigint_column)

DROP DATABASE IF EXISTS repro;
CREATE DATABASE repro;
USE repro;

CREATE TABLE src (
  vp_rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  c0 BIGINT NULL
) ENGINE=InnoDB;
INSERT INTO src (c0) VALUES (0), (0), (1);
CREATE INDEX i_src ON src(c0, vp_rowid);

CREATE TABLE l (vp_rowid BIGINT NOT NULL PRIMARY KEY, c0 BIGINT NULL) ENGINE=InnoDB;
CREATE TABLE r (vp_rowid BIGINT NOT NULL PRIMARY KEY, c0 BIGINT NULL) ENGINE=InnoDB;
INSERT INTO l (vp_rowid, c0) SELECT vp_rowid, c0 FROM src;
INSERT INTO r (vp_rowid, c0) SELECT vp_rowid, c0 FROM src;

DROP TEMPORARY TABLE IF EXISTS joined;
CREATE TEMPORARY TABLE joined AS
  SELECT l.vp_rowid, l.c0 FROM l JOIN r ON l.vp_rowid = r.vp_rowid;

-- Single table query (has index, incorrect)
SELECT 'single' AS query_type, COUNT(*) AS row_count
FROM src
WHERE (IF(NULL, 0.3530399531809283, 0.43307147808600643)) IN (c0);

-- Split query (temp table no index, correct)
SELECT 'split' AS query_type, COUNT(*) AS row_count
FROM joined AS src
WHERE (IF(NULL, 0.3530399531809283, 0.43307147808600643)) IN (c0);

Case 2: GREATEST(string, double) IN (bigint_column) after splitting

DROP DATABASE IF EXISTS repro;
CREATE DATABASE repro;
USE repro;

CREATE TABLE src (
  vp_rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  c1 BIGINT NULL
) ENGINE=InnoDB;
INSERT INTO src (c1) VALUES (0);
ALTER TABLE src ADD UNIQUE KEY i2 (c1);

CREATE TABLE r (vp_rowid BIGINT NOT NULL PRIMARY KEY, c1 BIGINT NULL) ENGINE=InnoDB;
INSERT INTO r SELECT vp_rowid, c1 FROM src;

-- Single table query (optimizer sees Impossible WHERE, correct: 0 rows)
SELECT 'single' AS query_type, COUNT(*) AS row_count
FROM src
WHERE (GREATEST("-1112434914", 0.1914106003702002)) IN (c1);

-- Split query (scalar subquery hides the column, index lookup truncates, incorrect: 1 row)
SELECT 'split' AS query_type, COUNT(*) AS row_count
FROM (
  SELECT vp_rowid,
    (SELECT r.c1 FROM r WHERE r.vp_rowid = src.vp_rowid) AS c1
  FROM src
  WHERE vp_rowid IN (SELECT r.vp_rowid FROM r)
) AS src
WHERE (GREATEST("-1112434914", 0.1914106003702002)) IN (c1);
[2 Jul 18:45] Roy Lyseng
I am sorry, but MySQL 9.7.1 is a so called CPU release (Critical Patch Update) that only has security fixes. The problem mentioned will be solved in a later 9.7 release.

The second issue is verified.