From 78735dcaf757cd71c8f0ff3d21071b0f89018150 Mon Sep 17 00:00:00 2001 From: Igor Babaev Date: Thu, 8 Jul 2021 17:47:17 -0700 Subject: MDEV-26108 Crash with query referencing twice CTE that uses embedded recursive CTE This bug could affect queries that had at least two references to a CTE that used an embedded recursive CTE. Starting from version 10.4 some code in With_element::clone_parsed_spec() that assumed a certain order of selects after parsing the specification of a CTE became not valid anymore. It could lead to global select lists where some selects were missing. If a missing CTE happened to belong to the recursive part of a recursive CTE some recursive table references were not set as references to materialized derived tables and this caused a crash of the server. Approved by Oleksandr Byelkin --- mysql-test/main/cte_nonrecursive.result | 2 +- mysql-test/main/cte_recursive.result | 19 +++++++++++++++++++ mysql-test/main/cte_recursive.test | 17 +++++++++++++++++ sql/sql_cte.cc | 10 +++++++--- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/cte_nonrecursive.result b/mysql-test/main/cte_nonrecursive.result index 040afdf7ff4..4cd466ac350 100644 --- a/mysql-test/main/cte_nonrecursive.result +++ b/mysql-test/main/cte_nonrecursive.result @@ -1126,7 +1126,7 @@ NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: -Note 1003 with cte_e as (with cte_o as (with cte_i as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 7)select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 1)select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1 union select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1)select `cte_e1`.`a` AS `a` from `cte_e` `cte_e1` where `cte_e1`.`a` > 1 union select `cte_e2`.`a` AS `a` from `cte_e` `cte_e2` +Note 1003 with cte_e as (with cte_o as (with cte_i as (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 7)/* select#3 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 1)/* select#4 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1 union /* select#5 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1)/* select#1 */ select `cte_e1`.`a` AS `a` from `cte_e` `cte_e1` where `cte_e1`.`a` > 1 union /* select#6 */ select `cte_e2`.`a` AS `a` from `cte_e` `cte_e2` drop table t1; # # MDEV-13753: embedded CTE in a VIEW created in prepared statement diff --git a/mysql-test/main/cte_recursive.result b/mysql-test/main/cte_recursive.result index 6f30de39b46..74b450ff890 100644 --- a/mysql-test/main/cte_recursive.result +++ b/mysql-test/main/cte_recursive.result @@ -4791,3 +4791,22 @@ a NULL DROP TABLE t1; # End of 10.3 tests +# +# MDEV-26108: Recursive CTE embedded into another CTE which is used twice +# +create table t1 (a int); +insert into t1 values (5), (7); +with cte_e as ( +with recursive cte_r as ( +select a from t1 union select a+1 as a from cte_r r where a < 10 +) select * from cte_r +) select * from cte_e s1, cte_e s2 where s1.a=s2.a; +a a +5 5 +7 7 +6 6 +8 8 +9 9 +10 10 +drop table t1; +# End of 10.4 tests diff --git a/mysql-test/main/cte_recursive.test b/mysql-test/main/cte_recursive.test index c3537e5bd0c..3b140b3cc6c 100644 --- a/mysql-test/main/cte_recursive.test +++ b/mysql-test/main/cte_recursive.test @@ -3087,3 +3087,20 @@ SELECT * FROM cte; DROP TABLE t1; --echo # End of 10.3 tests + +--echo # +--echo # MDEV-26108: Recursive CTE embedded into another CTE which is used twice +--echo # + +create table t1 (a int); +insert into t1 values (5), (7); + +with cte_e as ( + with recursive cte_r as ( + select a from t1 union select a+1 as a from cte_r r where a < 10 + ) select * from cte_r +) select * from cte_e s1, cte_e s2 where s1.a=s2.a; + +drop table t1; + +--echo # End of 10.4 tests diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index dfcb4e18772..c5dcc15f018 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -1038,6 +1038,7 @@ st_select_lex_unit *With_element::clone_parsed_spec(LEX *old_lex, bool parse_status= false; st_select_lex *with_select; + st_select_lex *last_clone_select; char save_end= unparsed_spec.str[unparsed_spec.length]; ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= '\0'; @@ -1124,11 +1125,14 @@ st_select_lex_unit *With_element::clone_parsed_spec(LEX *old_lex, lex->unit.include_down(with_table->select_lex); lex->unit.set_slave(with_select); lex->unit.cloned_from= spec; + last_clone_select= lex->all_selects_list; + while (last_clone_select->next_select_in_list()) + last_clone_select= last_clone_select->next_select_in_list(); old_lex->all_selects_list= (st_select_lex*) (lex->all_selects_list-> - insert_chain_before( - (st_select_lex_node **) &(old_lex->all_selects_list), - with_select)); + insert_chain_before( + (st_select_lex_node **) &(old_lex->all_selects_list), + last_clone_select)); /* Now all references to the CTE defined outside of the cloned specification -- cgit v1.2.1