From 421f7e18a5c35ed4d2ba06baceaa989410f47070 Mon Sep 17 00:00:00 2001 From: Rex Johnston Date: Thu, 8 Jan 2026 13:55:07 +1300 Subject: [PATCH] MDEV-38473 Incorrect Empty Set with HAVING clause when SELECT and GROUP BY use different aliases for the same column MDEV-29300 fix causes a wrong result by incorrect removing a wrapper to an item that needed to be wrapped for the correct result. Direct access to the item causes an incorrect table reference to be used during join evaluation. We reverting that fix. Our original problem query is this SELECT (SELECT 0 GROUP BY c1 HAVING (SELECT c1)) FROM t1 group by c1; JOIN::prepare on /* select#2 */ select 0 group by t1.t1a having (subquery#3) fixing t1.t1a in group by clause, calls fix_outer_field() this item is resolved in an outer select (#1) and it is a grouping select, so we wrap it in Item_outer_ref and set this item to unfixed for later fixing in fix_inner_refs(). JOIN::prepare continues onto the having clause and fixes (subquery#3) which calls initiates the prepare series of calls, leading to setup_fields on the fields in this JOIN, one of which is an outer reference t1a. This is resolved to the item in the next most outer select in the group by clause. This item has been wrapped with an unfixed Item_outer_ref. It is found in resolve_ref_in_select_and_group() is it expected that this item will have already been fixed, hence this call in Item_field::fix_outer_field() DBUG_ASSERT(*ref && (*ref)->fixed()); but as explained above, it isn't fixed and debug builds assert here. Because this wrapper cannot be resolved here for reasons detailed in fix_inner_refs, and we cannot remove this wrapper without potentially returning an incorrect result, we have to relax this assertion. --- mysql-test/main/subselect4.result | 23 +++++++++++++++++++++++ mysql-test/main/subselect4.test | 18 ++++++++++++++++++ sql/item.cc | 25 +++++++++++++++++++------ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/mysql-test/main/subselect4.result b/mysql-test/main/subselect4.result index c0c67707231c4..ba4e6f17a957d 100644 --- a/mysql-test/main/subselect4.result +++ b/mysql-test/main/subselect4.result @@ -3373,4 +3373,27 @@ CREATE TABLE t(c INT); SELECT (SELECT 0 GROUP BY c HAVING (SELECT c)) FROM t GROUP BY c; (SELECT 0 GROUP BY c HAVING (SELECT c)) DROP TABLE t; +# +# MDEV-38476 Wrong Result (Empty Set) with derived_merge=on using AVG() on text column in HAVING clause +# +CREATE TABLE t1(c1 TEXT); +INSERT INTO t1(c1) VALUES ('a'),('ab'),('cc'); +SELECT MAX(ca1) FROM (SELECT c1 AS ca1 FROM t1) AS ta1 +GROUP BY ca1 +HAVING AVG(ca1) RLIKE (TRUE & CHARACTER_LENGTH(ca1)); +MAX(ca1) +ab +cc +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'a' +Warning 1292 Truncated incorrect DOUBLE value: 'ab' +Warning 1292 Truncated incorrect DOUBLE value: 'cc' +TRUNCATE t1; +INSERT INTO t1 (c1) VALUES (NULL), ('abc'); +SELECT ca1 FROM (SELECT c1 AS ca1, c1 AS ca2 FROM t1) AS ta1 +GROUP BY ca2 +HAVING (ca2 = ANY (SELECT c1 FROM t1)) IS NOT TRUE; +ca1 +NULL +DROP TABLE t1; # End of 10.11 tests diff --git a/mysql-test/main/subselect4.test b/mysql-test/main/subselect4.test index 083da1647b27c..e76c4638ddf39 100644 --- a/mysql-test/main/subselect4.test +++ b/mysql-test/main/subselect4.test @@ -2695,4 +2695,22 @@ CREATE TABLE t(c INT); SELECT (SELECT 0 GROUP BY c HAVING (SELECT c)) FROM t GROUP BY c; DROP TABLE t; +--echo # +--echo # MDEV-38476 Wrong Result (Empty Set) with derived_merge=on using AVG() on text column in HAVING clause +--echo # + +CREATE TABLE t1(c1 TEXT); +INSERT INTO t1(c1) VALUES ('a'),('ab'),('cc'); +SELECT MAX(ca1) FROM (SELECT c1 AS ca1 FROM t1) AS ta1 + GROUP BY ca1 + HAVING AVG(ca1) RLIKE (TRUE & CHARACTER_LENGTH(ca1)); + +TRUNCATE t1; +INSERT INTO t1 (c1) VALUES (NULL), ('abc'); +SELECT ca1 FROM (SELECT c1 AS ca1, c1 AS ca2 FROM t1) AS ta1 + GROUP BY ca2 + HAVING (ca2 = ANY (SELECT c1 FROM t1)) IS NOT TRUE; + +DROP TABLE t1; + --echo # End of 10.11 tests diff --git a/sql/item.cc b/sql/item.cc index da3219d7545e4..4e9100e131cbe 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5782,8 +5782,6 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select) DBUG_ASSERT((*select_ref)->fixed()); return &select->ref_pointer_array[counter]; } - if (group_by_ref && (*group_by_ref)->type() == Item::REF_ITEM) - return ((Item_ref*)(*group_by_ref))->ref; if (group_by_ref) return group_by_ref; DBUG_ASSERT(FALSE); @@ -6063,8 +6061,24 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) return -1; /* Some error occurred (e.g. ambiguous names). */ if (ref != not_found_item) { - DBUG_ASSERT(*ref && (*ref)->fixed()); - prev_subselect_item->used_tables_and_const_cache_join(*ref); + DBUG_ASSERT(*ref); + + /* + If this item isn't yet fixed, it is an Item_outer_ref, wrapping an + item. If that item is fixed, we can fix this wrapper now. Otherwise + it will need to wait until the fix_inner_refs() in JOIN::prepare() + */ + if (!(*ref)->fixed() && + (*ref)->type() == Item::REF_ITEM) + { + Item_ref *item_ref= static_cast(*ref); + Item *refref= *(item_ref->ref); + if (refref->fixed()) + (*ref)->fix_fields_if_needed( thd, reference); + } + if ((*ref)->fixed()) + prev_subselect_item->used_tables_and_const_cache_join(*ref); + break; } } @@ -6105,8 +6119,7 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) Item *save; Item_ref *rf; - /* Should have been checked in resolve_ref_in_select_and_group(). */ - DBUG_ASSERT(*ref && (*ref)->fixed()); + DBUG_ASSERT(*ref); /* Here, a subset of actions performed by Item_ref::set_properties is not enough. So we pass ptr to NULL into Item_[direct]_ref