lists.mariadb.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

commits

Thread Start a new thread
Threads by month
  • ----- 2025 -----
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2024 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2023 -----
  • December
  • November
  • October
  • September
  • August
  • July
commits@lists.mariadb.org

  • 14605 discussions
[Commits] f792bd1: MDEV-20751 Permission Issue With Nested CTEs
by IgorBabaev 17 Dec '20

17 Dec '20
revision-id: f792bd168cc62d64b4c0edb6251b6b8a1352c98a (mariadb-10.2.31-630-gf792bd1) parent(s): a244be7044534a59199a0f11e856be37ba6f02c8 author: Igor Babaev committer: Igor Babaev timestamp: 2020-12-16 23:37:52 -0800 message: MDEV-20751 Permission Issue With Nested CTEs Due to this bug the server reported bogus messages about lack of SELECT privileges for base tables used in the specifications of CTE tables. It happened only if such a CTE were referred to at least twice. For any non-recursive reference to CTE that is not primary the specification of the CTE is cloned. The function check_table_access() is called for such reference. The function checks privileges of the tables referenced in the specification. As no name resolution was performed for CTE references whose definitions occurred outside the specification before the call of check_table_access() that was supposed to check the access rights of the underlying tables these references were considered as references to base tables rather than references to CTEs. Yet for CTEs as well as for derived tables no privileges are needed and thus cannot be granted. The patch ensures proper name resolution of all references to CTEs before any acl checks. Approved by Oleksandr Byelkin <sanja(a)mariadb.com> --- mysql-test/r/cte_nonrecursive.result | 34 ++++++++++++++++++++++++++++++ mysql-test/t/cte_nonrecursive.test | 40 ++++++++++++++++++++++++++++++++++++ sql/sql_cte.cc | 18 ++++++++++++++-- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/cte_nonrecursive.result b/mysql-test/r/cte_nonrecursive.result index a47fdcd..be3f33f 100644 --- a/mysql-test/r/cte_nonrecursive.result +++ b/mysql-test/r/cte_nonrecursive.result @@ -1725,4 +1725,38 @@ drop table db1.t1; drop database db1; create database test; use test; +# +# MDEV-20751: query using many CTEs with grant_tables enabled +# +connection default; +CREATE DATABASE db; +USE db; +CREATE TABLE t1 (a int) ENGINE=MYISAM; +INSERT INTO t1 VALUES (3), (7), (1); +CREATE USER 'u1'@'localhost'; +GRANT USAGE ON db.* TO 'u1'@'localhost'; +GRANT SELECT ON db.t1 TO 'u1'@'localhost'; +FLUSH PRIVILEGES; +connect u1,'localhost',u1,,; +connection u1; +USE db; +WITH +cte1 AS +(SELECT a FROM t1), +cte2 AS +(SELECT cte1.a FROM t1,cte1 WHERE cte1.a = t1.a), +cte3 AS +(SELECT cte2.a FROM t1,cte1,cte2 WHERE cte1.a = t1.a AND t1.a = cte2.a), +cte4 AS +(SELECT cte2.a FROM t1,cte2 WHERE cte2.a = t1.a) +SELECT * FROM cte4 as r; +a +3 +7 +1 +disconnect u1; +connection default; +DROP USER 'u1'@'localhost'; +DROP DATABASE db; +USE test; # End of 10.2 tests diff --git a/mysql-test/t/cte_nonrecursive.test b/mysql-test/t/cte_nonrecursive.test index 0174ddb..76fe080 100644 --- a/mysql-test/t/cte_nonrecursive.test +++ b/mysql-test/t/cte_nonrecursive.test @@ -1230,4 +1230,44 @@ drop database db1; create database test; use test; +--echo # +--echo # MDEV-20751: query using many CTEs with grant_tables enabled +--echo # + +--connection default + +CREATE DATABASE db; +USE db; + +CREATE TABLE t1 (a int) ENGINE=MYISAM; +INSERT INTO t1 VALUES (3), (7), (1); + +CREATE USER 'u1'@'localhost'; +GRANT USAGE ON db.* TO 'u1'@'localhost'; +GRANT SELECT ON db.t1 TO 'u1'@'localhost'; +FLUSH PRIVILEGES; + +--connect (u1,'localhost',u1,,) +--connection u1 +USE db; + +WITH +cte1 AS +(SELECT a FROM t1), +cte2 AS +(SELECT cte1.a FROM t1,cte1 WHERE cte1.a = t1.a), +cte3 AS +(SELECT cte2.a FROM t1,cte1,cte2 WHERE cte1.a = t1.a AND t1.a = cte2.a), +cte4 AS +(SELECT cte2.a FROM t1,cte2 WHERE cte2.a = t1.a) +SELECT * FROM cte4 as r; + +--disconnect u1 +--connection default + +DROP USER 'u1'@'localhost'; +DROP DATABASE db; + +USE test; + --echo # End of 10.2 tests diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index dd764da..e07a525 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -864,8 +864,6 @@ st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, goto err; spec_tables_tail= tbl; } - if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) - goto err; if (spec_tables) { if (with_table->next_global) @@ -891,6 +889,22 @@ st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, with_select)); if (check_dependencies_in_with_clauses(lex->with_clauses_list)) res= NULL; + /* + Resolve references to CTE from the spec_tables list that has not + been resolved yet. + */ + for (TABLE_LIST *tbl= spec_tables; + tbl; + tbl= tbl->next_global) + { + if (!tbl->with) + tbl->with= with_select->find_table_def_in_with_clauses(tbl); + if (tbl == spec_tables_tail) + break; + } + if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) + goto err; + lex->sphead= NULL; // in order not to delete lex->sphead lex_end(lex); err:
1 0
0 0
[Commits] 718891c: MDEV-20751 Permission Issue With Nested CTEs
by IgorBabaev 17 Dec '20

17 Dec '20
revision-id: 718891c9501d84941c5a8e17f76444a4bdef0b77 (mariadb-10.2.31-630-g718891c) parent(s): a244be7044534a59199a0f11e856be37ba6f02c8 author: Igor Babaev committer: Igor Babaev timestamp: 2020-12-16 23:34:04 -0800 message: MDEV-20751 Permission Issue With Nested CTEs Due to this bug the server reported bogus messages about lack of SELECT privileges for base tables used in the specifications of CTE tables. It happened only if such a CTE were referred to at least twice. For any non-recursive reference to CTE that is not primary the specification of the CTE is cloned. The function check_table_access() is called for such reference. The function checks privileges of the tables referenced in the specification. As no name resolution was performed for CTE references whose definitions occurred outside the specification before the call of check_table_access() that was supposed to check the access rights of the underlying tables these references were considered as references to base tables rather than references to CTEs. Yet for CTEs as well as for derived tables no privileges are needed and thus cannot be granted. The patch ensures proper name resolution of all references to CTEs before any acl checks. Approved by Oleksandr Byelkin <sanja(a)mariadb.com> --- mysql-test/r/cte_nonrecursive.result | 34 ++++++++++++++++++++++++++++++ mysql-test/t/cte_nonrecursive.test | 40 ++++++++++++++++++++++++++++++++++++ sql/sql_cte.cc | 19 +++++++++++++++-- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/cte_nonrecursive.result b/mysql-test/r/cte_nonrecursive.result index a47fdcd..be3f33f 100644 --- a/mysql-test/r/cte_nonrecursive.result +++ b/mysql-test/r/cte_nonrecursive.result @@ -1725,4 +1725,38 @@ drop table db1.t1; drop database db1; create database test; use test; +# +# MDEV-20751: query using many CTEs with grant_tables enabled +# +connection default; +CREATE DATABASE db; +USE db; +CREATE TABLE t1 (a int) ENGINE=MYISAM; +INSERT INTO t1 VALUES (3), (7), (1); +CREATE USER 'u1'@'localhost'; +GRANT USAGE ON db.* TO 'u1'@'localhost'; +GRANT SELECT ON db.t1 TO 'u1'@'localhost'; +FLUSH PRIVILEGES; +connect u1,'localhost',u1,,; +connection u1; +USE db; +WITH +cte1 AS +(SELECT a FROM t1), +cte2 AS +(SELECT cte1.a FROM t1,cte1 WHERE cte1.a = t1.a), +cte3 AS +(SELECT cte2.a FROM t1,cte1,cte2 WHERE cte1.a = t1.a AND t1.a = cte2.a), +cte4 AS +(SELECT cte2.a FROM t1,cte2 WHERE cte2.a = t1.a) +SELECT * FROM cte4 as r; +a +3 +7 +1 +disconnect u1; +connection default; +DROP USER 'u1'@'localhost'; +DROP DATABASE db; +USE test; # End of 10.2 tests diff --git a/mysql-test/t/cte_nonrecursive.test b/mysql-test/t/cte_nonrecursive.test index 0174ddb..76fe080 100644 --- a/mysql-test/t/cte_nonrecursive.test +++ b/mysql-test/t/cte_nonrecursive.test @@ -1230,4 +1230,44 @@ drop database db1; create database test; use test; +--echo # +--echo # MDEV-20751: query using many CTEs with grant_tables enabled +--echo # + +--connection default + +CREATE DATABASE db; +USE db; + +CREATE TABLE t1 (a int) ENGINE=MYISAM; +INSERT INTO t1 VALUES (3), (7), (1); + +CREATE USER 'u1'@'localhost'; +GRANT USAGE ON db.* TO 'u1'@'localhost'; +GRANT SELECT ON db.t1 TO 'u1'@'localhost'; +FLUSH PRIVILEGES; + +--connect (u1,'localhost',u1,,) +--connection u1 +USE db; + +WITH +cte1 AS +(SELECT a FROM t1), +cte2 AS +(SELECT cte1.a FROM t1,cte1 WHERE cte1.a = t1.a), +cte3 AS +(SELECT cte2.a FROM t1,cte1,cte2 WHERE cte1.a = t1.a AND t1.a = cte2.a), +cte4 AS +(SELECT cte2.a FROM t1,cte2 WHERE cte2.a = t1.a) +SELECT * FROM cte4 as r; + +--disconnect u1 +--connection default + +DROP USER 'u1'@'localhost'; +DROP DATABASE db; + +USE test; + --echo # End of 10.2 tests diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index dd764da..aa55d8c 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -864,8 +864,6 @@ st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, goto err; spec_tables_tail= tbl; } - if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) - goto err; if (spec_tables) { if (with_table->next_global) @@ -891,6 +889,23 @@ st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, with_select)); if (check_dependencies_in_with_clauses(lex->with_clauses_list)) res= NULL; + /* + Resolve references to CTE from the spec_tables list that has not + been resolved yet. + */ + for (TABLE_LIST *tbl= spec_tables; + tbl; + tbl= tbl->next_global) + { +// With_element *barrier= get_owner()->with_recursive ? 0 : this; + if (!tbl->with) + tbl->with= with_select->find_table_def_in_with_clauses(tbl); + if (tbl == spec_tables_tail) + break; + } + if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) + goto err; + lex->sphead= NULL; // in order not to delete lex->sphead lex_end(lex); err:
1 0
0 0
[Commits] fd6d1df: MDEV-23406 Signal 8 in maria_create after recursive cte query
by IgorBabaev 16 Dec '20

16 Dec '20
revision-id: fd6d1dfb21e3a9c8681919412d05b50b60cfc05c (mariadb-10.2.31-609-gfd6d1df) parent(s): a3f7f2334a267ec4e120f70e84a8551fb502860f author: Igor Babaev committer: Igor Babaev timestamp: 2020-12-16 09:11:11 -0800 message: MDEV-23406 Signal 8 in maria_create after recursive cte query This bug could cause a crash when executing queries that used mutually recursive CTEs with system variable big_tables set to 1. It happened due to several bugs in the code that handled recursive table references referred mutually recursive CTEs. For each recursive table reference a temporary table is created that contains all rows generated for the corresponding recursive CTE table on the previous step of recursion. This temporary table should be created in the same way as the temporary table created for a regular materialized derived table using the method select_union::create_result_table(). In this case when the temporary table is created it uses the select_union::TMP_TABLE_PARAM structure as the parameter for the table construction. However the code created the temporary table using just the function create_tmp_table() and passed pointers to certain fields of the TMP_TABLE_PARAM structure used for accumulation of rows of the recursive CTE table as parameters for update. This was a mistake because now different temporary tables cannot share some TMP_TABLE_PARAM fields in a general case. Besides, depending on how mutually recursive CTE tables were defined and which of them were referred in the executed query the select_union object allocated for a recursive table reference could be allocated again after the the temporary table had been created. In this case the TMP_TABLE_PARAM object associated with the temporary table created for the recursive table reference contained unassigned fields needed for execution when Aria engine is employed as the engine for temporary tables. This patch ensures that - select_union object is created only once for any recursive table reference - any temporary table created for recursive CTEs uses its own TMP_TABLE_PARAM structure The patch also fixes a problem caused by incomplete cleanup of join tables associated with recursive table references. Approved by Oleksandr Byelkin <sanja(a)mariadb.com> --- mysql-test/r/cte_recursive.result | 219 ++++++++++++++++++++++++++++++++++++++ mysql-test/t/cte_recursive.test | 94 ++++++++++++++++ sql/sql_class.h | 12 ++- sql/sql_cte.cc | 7 +- sql/sql_derived.cc | 9 +- sql/sql_select.cc | 4 - sql/sql_union.cc | 64 ++++++----- 7 files changed, 370 insertions(+), 39 deletions(-) diff --git a/mysql-test/r/cte_recursive.result b/mysql-test/r/cte_recursive.result index 5090a0f..b6b4ed7 100644 --- a/mysql-test/r/cte_recursive.result +++ b/mysql-test/r/cte_recursive.result @@ -4235,5 +4235,224 @@ drop database db1; create database test; use test; # +# MDEV-23406: query with mutually recursive CTEs when big_tables=1 +# +set @save_big_tables=@@big_tables; +set big_tables=1; +create table folks(id int, name char(32), dob date, father int, mother int); +insert into folks values +(100, 'Me', '2000-01-01', 20, 30), +(20, 'Dad', '1970-02-02', 10, 9), +(30, 'Mom', '1975-03-03', 8, 7), +(10, 'Grandpa Bill', '1940-04-05', null, null), +(9, 'Grandma Ann', '1941-10-15', null, null), +(25, 'Uncle Jim', '1968-11-18', 8, 7), +(98, 'Sister Amy', '2001-06-20', 20, 30), +(7, 'Grandma Sally', '1943-08-23', null, 6), +(8, 'Grandpa Ben', '1940-10-21', null, null), +(6, 'Grandgrandma Martha', '1923-05-17', null, null), +(67, 'Cousin Eddie', '1992-02-28', 25, 27), +(27, 'Auntie Melinda', '1971-03-29', null, null); +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +union +select h.*, w.* +from folks v, folks h, folks w +where v.name = 'Me' and +(v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +explain with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +union +select h.*, w.* +from folks v, folks h, folks w +where v.name = 'Me' and +(v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY <derived3> ALL NULL NULL NULL NULL 1728 +4 DERIVED <derived3> ALL NULL NULL NULL NULL 1728 +5 RECURSIVE UNION <derived3> ALL NULL NULL NULL NULL 1728 +NULL UNION RESULT <union4,5> ALL NULL NULL NULL NULL NULL +3 DERIVED v ALL NULL NULL NULL NULL 12 Using where +3 DERIVED h ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) +3 DERIVED w ALL NULL NULL NULL NULL 12 Using where; Using join buffer (incremental, BNL join) +2 RECURSIVE UNION <derived4> ALL NULL NULL NULL NULL 2 +2 RECURSIVE UNION h ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) +2 RECURSIVE UNION w ALL NULL NULL NULL NULL 12 Using where; Using join buffer (incremental, BNL join) +NULL UNION RESULT <union3,2> ALL NULL NULL NULL NULL NULL +prepare stmt from "with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +union +select h.*, w.* +from folks v, folks h, folks w +where v.name = 'Me' and +(v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples"; +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +deallocate prepare stmt; +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select * +from folks +where name = 'Me' + union all +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union all +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +explain with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select * +from folks +where name = 'Me' + union all +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union all +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY <derived2> ALL NULL NULL NULL NULL 2 +3 DERIVED folks ALL NULL NULL NULL NULL 12 Using where +4 RECURSIVE UNION <derived2> ALL NULL NULL NULL NULL 2 +5 RECURSIVE UNION <derived2> ALL NULL NULL NULL NULL 2 +NULL UNION RESULT <union3,4,5> ALL NULL NULL NULL NULL NULL +2 DERIVED h ALL NULL NULL NULL NULL 12 +2 DERIVED <derived3> ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) +2 DERIVED w ALL NULL NULL NULL NULL 12 Using where; Using join buffer (incremental, BNL join) +prepare stmt from "with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select * +from folks +where name = 'Me' + union all +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union all +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples"; +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +deallocate prepare stmt; +drop table folks; +set big_tables=@save_big_tables; +# # End of 10.2 tests # diff --git a/mysql-test/t/cte_recursive.test b/mysql-test/t/cte_recursive.test index f902ac2..849e76b 100644 --- a/mysql-test/t/cte_recursive.test +++ b/mysql-test/t/cte_recursive.test @@ -2726,5 +2726,99 @@ create database test; use test; --echo # +--echo # MDEV-23406: query with mutually recursive CTEs when big_tables=1 +--echo # + +set @save_big_tables=@@big_tables; +set big_tables=1; + +create table folks(id int, name char(32), dob date, father int, mother int); + +insert into folks values +(100, 'Me', '2000-01-01', 20, 30), +(20, 'Dad', '1970-02-02', 10, 9), +(30, 'Mom', '1975-03-03', 8, 7), +(10, 'Grandpa Bill', '1940-04-05', null, null), +(9, 'Grandma Ann', '1941-10-15', null, null), +(25, 'Uncle Jim', '1968-11-18', 8, 7), +(98, 'Sister Amy', '2001-06-20', 20, 30), +(7, 'Grandma Sally', '1943-08-23', null, 6), +(8, 'Grandpa Ben', '1940-10-21', null, null), +(6, 'Grandgrandma Martha', '1923-05-17', null, null), +(67, 'Cousin Eddie', '1992-02-28', 25, 27), +(27, 'Auntie Melinda', '1971-03-29', null, null); + +let q= +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, + w_id, w_name, w_dob, w_father, w_mother) +as +( + select h.*, w.* + from folks h, folks w, coupled_ancestors a + where a.father = h.id AND a.mother = w.id + union + select h.*, w.* + from folks v, folks h, folks w + where v.name = 'Me' and + (v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( + select h_id, h_name, h_dob, h_father, h_mother + from ancestor_couples + union + select w_id, w_name, w_dob, w_father, w_mother + from ancestor_couples +) +select h_name, h_dob, w_name, w_dob + from ancestor_couples; + +eval $q; +eval explain $q; +eval prepare stmt from "$q"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +let $q= +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, + w_id, w_name, w_dob, w_father, w_mother) +as +( + select h.*, w.* + from folks h, folks w, coupled_ancestors a + where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( + select * + from folks + where name = 'Me' + union all + select h_id, h_name, h_dob, h_father, h_mother + from ancestor_couples + union all + select w_id, w_name, w_dob, w_father, w_mother + from ancestor_couples +) +select h_name, h_dob, w_name, w_dob + from ancestor_couples; + +eval $q; +eval explain $q; +eval prepare stmt from "$q"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +drop table folks; + +set big_tables=@save_big_tables; + +--echo # --echo # End of 10.2 tests --echo # diff --git a/sql/sql_class.h b/sql/sql_class.h index 84f188b..20edbde 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -5151,10 +5151,15 @@ class select_union_recursive :public select_union public: /* The temporary table with the new records generated by one iterative step */ TABLE *incr_table; + /* The TMP_TABLE_PARAM structure used to create incr_table */ + TMP_TABLE_PARAM incr_table_param; /* One of tables from the list rec_tables (determined dynamically) */ TABLE *first_rec_table_to_update; - /* The temporary tables used for recursive table references */ - List<TABLE> rec_tables; + /* + The list of all recursive table references to the CTE for whose + specification this select_union_recursive was created + */ + List<TABLE_LIST> rec_table_refs; /* The count of how many times cleanup() was called with cleaned==false for the unit specifying the recursive CTE for which this object was created @@ -5164,7 +5169,8 @@ class select_union_recursive :public select_union select_union_recursive(THD *thd_arg): select_union(thd_arg), - incr_table(0), first_rec_table_to_update(0), cleanup_count(0) {}; + incr_table(0), first_rec_table_to_update(0), cleanup_count(0) + { incr_table_param.init(); }; int send_data(List<Item> &items); bool create_result_table(THD *thd, List<Item> *column_types, diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index a8bccf0..dd764da 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -1429,10 +1429,11 @@ void With_element::print(String *str, enum_query_type query_type) bool With_element::instantiate_tmp_tables() { - List_iterator_fast<TABLE> li(rec_result->rec_tables); - TABLE *rec_table; - while ((rec_table= li++)) + List_iterator_fast<TABLE_LIST> li(rec_result->rec_table_refs); + TABLE_LIST *rec_tbl; + while ((rec_tbl= li++)) { + TABLE *rec_table= rec_tbl->table; if (!rec_table->is_created() && instantiate_tmp_table(rec_table, rec_table->s->key_info, diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 5379dd4..33f323b 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -677,7 +677,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) if (derived->is_with_table_recursive_reference()) { /* Here 'derived" is a secondary recursive table reference */ - unit->with_element->rec_result->rec_tables.push_back(derived->table); + unit->with_element->rec_result->rec_table_refs.push_back(derived); } } DBUG_ASSERT(derived->table || res); @@ -733,7 +733,9 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) derived->fill_me= FALSE; - if (!(derived->derived_result= new (thd->mem_root) select_union(thd))) + if ((!derived->is_with_table_recursive_reference() || + !derived->derived_result) && + !(derived->derived_result= new (thd->mem_root) select_union(thd))) DBUG_RETURN(TRUE); // out of memory lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED; @@ -752,7 +754,8 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) Depending on the result field translation will or will not be created. */ - if (derived->init_derived(thd, FALSE)) + if (!derived->is_with_table_recursive_reference() && + derived->init_derived(thd, FALSE)) goto exit; /* diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 8b4401b..d5e5a79 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11929,10 +11929,6 @@ void JOIN_TAB::cleanup() { DBUG_ENTER("JOIN_TAB::cleanup"); - if (tab_list && tab_list->is_with_table_recursive_reference() && - tab_list->with->is_cleaned()) - DBUG_VOID_RETURN; - DBUG_PRINT("enter", ("tab: %p table %s.%s", this, (table ? table->s->db.str : "?"), diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 7716f79..7baedfb 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -211,7 +211,10 @@ select_union_recursive::create_result_table(THD *thd_arg, create_table, keep_row_order)) return true; - if (! (incr_table= create_tmp_table(thd_arg, &tmp_table_param, *column_types, + incr_table_param.init(); + incr_table_param.field_count= column_types->elements; + incr_table_param.bit_fields_as_long= bit_fields_as_long; + if (! (incr_table= create_tmp_table(thd_arg, &incr_table_param, *column_types, (ORDER*) 0, false, 1, options, HA_POS_ERROR, "", true, keep_row_order))) @@ -221,20 +224,6 @@ select_union_recursive::create_result_table(THD *thd_arg, for (uint i=0; i < table->s->fields; i++) incr_table->field[i]->flags &= ~(PART_KEY_FLAG | PART_INDIRECT_KEY_FLAG); - TABLE *rec_table= 0; - if (! (rec_table= create_tmp_table(thd_arg, &tmp_table_param, *column_types, - (ORDER*) 0, false, 1, - options, HA_POS_ERROR, alias, - true, keep_row_order))) - return true; - - rec_table->keys_in_use_for_query.clear_all(); - for (uint i=0; i < table->s->fields; i++) - rec_table->field[i]->flags &= ~(PART_KEY_FLAG | PART_INDIRECT_KEY_FLAG); - - if (rec_tables.push_back(rec_table)) - return true; - return false; } @@ -272,23 +261,25 @@ void select_union_recursive::cleanup() free_tmp_table(thd, incr_table); } - List_iterator<TABLE> it(rec_tables); - TABLE *tab; - while ((tab= it++)) + List_iterator<TABLE_LIST> it(rec_table_refs); + TABLE_LIST *tbl; + while ((tbl= it++)) { + TABLE *tab= tbl->table; if (tab->is_created()) { tab->file->extra(HA_EXTRA_RESET_STATE); tab->file->ha_delete_all_rows(); } - /* + /* The table will be closed later in close_thread_tables(), because it might be used in the statements like ANALYZE WITH r AS (...) SELECT * from r - where r is defined through recursion. + where r is defined through recursion. */ tab->next= thd->rec_tables; thd->rec_tables= tab; + tbl->derived_result= 0; } } @@ -715,9 +706,29 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, goto err; if (!derived->table) { - derived->table= with_element->rec_result->rec_tables.head(); - if (derived->derived_result) - derived->derived_result->table= derived->table; + bool res= false; + + if ((!derived->is_with_table_recursive_reference() || + !derived->derived_result) && + !(derived->derived_result= new (thd->mem_root) select_union(thd))) + goto err; // out of memory + thd->create_tmp_table_for_derived= TRUE; + res= derived->derived_result->create_result_table(thd, + &types, + FALSE, + create_options, + derived->alias, + FALSE, FALSE); + thd->create_tmp_table_for_derived= FALSE; + if (res) + goto err; + derived->derived_result->set_unit(this); + derived->table= derived->derived_result->table; + if (derived->is_with_table_recursive_reference()) + { + /* Here 'derived" is the primary recursive table reference */ + derived->with->rec_result->rec_table_refs.push_back(derived); + } } with_element->mark_as_with_prepared_anchor(); is_rec_result_table_created= true; @@ -1272,11 +1283,11 @@ bool st_select_lex_unit::exec_recursive() TABLE *incr_table= with_element->rec_result->incr_table; st_select_lex *end= NULL; bool is_unrestricted= with_element->is_unrestricted(); - List_iterator_fast<TABLE> li(with_element->rec_result->rec_tables); + List_iterator_fast<TABLE_LIST> li(with_element->rec_result->rec_table_refs); TMP_TABLE_PARAM *tmp_table_param= &with_element->rec_result->tmp_table_param; ha_rows examined_rows= 0; bool was_executed= executed; - TABLE *rec_table; + TABLE_LIST *rec_tbl; DBUG_ENTER("st_select_lex_unit::exec_recursive"); @@ -1335,8 +1346,9 @@ bool st_select_lex_unit::exec_recursive() else with_element->level++; - while ((rec_table= li++)) + while ((rec_tbl= li++)) { + TABLE *rec_table= rec_tbl->table; saved_error= incr_table->insert_all_rows_into_tmp_table(thd, rec_table, tmp_table_param,
1 0
0 0
[Commits] 805fcc4c202: MDEV-9750: Quick memory exhaustion with 'extended_keys=on' ...
by psergey 15 Dec '20

15 Dec '20
revision-id: 805fcc4c202d601b6bf4f2a39aa15c1859c6f4db (mariadb-10.4.11-421-g805fcc4c202) parent(s): 8f7e4642c56bd03327947043c5d93a7cbfaaaed3 author: Sergei Petrunia committer: Sergei Petrunia timestamp: 2020-12-15 18:24:16 +0300 message: MDEV-9750: Quick memory exhaustion with 'extended_keys=on' ... Part #2: Add debug code to verify SEL_ARG graph weight --- sql/opt_range.cc | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- sql/opt_range.h | 3 +++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/sql/opt_range.cc b/sql/opt_range.cc index eb093e39011..aef9654c646 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -9930,11 +9930,53 @@ get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ARG *root1) return 0; } +#ifndef DBUG_OFF +/* + Verify SEL_TREE's weight. + + Recompute the weight and compare +*/ +uint SEL_ARG::verify_weight() +{ + uint computed_weight= 0; + SEL_ARG *first_arg= first(); + + if (first_arg) + { + for (SEL_ARG *arg= first_arg; arg; arg= arg->next) + { + computed_weight += 1; + if (arg->next_key_part) + computed_weight+= arg->next_key_part->verify_weight(); + } + } + else + { + // first()=NULL means this is a special kind of SEL_ARG, e.g. + // SEL_ARG with type=MAYBE_KEY + computed_weight= 1; + if (next_key_part) + computed_weight += next_key_part->verify_weight(); + } + + if (computed_weight != weight) + { + sql_print_error("SEL_ARG weight mismatch: computed %u have %u\n", + computed_weight, weight); + } + return computed_weight; +} +#endif static SEL_ARG *key_or_with_limit(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2) { - return enforce_sel_arg_weight_limit(key_or(param, key1, key2)); + SEL_ARG *res= enforce_sel_arg_weight_limit(key_or(param, key1, key2)); +#ifndef DBUG_OFF + if (res) + res->verify_weight(); +#endif + return res; } @@ -9942,7 +9984,12 @@ static SEL_ARG *key_and_with_limit(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2, uint clone_flag) { - return enforce_sel_arg_weight_limit(key_and(param, key1, key2, clone_flag)); + SEL_ARG *res= enforce_sel_arg_weight_limit(key_and(param, key1, key2, clone_flag)); +#ifndef DBUG_OFF + if (res) + res->verify_weight(); +#endif + return res; } diff --git a/sql/opt_range.h b/sql/opt_range.h index d37339707b8..14b52d358b0 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -309,6 +309,9 @@ class SEL_ARG :public Sql_alloc */ uint weight; enum { MAX_WEIGHT = 32000 }; +#ifndef DBUG_OFF + uint verify_weight(); +#endif /* See RANGE_OPT_PARAM::alloced_sel_args */ enum { MAX_SEL_ARGS = 16000 };
1 0
0 0
[Commits] fedaf60: MDEV-23406 Signal 8 in maria_create after recursive cte query
by IgorBabaev 15 Dec '20

15 Dec '20
revision-id: fedaf60ceb0d6898c9e2611c9334d9e5d8ddc5e8 (mariadb-10.2.31-609-gfedaf60) parent(s): a3f7f2334a267ec4e120f70e84a8551fb502860f author: Igor Babaev committer: Igor Babaev timestamp: 2020-12-14 23:17:45 -0800 message: MDEV-23406 Signal 8 in maria_create after recursive cte query This bug could cause a crash when executing queries that used mutually recursive CTEs with system variable big_tables set to 1. It happened due to several bugs in the code that handled recursive table references referred mutually recursive CTEs. For each recursive table reference a temporary table is created that contains all rows generated for the corresponding recursive CTE table on the previous step of recursion. This temporary table should be created in the same way as the temporary table created for a regular materialized derived table using the method select_union::create_result_table(). In this case when the temporary table is created it uses the select_union::TMP_TABLE_PARAM structure as the parameter for the table construction. However the code created the temporary table using just the function create_tmp_table() and passed pointers to certain fields of the TMP_TABLE_PARAM structure used for accumulation of rows of the recursive CTE table as parameters for update. This was a mistake because now different temporary tables cannot share some TMP_TABLE_PARAM fields in a general case. Besides, depending on how mutually recursive CTE tables were defined and which of them were referred in the executed query the select_union object allocated for a recursive table reference could be allocated again after the the temporary table had been created. In this case the TMP_TABLE_PARAM object associated with the temporary table created for the recursive table reference contained unassigned fields needed for execution when Aria engine is employed as the engine for temporary tables. This patch ensures that - select_union object is created only once for any recursive table reference - any temporary table created for recursive CTEs uses its own TMP_TABLE_PARAM structure The patch also fixes a problem caused by incomplete cleanup of join tables associated with recursive table references. --- mysql-test/r/cte_recursive.result | 219 ++++++++++++++++++++++++++++++++++++++ mysql-test/t/cte_recursive.test | 94 ++++++++++++++++ sql/sql_class.h | 12 ++- sql/sql_cte.cc | 7 +- sql/sql_derived.cc | 9 +- sql/sql_select.cc | 4 - sql/sql_union.cc | 67 +++++++----- 7 files changed, 372 insertions(+), 40 deletions(-) diff --git a/mysql-test/r/cte_recursive.result b/mysql-test/r/cte_recursive.result index 5090a0f..b6b4ed7 100644 --- a/mysql-test/r/cte_recursive.result +++ b/mysql-test/r/cte_recursive.result @@ -4235,5 +4235,224 @@ drop database db1; create database test; use test; # +# MDEV-23406: query with mutually recursive CTEs when big_tables=1 +# +set @save_big_tables=@@big_tables; +set big_tables=1; +create table folks(id int, name char(32), dob date, father int, mother int); +insert into folks values +(100, 'Me', '2000-01-01', 20, 30), +(20, 'Dad', '1970-02-02', 10, 9), +(30, 'Mom', '1975-03-03', 8, 7), +(10, 'Grandpa Bill', '1940-04-05', null, null), +(9, 'Grandma Ann', '1941-10-15', null, null), +(25, 'Uncle Jim', '1968-11-18', 8, 7), +(98, 'Sister Amy', '2001-06-20', 20, 30), +(7, 'Grandma Sally', '1943-08-23', null, 6), +(8, 'Grandpa Ben', '1940-10-21', null, null), +(6, 'Grandgrandma Martha', '1923-05-17', null, null), +(67, 'Cousin Eddie', '1992-02-28', 25, 27), +(27, 'Auntie Melinda', '1971-03-29', null, null); +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +union +select h.*, w.* +from folks v, folks h, folks w +where v.name = 'Me' and +(v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +explain with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +union +select h.*, w.* +from folks v, folks h, folks w +where v.name = 'Me' and +(v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY <derived3> ALL NULL NULL NULL NULL 1728 +4 DERIVED <derived3> ALL NULL NULL NULL NULL 1728 +5 RECURSIVE UNION <derived3> ALL NULL NULL NULL NULL 1728 +NULL UNION RESULT <union4,5> ALL NULL NULL NULL NULL NULL +3 DERIVED v ALL NULL NULL NULL NULL 12 Using where +3 DERIVED h ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) +3 DERIVED w ALL NULL NULL NULL NULL 12 Using where; Using join buffer (incremental, BNL join) +2 RECURSIVE UNION <derived4> ALL NULL NULL NULL NULL 2 +2 RECURSIVE UNION h ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) +2 RECURSIVE UNION w ALL NULL NULL NULL NULL 12 Using where; Using join buffer (incremental, BNL join) +NULL UNION RESULT <union3,2> ALL NULL NULL NULL NULL NULL +prepare stmt from "with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +union +select h.*, w.* +from folks v, folks h, folks w +where v.name = 'Me' and +(v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples"; +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +deallocate prepare stmt; +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select * +from folks +where name = 'Me' + union all +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union all +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +explain with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select * +from folks +where name = 'Me' + union all +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union all +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY <derived2> ALL NULL NULL NULL NULL 2 +3 DERIVED folks ALL NULL NULL NULL NULL 12 Using where +4 RECURSIVE UNION <derived2> ALL NULL NULL NULL NULL 2 +5 RECURSIVE UNION <derived2> ALL NULL NULL NULL NULL 2 +NULL UNION RESULT <union3,4,5> ALL NULL NULL NULL NULL NULL +2 DERIVED h ALL NULL NULL NULL NULL 12 +2 DERIVED <derived3> ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) +2 DERIVED w ALL NULL NULL NULL NULL 12 Using where; Using join buffer (incremental, BNL join) +prepare stmt from "with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, +w_id, w_name, w_dob, w_father, w_mother) +as +( +select h.*, w.* +from folks h, folks w, coupled_ancestors a +where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( +select * +from folks +where name = 'Me' + union all +select h_id, h_name, h_dob, h_father, h_mother +from ancestor_couples +union all +select w_id, w_name, w_dob, w_father, w_mother +from ancestor_couples +) +select h_name, h_dob, w_name, w_dob +from ancestor_couples"; +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +execute stmt; +h_name h_dob w_name w_dob +Dad 1970-02-02 Mom 1975-03-03 +Grandpa Bill 1940-04-05 Grandma Ann 1941-10-15 +Grandpa Ben 1940-10-21 Grandma Sally 1943-08-23 +deallocate prepare stmt; +drop table folks; +set big_tables=@save_big_tables; +# # End of 10.2 tests # diff --git a/mysql-test/t/cte_recursive.test b/mysql-test/t/cte_recursive.test index f902ac2..849e76b 100644 --- a/mysql-test/t/cte_recursive.test +++ b/mysql-test/t/cte_recursive.test @@ -2726,5 +2726,99 @@ create database test; use test; --echo # +--echo # MDEV-23406: query with mutually recursive CTEs when big_tables=1 +--echo # + +set @save_big_tables=@@big_tables; +set big_tables=1; + +create table folks(id int, name char(32), dob date, father int, mother int); + +insert into folks values +(100, 'Me', '2000-01-01', 20, 30), +(20, 'Dad', '1970-02-02', 10, 9), +(30, 'Mom', '1975-03-03', 8, 7), +(10, 'Grandpa Bill', '1940-04-05', null, null), +(9, 'Grandma Ann', '1941-10-15', null, null), +(25, 'Uncle Jim', '1968-11-18', 8, 7), +(98, 'Sister Amy', '2001-06-20', 20, 30), +(7, 'Grandma Sally', '1943-08-23', null, 6), +(8, 'Grandpa Ben', '1940-10-21', null, null), +(6, 'Grandgrandma Martha', '1923-05-17', null, null), +(67, 'Cousin Eddie', '1992-02-28', 25, 27), +(27, 'Auntie Melinda', '1971-03-29', null, null); + +let q= +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, + w_id, w_name, w_dob, w_father, w_mother) +as +( + select h.*, w.* + from folks h, folks w, coupled_ancestors a + where a.father = h.id AND a.mother = w.id + union + select h.*, w.* + from folks v, folks h, folks w + where v.name = 'Me' and + (v.father = h.id AND v.mother= w.id) +), +coupled_ancestors (id, name, dob, father, mother) +as +( + select h_id, h_name, h_dob, h_father, h_mother + from ancestor_couples + union + select w_id, w_name, w_dob, w_father, w_mother + from ancestor_couples +) +select h_name, h_dob, w_name, w_dob + from ancestor_couples; + +eval $q; +eval explain $q; +eval prepare stmt from "$q"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +let $q= +with recursive +ancestor_couples(h_id, h_name, h_dob, h_father, h_mother, + w_id, w_name, w_dob, w_father, w_mother) +as +( + select h.*, w.* + from folks h, folks w, coupled_ancestors a + where a.father = h.id AND a.mother = w.id +), +coupled_ancestors (id, name, dob, father, mother) +as +( + select * + from folks + where name = 'Me' + union all + select h_id, h_name, h_dob, h_father, h_mother + from ancestor_couples + union all + select w_id, w_name, w_dob, w_father, w_mother + from ancestor_couples +) +select h_name, h_dob, w_name, w_dob + from ancestor_couples; + +eval $q; +eval explain $q; +eval prepare stmt from "$q"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +drop table folks; + +set big_tables=@save_big_tables; + +--echo # --echo # End of 10.2 tests --echo # diff --git a/sql/sql_class.h b/sql/sql_class.h index 84f188b..20edbde 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -5151,10 +5151,15 @@ class select_union_recursive :public select_union public: /* The temporary table with the new records generated by one iterative step */ TABLE *incr_table; + /* The TMP_TABLE_PARAM structure used to create incr_table */ + TMP_TABLE_PARAM incr_table_param; /* One of tables from the list rec_tables (determined dynamically) */ TABLE *first_rec_table_to_update; - /* The temporary tables used for recursive table references */ - List<TABLE> rec_tables; + /* + The list of all recursive table references to the CTE for whose + specification this select_union_recursive was created + */ + List<TABLE_LIST> rec_table_refs; /* The count of how many times cleanup() was called with cleaned==false for the unit specifying the recursive CTE for which this object was created @@ -5164,7 +5169,8 @@ class select_union_recursive :public select_union select_union_recursive(THD *thd_arg): select_union(thd_arg), - incr_table(0), first_rec_table_to_update(0), cleanup_count(0) {}; + incr_table(0), first_rec_table_to_update(0), cleanup_count(0) + { incr_table_param.init(); }; int send_data(List<Item> &items); bool create_result_table(THD *thd, List<Item> *column_types, diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index a8bccf0..dd764da 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -1429,10 +1429,11 @@ void With_element::print(String *str, enum_query_type query_type) bool With_element::instantiate_tmp_tables() { - List_iterator_fast<TABLE> li(rec_result->rec_tables); - TABLE *rec_table; - while ((rec_table= li++)) + List_iterator_fast<TABLE_LIST> li(rec_result->rec_table_refs); + TABLE_LIST *rec_tbl; + while ((rec_tbl= li++)) { + TABLE *rec_table= rec_tbl->table; if (!rec_table->is_created() && instantiate_tmp_table(rec_table, rec_table->s->key_info, diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 5379dd4..33f323b 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -677,7 +677,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) if (derived->is_with_table_recursive_reference()) { /* Here 'derived" is a secondary recursive table reference */ - unit->with_element->rec_result->rec_tables.push_back(derived->table); + unit->with_element->rec_result->rec_table_refs.push_back(derived); } } DBUG_ASSERT(derived->table || res); @@ -733,7 +733,9 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) derived->fill_me= FALSE; - if (!(derived->derived_result= new (thd->mem_root) select_union(thd))) + if ((!derived->is_with_table_recursive_reference() || + !derived->derived_result) && + !(derived->derived_result= new (thd->mem_root) select_union(thd))) DBUG_RETURN(TRUE); // out of memory lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED; @@ -752,7 +754,8 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) Depending on the result field translation will or will not be created. */ - if (derived->init_derived(thd, FALSE)) + if (!derived->is_with_table_recursive_reference() && + derived->init_derived(thd, FALSE)) goto exit; /* diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 8b4401b..d5e5a79 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11929,10 +11929,6 @@ void JOIN_TAB::cleanup() { DBUG_ENTER("JOIN_TAB::cleanup"); - if (tab_list && tab_list->is_with_table_recursive_reference() && - tab_list->with->is_cleaned()) - DBUG_VOID_RETURN; - DBUG_PRINT("enter", ("tab: %p table %s.%s", this, (table ? table->s->db.str : "?"), diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 7716f79..adad1c5 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -211,7 +211,10 @@ select_union_recursive::create_result_table(THD *thd_arg, create_table, keep_row_order)) return true; - if (! (incr_table= create_tmp_table(thd_arg, &tmp_table_param, *column_types, + incr_table_param.init(); + incr_table_param.field_count= column_types->elements; + incr_table_param.bit_fields_as_long= bit_fields_as_long; + if (! (incr_table= create_tmp_table(thd_arg, &incr_table_param, *column_types, (ORDER*) 0, false, 1, options, HA_POS_ERROR, "", true, keep_row_order))) @@ -221,20 +224,6 @@ select_union_recursive::create_result_table(THD *thd_arg, for (uint i=0; i < table->s->fields; i++) incr_table->field[i]->flags &= ~(PART_KEY_FLAG | PART_INDIRECT_KEY_FLAG); - TABLE *rec_table= 0; - if (! (rec_table= create_tmp_table(thd_arg, &tmp_table_param, *column_types, - (ORDER*) 0, false, 1, - options, HA_POS_ERROR, alias, - true, keep_row_order))) - return true; - - rec_table->keys_in_use_for_query.clear_all(); - for (uint i=0; i < table->s->fields; i++) - rec_table->field[i]->flags &= ~(PART_KEY_FLAG | PART_INDIRECT_KEY_FLAG); - - if (rec_tables.push_back(rec_table)) - return true; - return false; } @@ -272,23 +261,25 @@ void select_union_recursive::cleanup() free_tmp_table(thd, incr_table); } - List_iterator<TABLE> it(rec_tables); - TABLE *tab; - while ((tab= it++)) + List_iterator<TABLE_LIST> it(rec_table_refs); + TABLE_LIST *tbl; + while ((tbl= it++)) { + TABLE *tab= tbl->table; if (tab->is_created()) { tab->file->extra(HA_EXTRA_RESET_STATE); tab->file->ha_delete_all_rows(); } - /* + /* The table will be closed later in close_thread_tables(), because it might be used in the statements like ANALYZE WITH r AS (...) SELECT * from r - where r is defined through recursion. + where r is defined through recursion. */ tab->next= thd->rec_tables; thd->rec_tables= tab; + tbl->derived_result= 0; } } @@ -715,9 +706,29 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, goto err; if (!derived->table) { - derived->table= with_element->rec_result->rec_tables.head(); - if (derived->derived_result) - derived->derived_result->table= derived->table; + bool res= false; + + if ((!derived->is_with_table_recursive_reference() || + !derived->derived_result) && + !(derived->derived_result= new (thd->mem_root) select_union(thd))) + goto err; // out of memory + thd->create_tmp_table_for_derived= TRUE; + res= derived->derived_result->create_result_table(thd, + &types, + FALSE, + create_options, + derived->alias, + FALSE, FALSE); + thd->create_tmp_table_for_derived= FALSE; + if (res) + goto err; + derived->derived_result->set_unit(this); + derived->table= derived->derived_result->table; + if (derived->is_with_table_recursive_reference()) + { + /* Here 'derived" is the primary recursive table reference */ + derived->with->rec_result->rec_table_refs.push_back(derived); + } } with_element->mark_as_with_prepared_anchor(); is_rec_result_table_created= true; @@ -1272,11 +1283,11 @@ bool st_select_lex_unit::exec_recursive() TABLE *incr_table= with_element->rec_result->incr_table; st_select_lex *end= NULL; bool is_unrestricted= with_element->is_unrestricted(); - List_iterator_fast<TABLE> li(with_element->rec_result->rec_tables); + List_iterator_fast<TABLE_LIST> li(with_element->rec_result->rec_table_refs); TMP_TABLE_PARAM *tmp_table_param= &with_element->rec_result->tmp_table_param; ha_rows examined_rows= 0; bool was_executed= executed; - TABLE *rec_table; + TABLE_LIST *rec_tbl; DBUG_ENTER("st_select_lex_unit::exec_recursive"); @@ -1335,8 +1346,9 @@ bool st_select_lex_unit::exec_recursive() else with_element->level++; - while ((rec_table= li++)) + while ((rec_tbl= li++)) { + TABLE *rec_table= rec_tbl->table; saved_error= incr_table->insert_all_rows_into_tmp_table(thd, rec_table, tmp_table_param, @@ -1344,8 +1356,9 @@ bool st_select_lex_unit::exec_recursive() if (!with_element->rec_result->first_rec_table_to_update) with_element->rec_result->first_rec_table_to_update= rec_table; if (with_element->level == 1 && rec_table->reginfo.join_tab) - rec_table->reginfo.join_tab->preread_init_done= true; + rec_table->reginfo.join_tab->preread_init_done= true; } + for (Item_subselect *sq= with_element->sq_with_rec_ref.first; sq; sq= sq->next_with_rec_ref)
1 0
0 0
[Commits] dcc7f93965f: MDEV-21958: Query having many NOT-IN clauses running forever, part 3
by psergey 11 Dec '20

11 Dec '20
revision-id: dcc7f93965f7fb534f29c5c682b2abf22e808fe2 (mariadb-10.4.11-497-gdcc7f93965f) parent(s): 502bc77f23715f84a049fe6c28a861e9af271016 author: Sergei Petrunia committer: Sergei Petrunia timestamp: 2020-12-11 22:45:54 +0300 message: MDEV-21958: Query having many NOT-IN clauses running forever, part 3 Add the new file --- mysql-test/main/range_notembedded.result | 31 ++++++++++++++++++++++++++++++ mysql-test/main/range_notembedded.test | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/mysql-test/main/range_notembedded.result b/mysql-test/main/range_notembedded.result new file mode 100644 index 00000000000..7084e0ca7a0 --- /dev/null +++ b/mysql-test/main/range_notembedded.result @@ -0,0 +1,31 @@ +drop table if exists t1,t2; +# +# MDEV-21958: Query having many NOT-IN clauses running forever +# +create table t2 ( +pk int primary key, +key1 int, +col1 int, +key (key1, pk) +); +insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); +set @tmp_21958=@@optimizer_trace; +set optimizer_trace=1; +explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL PRIMARY,key1 NULL NULL NULL 5 Using where +# This should show only ranges in form "(1) <= (key1) <= (1)" +# ranges over "pk" should not be constructed. +select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +from information_schema.optimizer_trace; +json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +[ + + [ + "(1) <= (key1) <= (1)", + "(2) <= (key1) <= (2)", + "(3) <= (key1) <= (3)" + ] +] +set optimizer_trace=@tmp_21958; +drop table t2; diff --git a/mysql-test/main/range_notembedded.test b/mysql-test/main/range_notembedded.test new file mode 100644 index 00000000000..4dc49429ff1 --- /dev/null +++ b/mysql-test/main/range_notembedded.test @@ -0,0 +1,33 @@ +# +# Range tests without embedded server. +# The first reason to have them is that embedded server doesn't have +# optimizer trace. +# +--source include/not_embedded.inc +--disable_warnings +drop table if exists t1,t2; +--enable_warnings + +--echo # +--echo # MDEV-21958: Query having many NOT-IN clauses running forever +--echo # +create table t2 ( + pk int primary key, + key1 int, + col1 int, + key (key1, pk) +); + +insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); + +set @tmp_21958=@@optimizer_trace; +set optimizer_trace=1; +explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); + +--echo # This should show only ranges in form "(1) <= (key1) <= (1)" +--echo # ranges over "pk" should not be constructed. +select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +from information_schema.optimizer_trace; +set optimizer_trace=@tmp_21958; + +drop table t2;
1 0
0 0
[Commits] 502bc77f237: MDEV-21958: Query having many NOT-IN clauses running forever, part 2
by psergey 11 Dec '20

11 Dec '20
revision-id: 502bc77f23715f84a049fe6c28a861e9af271016 (mariadb-10.4.11-496-g502bc77f237) parent(s): 4addd31531f722438b8b702c9cd00c28b61efce3 author: Sergei Petrunia committer: Sergei Petrunia timestamp: 2020-12-11 22:44:13 +0300 message: MDEV-21958: Query having many NOT-IN clauses running forever, part 2 Move the testcase into a separate file: embedded server doesn't have optimizer trace. --- mysql-test/main/range.result | 30 ------------------------------ mysql-test/main/range.test | 24 ------------------------ mysql-test/main/range_mrr_icp.result | 30 ------------------------------ 3 files changed, 84 deletions(-) diff --git a/mysql-test/main/range.result b/mysql-test/main/range.result index 132ca019a61..c10ddf9d9fd 100644 --- a/mysql-test/main/range.result +++ b/mysql-test/main/range.result @@ -3241,36 +3241,6 @@ analyze SELECT * FROM t1 where a in (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra 1 SIMPLE t1 index a a 5 NULL 2000 2000.00 10.05 60.05 Using where; Using index drop table t1,ten,t2; -# -# MDEV-21958: Query having many NOT-IN clauses running forever -# -create table t2 ( -pk int primary key, -key1 int, -col1 int, -key (key1, pk) -); -insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); -set @tmp_21958=@@optimizer_trace; -set optimizer_trace=1; -explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 ALL PRIMARY,key1 NULL NULL NULL 5 Using where -# This should show only ranges in form "(1) <= (key1) <= (1)" -# ranges over "pk" should not be constructed. -select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) -from information_schema.optimizer_trace; -json_detailed(JSON_EXTRACT(trace, '$**.ranges')) -[ - - [ - "(1) <= (key1) <= (1)", - "(2) <= (key1) <= (2)", - "(3) <= (key1) <= (3)" - ] -] -set optimizer_trace=@tmp_21958; -drop table t2; # End of 10.4 tests set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= diff --git a/mysql-test/main/range.test b/mysql-test/main/range.test index fab6c8530c2..65f580698c5 100644 --- a/mysql-test/main/range.test +++ b/mysql-test/main/range.test @@ -2207,30 +2207,6 @@ let $a= `select group_concat(a) from t2`; eval analyze SELECT * FROM t1 where a in ($a); drop table t1,ten,t2; ---echo # ---echo # MDEV-21958: Query having many NOT-IN clauses running forever ---echo # -create table t2 ( - pk int primary key, - key1 int, - col1 int, - key (key1, pk) -); - -insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); - -set @tmp_21958=@@optimizer_trace; -set optimizer_trace=1; -explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); - ---echo # This should show only ranges in form "(1) <= (key1) <= (1)" ---echo # ranges over "pk" should not be constructed. -select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) -from information_schema.optimizer_trace; -set optimizer_trace=@tmp_21958; - -drop table t2; - --echo # End of 10.4 tests set global innodb_stats_persistent= @innodb_stats_persistent_save; diff --git a/mysql-test/main/range_mrr_icp.result b/mysql-test/main/range_mrr_icp.result index ebefc21a58d..826ac621064 100644 --- a/mysql-test/main/range_mrr_icp.result +++ b/mysql-test/main/range_mrr_icp.result @@ -3238,36 +3238,6 @@ analyze SELECT * FROM t1 where a in (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra 1 SIMPLE t1 index a a 5 NULL 2000 2000.00 10.05 60.05 Using where; Using index drop table t1,ten,t2; -# -# MDEV-21958: Query having many NOT-IN clauses running forever -# -create table t2 ( -pk int primary key, -key1 int, -col1 int, -key (key1, pk) -); -insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); -set @tmp_21958=@@optimizer_trace; -set optimizer_trace=1; -explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 ALL PRIMARY,key1 NULL NULL NULL 5 Using where -# This should show only ranges in form "(1) <= (key1) <= (1)" -# ranges over "pk" should not be constructed. -select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) -from information_schema.optimizer_trace; -json_detailed(JSON_EXTRACT(trace, '$**.ranges')) -[ - - [ - "(1) <= (key1) <= (1)", - "(2) <= (key1) <= (2)", - "(3) <= (key1) <= (3)" - ] -] -set optimizer_trace=@tmp_21958; -drop table t2; # End of 10.4 tests set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages=
1 0
0 0
[Commits] 4addd31531f: MDEV-21958: Query having many NOT-IN clauses running forever
by psergey 11 Dec '20

11 Dec '20
revision-id: 4addd31531f722438b8b702c9cd00c28b61efce3 (mariadb-10.4.11-495-g4addd31531f) parent(s): 0adbf27f00e19801a8e244292000e85e6889b9aa author: Sergei Petrunia committer: Sergei Petrunia timestamp: 2020-12-11 18:54:21 +0300 message: MDEV-21958: Query having many NOT-IN clauses running forever Basic variant of the fix: do not consider conditions in form unique_key NOT IN (c1,c2...) to be sargable. If there are only a few constants, the condition is not selective. If there are a lot constants, the overhead of processing such a huge range list is not worth it. --- mysql-test/main/range.result | 30 ++++++++++++++++++++++++++++++ mysql-test/main/range.test | 24 ++++++++++++++++++++++++ mysql-test/main/range_mrr_icp.result | 30 ++++++++++++++++++++++++++++++ sql/opt_range.cc | 24 ++++++++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/mysql-test/main/range.result b/mysql-test/main/range.result index c10ddf9d9fd..132ca019a61 100644 --- a/mysql-test/main/range.result +++ b/mysql-test/main/range.result @@ -3241,6 +3241,36 @@ analyze SELECT * FROM t1 where a in (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra 1 SIMPLE t1 index a a 5 NULL 2000 2000.00 10.05 60.05 Using where; Using index drop table t1,ten,t2; +# +# MDEV-21958: Query having many NOT-IN clauses running forever +# +create table t2 ( +pk int primary key, +key1 int, +col1 int, +key (key1, pk) +); +insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); +set @tmp_21958=@@optimizer_trace; +set optimizer_trace=1; +explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL PRIMARY,key1 NULL NULL NULL 5 Using where +# This should show only ranges in form "(1) <= (key1) <= (1)" +# ranges over "pk" should not be constructed. +select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +from information_schema.optimizer_trace; +json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +[ + + [ + "(1) <= (key1) <= (1)", + "(2) <= (key1) <= (2)", + "(3) <= (key1) <= (3)" + ] +] +set optimizer_trace=@tmp_21958; +drop table t2; # End of 10.4 tests set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= diff --git a/mysql-test/main/range.test b/mysql-test/main/range.test index 65f580698c5..fab6c8530c2 100644 --- a/mysql-test/main/range.test +++ b/mysql-test/main/range.test @@ -2207,6 +2207,30 @@ let $a= `select group_concat(a) from t2`; eval analyze SELECT * FROM t1 where a in ($a); drop table t1,ten,t2; +--echo # +--echo # MDEV-21958: Query having many NOT-IN clauses running forever +--echo # +create table t2 ( + pk int primary key, + key1 int, + col1 int, + key (key1, pk) +); + +insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); + +set @tmp_21958=@@optimizer_trace; +set optimizer_trace=1; +explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); + +--echo # This should show only ranges in form "(1) <= (key1) <= (1)" +--echo # ranges over "pk" should not be constructed. +select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +from information_schema.optimizer_trace; +set optimizer_trace=@tmp_21958; + +drop table t2; + --echo # End of 10.4 tests set global innodb_stats_persistent= @innodb_stats_persistent_save; diff --git a/mysql-test/main/range_mrr_icp.result b/mysql-test/main/range_mrr_icp.result index 826ac621064..ebefc21a58d 100644 --- a/mysql-test/main/range_mrr_icp.result +++ b/mysql-test/main/range_mrr_icp.result @@ -3238,6 +3238,36 @@ analyze SELECT * FROM t1 where a in (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra 1 SIMPLE t1 index a a 5 NULL 2000 2000.00 10.05 60.05 Using where; Using index drop table t1,ten,t2; +# +# MDEV-21958: Query having many NOT-IN clauses running forever +# +create table t2 ( +pk int primary key, +key1 int, +col1 int, +key (key1, pk) +); +insert into t2 (pk, key1) values (1,1),(2,2),(3,3),(4,4),(5,5); +set @tmp_21958=@@optimizer_trace; +set optimizer_trace=1; +explain select * from t2 where key1 in (1,2,3) and pk not in (1,2,3); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL PRIMARY,key1 NULL NULL NULL 5 Using where +# This should show only ranges in form "(1) <= (key1) <= (1)" +# ranges over "pk" should not be constructed. +select json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +from information_schema.optimizer_trace; +json_detailed(JSON_EXTRACT(trace, '$**.ranges')) +[ + + [ + "(1) <= (key1) <= (1)", + "(2) <= (key1) <= (2)", + "(3) <= (key1) <= (3)" + ] +] +set optimizer_trace=@tmp_21958; +drop table t2; # End of 10.4 tests set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 043a1e70f61..1a7ac1044c3 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -7787,6 +7787,30 @@ SEL_TREE *Item_func_in::get_func_mm_tree(RANGE_OPT_PARAM *param, if (array->count > NOT_IN_IGNORE_THRESHOLD || !value_item) DBUG_RETURN(0); + /* + If this is "unique_key NOT IN (...)", do not consider it sargable (for + any index, not just the unique one). The logic is as follows: + - if there are only a few constants, this condition is not selective + (unless the table is also very small in which case we won't gain + anything) + - If there are a lot of constants, the overhead of building and + processing enormous range list is not worth it. + */ + if (param->using_real_indexes) + { + key_map::Iterator it(field->key_start); + uint key_no; + while ((key_no= it.next_bit()) != key_map::Iterator::BITMAP_END) + { + KEY *key_info= &param->table->key_info[key_no]; + if (key_info->user_defined_key_parts == 1 && + (key_info->flags & HA_NOSAME)) + { + DBUG_RETURN(0); + } + } + } + /* Get a SEL_TREE for "(-inf|NULL) < X < c_0" interval. */ uint i=0; do
1 0
0 0
[Commits] 0adbf27f00e: Run innodb.innodb_stats test with EITS disabled.
by psergey 11 Dec '20

11 Dec '20
revision-id: 0adbf27f00e19801a8e244292000e85e6889b9aa (mariadb-10.4.11-494-g0adbf27f00e) parent(s): e007fcf59dd02407277b130dbd3984a7b5352c5e author: Sergei Petrunia committer: Sergei Petrunia timestamp: 2020-12-11 17:33:44 +0300 message: Run innodb.innodb_stats test with EITS disabled. - There is no reason to collect EITS statistics - The test is sporadically failing on some platforms. I believe the issue is in InnoDB. Let's rule out EITS code as a possible source of the issue. --- mysql-test/suite/innodb/r/innodb_stats.result | 10 ---------- mysql-test/suite/innodb/t/innodb_stats.test | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb_stats.result b/mysql-test/suite/innodb/r/innodb_stats.result index eadce75318b..d2c3bd0127e 100644 --- a/mysql-test/suite/innodb/r/innodb_stats.result +++ b/mysql-test/suite/innodb/r/innodb_stats.result @@ -4,7 +4,6 @@ dummy INSERT, the table should be empty dummy INSERT, the table should be empty ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -57,7 +56,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -110,7 +108,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (1); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -163,7 +160,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (1), (1); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -216,7 +212,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -269,7 +264,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (2); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -322,7 +316,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (1), (2); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -375,7 +368,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (2), (3); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -428,7 +420,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (1), (2), (3), (3); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, @@ -481,7 +472,6 @@ TRUNCATE TABLE test_innodb_stats; INSERT INTO test_innodb_stats (a) VALUES (1), (2), (3), (4), (5), (1), (2), (3), (4), (5); ANALYZE TABLE test_innodb_stats; Table Op Msg_type Msg_text -test.test_innodb_stats analyze status Engine-independent statistics collected test.test_innodb_stats analyze status OK SELECT stat_name, diff --git a/mysql-test/suite/innodb/t/innodb_stats.test b/mysql-test/suite/innodb/t/innodb_stats.test index 09515ec9720..1a962e3568e 100644 --- a/mysql-test/suite/innodb/t/innodb_stats.test +++ b/mysql-test/suite/innodb/t/innodb_stats.test @@ -10,7 +10,7 @@ DROP TABLE IF EXISTS test_innodb_stats; set @save_use_stat_tables= @@use_stat_tables; -set @@use_stat_tables= COMPLEMENTARY; +set @@use_stat_tables= NEVER; CREATE TABLE test_innodb_stats ( a INT,
1 0
0 0
[Commits] 33ffd3aeb5f: MDEV-24353: Adding GROUP BY slows down a query
by varun 10 Dec '20

10 Dec '20
revision-id: 33ffd3aeb5ff753ecdcd035114bbfd3acbbcafc2 (mariadb-10.5.2-320-g33ffd3aeb5f) parent(s): e9f33b7760539e2ba60fb236fab8eaf0ce53ca4a author: Varun Gupta committer: Varun Gupta timestamp: 2020-12-09 19:31:30 +0530 message: MDEV-24353: Adding GROUP BY slows down a query A heuristic in best_access_path says that if for an index ref access involved key parts which are greater than equal to that for range access, then range access should not be considered. The assumption made by this heuristic does not hold when the range optimizer opted to use the group-by min-max optimization. So the fix here would be to not consider the heuristic if the range optimizer picked the usage of group-by min-max optimization. --- mysql-test/main/group_min_max.result | 23 ++++++++++++++++++++++- mysql-test/main/group_min_max.test | 18 ++++++++++++++++++ sql/sql_select.cc | 7 +++++-- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/mysql-test/main/group_min_max.result b/mysql-test/main/group_min_max.result index 4f32db780fd..5f51be715fe 100644 --- a/mysql-test/main/group_min_max.result +++ b/mysql-test/main/group_min_max.result @@ -2664,7 +2664,7 @@ a b 3 13 explain extended select sql_buffer_result a, max(b)+1 from t1 where a = 0 group by a; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ref a,index a 5 const 15 20.00 Using index; Using temporary +1 SIMPLE t1 range a,index a 5 NULL 3 100.00 Using where; Using index for group-by; Using temporary Warnings: Note 1003 select sql_buffer_result `test`.`t1`.`a` AS `a`,max(`test`.`t1`.`b`) + 1 AS `max(b)+1` from `test`.`t1` where `test`.`t1`.`a` = 0 group by `test`.`t1`.`a` drop table t1; @@ -4027,3 +4027,24 @@ drop table t1; # # End of 10.1 tests # +# +# MDEV-24353: Adding GROUP BY slows down a query +# +CREATE TABLE t1 (p int NOT NULL, a int NOT NULL, PRIMARY KEY (p,a)); +insert into t1 select 2,seq from seq_0_to_1000; +EXPLAIN select MIN(a) from t1 where p = 2 group by p; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 10 Using where; Using index for group-by +SELECT MIN(a) from t1 where p = 2 group by p; +MIN(a) +0 +EXPLAIN select MIN(a) from t1 group by p; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL PRIMARY 4 NULL 10 Using index for group-by +SELECT MIN(a) from t1 where p = 2; +MIN(a) +0 +drop table t1; +# +# End of 10.6 tests +# diff --git a/mysql-test/main/group_min_max.test b/mysql-test/main/group_min_max.test index 526552dda92..1db479b1c74 100644 --- a/mysql-test/main/group_min_max.test +++ b/mysql-test/main/group_min_max.test @@ -4,6 +4,7 @@ # --source include/default_optimizer_switch.inc +--source include/have_sequence.inc # # TODO: @@ -1689,3 +1690,20 @@ drop table t1; --echo # --echo # End of 10.1 tests --echo # + +--echo # +--echo # MDEV-24353: Adding GROUP BY slows down a query +--echo # + +CREATE TABLE t1 (p int NOT NULL, a int NOT NULL, PRIMARY KEY (p,a)); +insert into t1 select 2,seq from seq_0_to_1000; + +EXPLAIN select MIN(a) from t1 where p = 2 group by p; +SELECT MIN(a) from t1 where p = 2 group by p; +EXPLAIN select MIN(a) from t1 group by p; +SELECT MIN(a) from t1 where p = 2; +drop table t1; + +--echo # +--echo # End of 10.6 tests +--echo # diff --git a/sql/sql_select.cc b/sql/sql_select.cc index bf3d4fd9bb1..060ac427a3c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -7926,7 +7926,8 @@ best_access_path(JOIN *join, access is to use the same index IDX, with the same or more key parts. (note: it is not clear how this rule is/should be extended to index_merge quick selects). Also if we have a hash join we prefer that - over a table scan + over a table scan. This heuristic is no more allowed for + if the range optimizer opted to use the group-by min-max optimization. (3) See above note about InnoDB. (4) NOT ("FORCE INDEX(...)" is used for table and there is 'ref' access path, but there is no quick select) @@ -7944,7 +7945,9 @@ best_access_path(JOIN *join, Json_writer_object trace_access_scan(thd); if ((records >= s->found_records || best > s->read_time) && // (1) !(best_key && best_key->key == MAX_KEY) && // (2) - !(s->quick && best_key && s->quick->index == best_key->key && // (2) + !(s->quick && + s->quick->get_type() != QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX && + best_key && s->quick->index == best_key->key && // (2) best_max_key_part >= s->table->opt_range[best_key->key].key_parts) &&// (2) !((s->table->file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX) && // (3) ! s->table->covering_keys.is_clear_all() && best_key && !s->quick) &&// (3)
2 1
0 0
  • ← Newer
  • 1
  • ...
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • ...
  • 1461
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.