Bug #121646 Wrong result: EXISTS predicate lost in semijoin materialization inside nested outer joins
Description:
MySQL Community Server 9.7.1 returns incorrect results after the `replace_where_with_true` mutation relaxes the outer WHERE clause of a SELECT containing nested outer joins. The original returns 5,184 NULL values, but the mutated query returns 5,184 copies of `1`. Relaxing WHERE must retain every original result row, including multiplicities; the observed results violate that requirement.
The reduced reproduction uses two single-column tables and 73 input rows. The only query change is `WHERE a.c IS NOT NULL` to `WHERE 1 = 1`, emitted by the project's existing mutation operator. Both queries use default optimizer settings without hints.
The mutated execution plan uses semijoin materialization and omits the false subquery predicate `s.c > 1`. The original uses FirstMatch and retains that filter. These observations identify a failing optimization path; the precise internal cause has not been established.
## Expected behaviour
Both queries: 5,184 rows, each containing NULL.
## Actual behaviour
| Query | Rows | Value |
|---|---|---|
| Original | 5,184 | NULL |
| Mutated | 5,184 | `1` |
How to repeat:
-- Run in a fresh test database.
SET SESSION optimizer_switch = DEFAULT;
CREATE TABLE valscope_exists_outer (c BLOB);
CREATE TABLE valscope_exists_inner (c INT);
INSERT INTO valscope_exists_outer (c)
WITH RECURSIVE seq(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM seq WHERE n < 72
)
SELECT 'x' FROM seq;
INSERT INTO valscope_exists_inner VALUES (1);
ANALYZE TABLE valscope_exists_outer, valscope_exists_inner;
-- Original: expected and actual 5,184 NULL values.
SELECT b.c
FROM valscope_exists_outer AS a
LEFT JOIN valscope_exists_inner AS b
ON EXISTS (
SELECT 1
FROM valscope_exists_inner AS s
WHERE s.c > 1
)
RIGHT JOIN valscope_exists_outer AS d
ON a.c IS NOT NULL
WHERE a.c IS NOT NULL;
-- Mutated by replace_where_with_true: expected 5,184 NULL values,
-- actual 5,184 copies of 1.
SELECT b.c
FROM valscope_exists_outer AS a
LEFT JOIN valscope_exists_inner AS b
ON EXISTS (
SELECT 1
FROM valscope_exists_inner AS s
WHERE s.c > 1
)
RIGHT JOIN valscope_exists_outer AS d
ON a.c IS NOT NULL
WHERE 1 = 1;
Portions of this website are copyright © 2001, 2002 The PHP Group
Page generated in 0.049 sec. using MySQL 8.4.7-u6-cloud
Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the
respective copyright holders.
It is not reviewed in advance
by Oracle and does not necessarily represent the opinion of
Oracle or any other party.
Description: MySQL Community Server 9.7.1 returns incorrect results after the `replace_where_with_true` mutation relaxes the outer WHERE clause of a SELECT containing nested outer joins. The original returns 5,184 NULL values, but the mutated query returns 5,184 copies of `1`. Relaxing WHERE must retain every original result row, including multiplicities; the observed results violate that requirement. The reduced reproduction uses two single-column tables and 73 input rows. The only query change is `WHERE a.c IS NOT NULL` to `WHERE 1 = 1`, emitted by the project's existing mutation operator. Both queries use default optimizer settings without hints. The mutated execution plan uses semijoin materialization and omits the false subquery predicate `s.c > 1`. The original uses FirstMatch and retains that filter. These observations identify a failing optimization path; the precise internal cause has not been established. ## Expected behaviour Both queries: 5,184 rows, each containing NULL. ## Actual behaviour | Query | Rows | Value | |---|---|---| | Original | 5,184 | NULL | | Mutated | 5,184 | `1` | How to repeat: -- Run in a fresh test database. SET SESSION optimizer_switch = DEFAULT; CREATE TABLE valscope_exists_outer (c BLOB); CREATE TABLE valscope_exists_inner (c INT); INSERT INTO valscope_exists_outer (c) WITH RECURSIVE seq(n) AS ( SELECT 1 UNION ALL SELECT n + 1 FROM seq WHERE n < 72 ) SELECT 'x' FROM seq; INSERT INTO valscope_exists_inner VALUES (1); ANALYZE TABLE valscope_exists_outer, valscope_exists_inner; -- Original: expected and actual 5,184 NULL values. SELECT b.c FROM valscope_exists_outer AS a LEFT JOIN valscope_exists_inner AS b ON EXISTS ( SELECT 1 FROM valscope_exists_inner AS s WHERE s.c > 1 ) RIGHT JOIN valscope_exists_outer AS d ON a.c IS NOT NULL WHERE a.c IS NOT NULL; -- Mutated by replace_where_with_true: expected 5,184 NULL values, -- actual 5,184 copies of 1. SELECT b.c FROM valscope_exists_outer AS a LEFT JOIN valscope_exists_inner AS b ON EXISTS ( SELECT 1 FROM valscope_exists_inner AS s WHERE s.c > 1 ) RIGHT JOIN valscope_exists_outer AS d ON a.c IS NOT NULL WHERE 1 = 1;