From adc13e2c167c90f4b287efa7b1165c68d441be8d Mon Sep 17 00:00:00 2001 From: Igor Babaev Date: Wed, 26 Jul 2023 22:59:25 -0700 Subject: [PATCH] MDEV-31150 Crash on 2nd execution of update using mergeable derived table This bug caused crashes of the server on the second execution of update statements that used mergeable derived tables. The crashes happened in the function multi_update_check_table_access() when the code tried to dereference the pointer stored in field TABLE_LIST::TABLE for a mergeable derived table. The fact is this field is set to NULL after the first execution of the query. At the same any action performed by the function is actually not needed for derived tables. Approved by Oleksandr Byelkin --- mysql-test/main/multi_update.result | 276 ++++++++++++++++++++++++++++ mysql-test/main/multi_update.test | 137 ++++++++++++++ sql/sql_update.cc | 8 + 3 files changed, 421 insertions(+) diff --git a/mysql-test/main/multi_update.result b/mysql-test/main/multi_update.result index 2364ee732ce..1a87ec16b94 100644 --- a/mysql-test/main/multi_update.result +++ b/mysql-test/main/multi_update.result @@ -1384,3 +1384,279 @@ c1 c2 c3 12 5 8 drop table t1,t2,t3,t; # End of 10.4 tests +# +# MDEV-31150: 2nd execution of multi-update with +# mergeable derived table in WHERE +# +CREATE TABLE t1 (a int); +INSERT INTO t1 VALUES (3),(7),(1),(3); +CREATE TABLE t2 (b int); +INSERT INTO t2 VALUES (3),(4),(1); +CREATE TABLE t3 (c int); +INSERT INTO t3 VALUES (3),(4),(1),(5),(1); +CREATE PROCEDURE restore_t1() +BEGIN +DELETE FROM t1; +INSERT INTO t1 VALUES (3),(7),(1),(3); +END| +SELECT * FROM t1; +a +3 +7 +1 +3 +EXPLAIN UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (SELECT * FROM (SELECT b FROM t2) dt); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 Using where; FirstMatch(t1) +1 PRIMARY t3 ALL NULL NULL NULL NULL 5 Using where +UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (SELECT * FROM (SELECT b FROM t2) dt); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +PREPARE stmt FROM "UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (SELECT * FROM (SELECT b FROM t2) dt)"; +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DEALLOCATE PREPARE stmt; +CREATE PROCEDURE p1() UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (SELECT * FROM (SELECT b FROM t2) dt); +SELECT * FROM t1; +a +3 +7 +1 +3 +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DROP procedure p1; +SELECT * FROM t1; +a +3 +7 +1 +3 +EXPLAIN UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 Using where; FirstMatch(t1) +1 PRIMARY t3 ALL NULL NULL NULL NULL 5 Using where +UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +PREPARE stmt FROM "UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte)"; +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DEALLOCATE PREPARE stmt; +CREATE PROCEDURE p1() UPDATE t1,t3 SET a = NULL +WHERE a=c AND a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); +SELECT * FROM t1; +a +3 +7 +1 +3 +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DROP procedure p1; +SELECT * FROM t1; +a +3 +7 +1 +3 +EXPLAIN UPDATE t1 SET a = NULL +WHERE a IN (SELECT * FROM (SELECT b FROM t2) dt); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 Using where; FirstMatch(t1) +UPDATE t1 SET a = NULL +WHERE a IN (SELECT * FROM (SELECT b FROM t2) dt); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +PREPARE stmt FROM "UPDATE t1 SET a = NULL +WHERE a IN (SELECT * FROM (SELECT b FROM t2) dt)"; +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DEALLOCATE PREPARE stmt; +CREATE PROCEDURE p1() UPDATE t1 SET a = NULL +WHERE a IN (SELECT * FROM (SELECT b FROM t2) dt); +SELECT * FROM t1; +a +3 +7 +1 +3 +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DROP procedure p1; +SELECT * FROM t1; +a +3 +7 +1 +3 +EXPLAIN UPDATE t1 SET a = NULL +WHERE a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 Using where; FirstMatch(t1) +UPDATE t1 SET a = NULL +WHERE a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +PREPARE stmt FROM "UPDATE t1 SET a = NULL +WHERE a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte)"; +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DEALLOCATE PREPARE stmt; +CREATE PROCEDURE p1() UPDATE t1 SET a = NULL +WHERE a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); +SELECT * FROM t1; +a +3 +7 +1 +3 +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +a +NULL +7 +NULL +NULL +CALL restore_t1(); +DROP procedure p1; +DROP TABLE t1,t2,t3; +DROP PROCEDURE restore_t1; +# End of 11.1 tests diff --git a/mysql-test/main/multi_update.test b/mysql-test/main/multi_update.test index 325f63f696f..c6247a21b21 100644 --- a/mysql-test/main/multi_update.test +++ b/mysql-test/main/multi_update.test @@ -1201,3 +1201,140 @@ select * from t1; drop table t1,t2,t3,t; --echo # End of 10.4 tests + +--echo # +--echo # MDEV-31150: 2nd execution of multi-update with +--echo # mergeable derived table in WHERE +--echo # + +CREATE TABLE t1 (a int); +INSERT INTO t1 VALUES (3),(7),(1),(3); +CREATE TABLE t2 (b int); +INSERT INTO t2 VALUES (3),(4),(1); +CREATE TABLE t3 (c int); +INSERT INTO t3 VALUES (3),(4),(1),(5),(1); + +--delimiter | +CREATE PROCEDURE restore_t1() +BEGIN +DELETE FROM t1; +INSERT INTO t1 VALUES (3),(7),(1),(3); +END| +--delimiter ; + +let $q1= +UPDATE t1,t3 SET a = NULL + WHERE a=c AND a IN (SELECT * FROM (SELECT b FROM t2) dt); + +SELECT * FROM t1; +eval EXPLAIN $q1; +eval $q1; +SELECT * FROM t1; +CALL restore_t1(); +eval PREPARE stmt FROM "$q1"; +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +DEALLOCATE PREPARE stmt; + +eval CREATE PROCEDURE p1() $q1; +SELECT * FROM t1; +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +DROP procedure p1; + +let $q2= +UPDATE t1,t3 SET a = NULL + WHERE a=c AND a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); + +SELECT * FROM t1; +eval EXPLAIN $q2; +eval $q2; +SELECT * FROM t1; +CALL restore_t1(); +eval PREPARE stmt FROM "$q2"; +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +DEALLOCATE PREPARE stmt; + +eval CREATE PROCEDURE p1() $q2; +SELECT * FROM t1; +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +DROP procedure p1; + +let $q3= +UPDATE t1 SET a = NULL + WHERE a IN (SELECT * FROM (SELECT b FROM t2) dt); + +SELECT * FROM t1; +eval EXPLAIN $q3; +eval $q3; +SELECT * FROM t1; +CALL restore_t1(); +eval PREPARE stmt FROM "$q3"; +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +DEALLOCATE PREPARE stmt; + +eval CREATE PROCEDURE p1() $q3; +SELECT * FROM t1; +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +DROP procedure p1; + +let $q4= +UPDATE t1 SET a = NULL + WHERE a IN (WITH cte AS (SELECT b FROM t2) SELECT * FROM cte); + +SELECT * FROM t1; +eval EXPLAIN $q4; +eval $q4; +SELECT * FROM t1; +CALL restore_t1(); +eval PREPARE stmt FROM "$q4"; +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +EXECUTE stmt; +SELECT * FROM t1; +CALL restore_t1(); +DEALLOCATE PREPARE stmt; + +eval CREATE PROCEDURE p1() $q4; +SELECT * FROM t1; +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +CALL p1(); +SELECT * FROM t1; +CALL restore_t1(); +DROP procedure p1; + +DROP TABLE t1,t2,t3; +DROP PROCEDURE restore_t1; + +--echo # End of 11.1 tests diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 4eec441e9aa..c5331747e45 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1497,6 +1497,14 @@ static bool multi_update_check_table_access(THD *thd, TABLE_LIST *table, else { /* Must be a base or derived table. */ + /* + Derived tables do not need the check below. + Besides one have take into account that for mergeable derived tables + TABLE_LIST::TABLE is set to NULL after the first execution of the query. + */ + if (table->is_derived()) + return false; + const bool updated= table->table->map & tables_for_update; if (check_table_access(thd, updated ? UPDATE_ACL : SELECT_ACL, table, FALSE, 1, FALSE))