diff options
author | Dmitry Shulga <dmitry.shulga@mariadb.com> | 2022-03-27 14:02:34 +0700 |
---|---|---|
committer | Dmitry Shulga <dmitry.shulga@mariadb.com> | 2022-03-30 16:11:18 +0700 |
commit | bdba1d46bbf08044274cbb5a94a6cb395485c0af (patch) | |
tree | 9b5953ab51f7d4e22f2793c24e03afc0d0ab0c46 | |
parent | 33ff18627ea009709bb0ba55b68f873e6c6c784c (diff) | |
download | mariadb-git-bdba1d46bbf08044274cbb5a94a6cb395485c0af.tar.gz |
MDEV-19631: Assertion `0' failed in st_select_lex_unit::optimize or different plan upon 2nd execution of PS with EXPLAIN
Second execution of a prepared statement for a query containing a constant
subquery with union that can be optimized away, could result in server abnormal
termination for debug build or incorrect result set output for release build.
For example, the following test case crashes a server built with debug on second
run of the statement EXECUTE stmt
CREATE TABLE t1 (a INT);
PREPARE stmt FROM 'EXPLAIN SELECT * FROM t1 HAVING 6 IN ( SELECT 6 UNION SELECT 5 )';
EXECUTE stmt;
EXECUTE stmt;
The reason for incorrect result set output or abnormal server termination
is careless working with the data member fake_select_lex->options inside
the function mysql_explain_union(). Once the flag SELECT_DESCRIBE is set in
the data member fake_select_lex->option before calling the methods
SELECT_LEX_UNIT::prepare/SELECT_LEX_UNIT::execute
the original value of the option is no longer restored.
As a consequence, next time the prepared statement is re-executed we have
the fake_select_lex with the flag SELECT_DESCRIBE set in the data member
fake_select_lex->option, that is incorrect. In result, the method
Item_subselect::assigned()
is not invoked during evaluation of a constant condition (constant subquery
with union) that being performed on OPTIMIZE phase of query handling.
This leads to the fact that records in the temporary table are not deleted
before calling
table->file->ha_enable_indexes(HA_KEY_SWITCH_ALL)
in the method st_select_lex_unit::optimize().
In result table->file->ha_enable_indexes(HA_KEY_SWITCH_ALL) returns error
and DBUG_ASSERT(0) is fired.
Stack trace to the line where the error generated on re-enabling indexes
for next subselect iteration is below:
st_select_lex_unit::optimize (at sql_union.cc:954)
handler::ha_enable_indexes (at handler.cc:4338)
ha_heap::enable_indexes (at ha_heap.cc:519)
heap_enable_indexes (at hp_clear.c:164)
The code snippet to clarify raising the error is also listed:
int heap_enable_indexes(HP_INFO *info)
{
int error= 0;
HP_SHARE *share= info->s;
if (share->data_length || share->index_length)
error= HA_ERR_CRASHED; <<== set error the value HA_ERR_CRASHED
since share->data_length != 0
To fix this issue the original value of unit->fake_select_lex->options
has to be saved before setting the flag SELECT_DESCRIBE and restored
on return from invocation of SELECT_LEX_UNIT::prepare/SELECT_LEX_UNIT::execute
-rw-r--r-- | mysql-test/r/ps.result | 22 | ||||
-rw-r--r-- | mysql-test/t/ps.test | 13 | ||||
-rw-r--r-- | sql/sql_select.cc | 6 |
3 files changed, 41 insertions, 0 deletions
diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 1cd41e5b3a4..81167bc85c0 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -5487,5 +5487,27 @@ a DEALLOCATE PREPARE stmt; DROP VIEW v1; DROP TABLE t1; +# +# MDEV-19631: Assertion `0' failed in st_select_lex_unit::optimize or +# different plan upon 2nd execution of PS with EXPLAIN +# +CREATE TABLE t1 (a INT); +PREPARE stmt FROM 'EXPLAIN SELECT * FROM t1 HAVING 6 IN ( SELECT 6 UNION SELECT 5 )'; +EXECUTE stmt; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 system NULL NULL NULL NULL 0 const row not found +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL +# Without the patch the second execution of the 'stmt' prepared statement +# would result in server crash. +EXECUTE stmt; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 system NULL NULL NULL NULL 0 const row not found +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL +DEALLOCATE PREPARE stmt; +DROP TABLE t1; # End of 10.2 tests # diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 4097d28a949..81d8e8e0fed 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -4976,6 +4976,19 @@ DEALLOCATE PREPARE stmt; DROP VIEW v1; DROP TABLE t1; +--echo # +--echo # MDEV-19631: Assertion `0' failed in st_select_lex_unit::optimize or +--echo # different plan upon 2nd execution of PS with EXPLAIN +--echo # +CREATE TABLE t1 (a INT); +PREPARE stmt FROM 'EXPLAIN SELECT * FROM t1 HAVING 6 IN ( SELECT 6 UNION SELECT 5 )'; +EXECUTE stmt; +--echo # Without the patch the second execution of the 'stmt' prepared statement +--echo # would result in server crash. +EXECUTE stmt; +# Cleanup +DEALLOCATE PREPARE stmt; +DROP TABLE t1; --echo # End of 10.2 tests --echo # diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a3e8b453897..22d3597de16 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -25400,14 +25400,20 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) if (unit->is_union() || unit->fake_select_lex) { + ulonglong save_options= 0; + if (unit->union_needs_tmp_table() && unit->fake_select_lex) { + save_options= unit->fake_select_lex->options; unit->fake_select_lex->select_number= FAKE_SELECT_LEX_ID; // just for initialization unit->fake_select_lex->type= "UNION RESULT"; unit->fake_select_lex->options|= SELECT_DESCRIBE; } if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) res= unit->exec(); + + if (unit->union_needs_tmp_table() && unit->fake_select_lex) + unit->fake_select_lex->options= save_options; } else { |