From a5e4c34991ed98357fafd6821eb756ad37ee0353 Mon Sep 17 00:00:00 2001 From: Galina Shalygina Date: Tue, 9 Jul 2024 17:37:04 +0200 Subject: [PATCH] MDEV-32608: Expression with constant subquery causes a crash in pushdown from HAVING The bug is caused by refixing of the constant subquery in pushdown from HAVING into WHERE optimization. Similarly to MDEV-29363 in the problematic query two references of the constant subquery are used. After the pushdown one of the references of the subquery is pushed into WHERE-clause and the second one remains as the part of the HAVING-clause. Before the represented fix, the constant subquery reference that was going to be pushed into WHERE was cleaned up and fixed. That caused the changes of the subquery itself and, therefore, changes for the second reference that remained in HAVING. These changes caused a crash. To fix this problem all constant objects that are going to be pushed into WHERE should be marked with an IMMUTABLE_FL flag. Objects marked with this flag are not cleaned up or fixed in the pushdown optimization. Approved by Igor Babaev --- mysql-test/main/having_cond_pushdown.result | 87 +++++++++++++++++++++ mysql-test/main/having_cond_pushdown.test | 26 ++++++ sql/item.cc | 19 +++++ sql/item.h | 3 +- sql/sql_lex.cc | 10 +-- 5 files changed, 138 insertions(+), 7 deletions(-) diff --git a/mysql-test/main/having_cond_pushdown.result b/mysql-test/main/having_cond_pushdown.result index e5e99fe2381..9713f014f43 100644 --- a/mysql-test/main/having_cond_pushdown.result +++ b/mysql-test/main/having_cond_pushdown.result @@ -5092,4 +5092,91 @@ SELECT * FROM v1 GROUP BY a HAVING a = (a IS NULL OR a IS NULL); a DROP VIEW v1; +# +# MDEV-32608: Expression with constant subquery causes a crash +# in pushdown from HAVING +# +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (2, 1), (3, 2); +EXPLAIN FORMAT=JSON SELECT * FROM t1 +GROUP BY b +HAVING (SELECT MAX(b) FROM t1) = a AND a + b = 3; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "having_condition": "t1.a = (subquery#2)", + "filesort": { + "sort_key": "t1.b", + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 2, + "filtered": 100, + "attached_condition": "(subquery#2) + t1.b = 3" + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 2, + "filtered": 100 + } + } + } + ] + } + } + } +} +SELECT * FROM t1 +GROUP BY b +HAVING (SELECT MAX(b) FROM t1) = a AND a + b = 3; +a b +2 1 +EXPLAIN FORMAT=JSON SELECT * FROM t1 +GROUP BY b +HAVING (SELECT MAX(b) FROM t1) = a AND a > b; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "having_condition": "t1.a = (subquery#2)", + "filesort": { + "sort_key": "t1.b", + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 2, + "filtered": 100, + "attached_condition": "(subquery#2) > t1.b" + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 2, + "filtered": 100 + } + } + } + ] + } + } + } +} +SELECT * FROM t1 +GROUP BY b +HAVING (SELECT MAX(b) FROM t1) = a AND a > b; +a b +2 1 +DROP TABLE t1; End of 10.5 tests diff --git a/mysql-test/main/having_cond_pushdown.test b/mysql-test/main/having_cond_pushdown.test index f2812fb14f2..fd414e62a8c 100644 --- a/mysql-test/main/having_cond_pushdown.test +++ b/mysql-test/main/having_cond_pushdown.test @@ -1558,4 +1558,30 @@ SELECT * FROM v1 DROP VIEW v1; +--echo # +--echo # MDEV-32608: Expression with constant subquery causes a crash +--echo # in pushdown from HAVING +--echo # + +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (2, 1), (3, 2); + +let $q= +SELECT * FROM t1 +GROUP BY b +HAVING (SELECT MAX(b) FROM t1) = a AND a + b = 3; + +eval EXPLAIN FORMAT=JSON $q; +eval $q; + +let $q= +SELECT * FROM t1 +GROUP BY b +HAVING (SELECT MAX(b) FROM t1) = a AND a > b; + +eval EXPLAIN FORMAT=JSON $q; +eval $q; + +DROP TABLE t1; + --echo End of 10.5 tests diff --git a/sql/item.cc b/sql/item.cc index 55960ab8739..e1ad7e7d13b 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1272,6 +1272,25 @@ bool Item::eq(const Item *item, bool binary_cmp) const } +Item *Item::multiple_equality_transformer(THD *thd, uchar *arg) +{ + if (const_item()) + { + /* + Mark constant item in the condition with the IMMUTABLE_FL flag. + It is needed to prevent cleanup of the sub-items of this item and following + fix_fields() call that can cause a crash on this step of the optimization. + This flag will be removed at the end of the pushdown optimization by + remove_immutable_flag_processor processor. + */ + int new_flag= IMMUTABLE_FL; + this->walk(&Item::set_extraction_flag_processor, false, + (void*)&new_flag); + } + return this; +} + + Item *Item::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) { if (!needs_charset_converter(tocs)) diff --git a/sql/item.h b/sql/item.h index 73c775a849d..6e8bcd347a4 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2295,8 +2295,7 @@ public: { return this; } virtual Item *field_transformer_for_having_pushdown(THD *thd, uchar *arg) { return this; } - virtual Item *multiple_equality_transformer(THD *thd, uchar *arg) - { return this; } + virtual Item *multiple_equality_transformer(THD *thd, uchar *arg); virtual bool expr_cache_is_needed(THD *) { return FALSE; } virtual Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); bool needs_charset_converter(uint32 length, CHARSET_INFO *tocs) const diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index e114d6de5e1..2dc5d119fd5 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -10902,7 +10902,11 @@ void mark_or_conds_to_avoid_pushdown(Item *cond) (if cond is marked with FULL_EXTRACTION_FL or cond is an AND condition and some of its parts are marked with FULL_EXTRACTION_FL) - In this case condition is transformed and pushed into attach_to_conds + + In this case condition is transformed with multiple_equality_transformer + transformer. It transforms all multiple equalities in the extracted + condition into the set of equalities. + After that the transformed condition is attached into attach_to_conds list. 2. Part of some other condition c1 that can't be entirely pushed (if с1 isn't marked with any flag). @@ -10919,10 +10923,6 @@ void mark_or_conds_to_avoid_pushdown(Item *cond) In this case build_pushable_cond() is called for c1. This method builds a clone of the c1 part that can be pushed. - Transformation mentioned above is made with multiple_equality_transformer - transformer. It transforms all multiple equalities in the extracted - condition into the set of equalities. - @note Conditions that can be pushed are collected in attach_to_conds in this way: 1. if cond is an AND condition its parts that can be pushed into WHERE