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 <sanja@mariadb.com>
This commit is contained in:
Igor Babaev 2023-07-26 22:59:25 -07:00
parent c6ac1e39b6
commit adc13e2c16
3 changed files with 421 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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))