diff options
author | Dmitry Shulga <dmitry.shulga@mariadb.com> | 2022-10-11 18:22:40 +0700 |
---|---|---|
committer | Dmitry Shulga <dmitry.shulga@mariadb.com> | 2022-10-11 18:22:40 +0700 |
commit | f64ecfd0de57cba087759a7521cf0d07a3134709 (patch) | |
tree | 23137d85ba6e47d0c8ffa787f137e2038ddb77f8 | |
parent | 7a28c82dcddc82008e5d9a8a27b95e9597295e23 (diff) | |
download | mariadb-git-bb-10.3-MDEV-16128.tar.gz |
MDEV-16128: Server crash in Item_func::print_op on 2nd execution of PSbb-10.3-MDEV-16128
For some queries that involve tables with different but convertible
character sets for columns taking part in the query, repeatable
execution of such queries in PS mode or as part of a stored routine
would result in server abnormal termination.
For example,
CREATE TABLE t1 (a2 varchar(10));
CREATE TABLE t2 (u1 varchar(10) CHARACTER SET utf8);
CREATE TABLE t3 (u2 varchar(10) CHARACTER SET utf8);
PREPARE stmt FROM
"SELECT t1.* FROM (t1 JOIN t2 ON (t2.u1 = t1.a2))
WHERE (EXISTS (SELECT 1 FROM t3 WHERE t3.u2 = t1.a2))";
EXECUTE stmt;
EXECUTE stmt; <== Running this prepared statement the second time
results in server crash.
The reason of server crash is that an instance of the class
Item_func_conv_charset, that created for conversion of a column
from one character set to another, is allocated on execution
memory root but pointer to this instance is stored in an item
placed on prepared statement memory root. Below is calls trace to
the place where an instance of the class Item_func_conv_charset
is created.
setup_conds
Item_func::fix_fields
Item_bool_rowready_func2::fix_length_and_dec
Item_func::setup_args_and_comparator
Item_func_or_sum::agg_arg_charsets_for_comparison
Item_func_or_sum::agg_arg_charsets
Item_func_or_sum::agg_item_set_converter
Item::safe_charset_converter
And the following trace shows the place where a pointer to
the instance of the class Item_func_conv_charset is passed
to the class Item_func_eq, that is created on a memory root of
the prepared statement.
Prepared_statement::execute
mysql_execute_command
execute_sqlcom_select
handle_select
mysql_select
JOIN::optimize
JOIN::optimize_inner
convert_join_subqueries_to_semijoins
convert_subq_to_sj
To fix the issue, switch to the Prepared Statement memory root
before calling the method Item_func::setup_args_and_comparator
in order to place any created Items on permanent memory root.
It may seem that such approach would result in a memory
leakage in case the parameter marker '?' is used in the query
as in the following example
PREPARE stmt FROM
"SELECT t1.* FROM (t1 JOIN t2 ON (t2.u1 = t1.a2))
WHERE (EXISTS (SELECT 1 FROM t3 WHERE t3.u2 = ?))";
EXECUTE stmt USING convert('A' using latin1);
but it wouldn't since for such case any of the parameter markers
is treated as a constant and no subquery to semijoin optimization
is performed.
m--------- | libmariadb | 0 | ||||
-rw-r--r-- | mysql-test/main/ps.result | 92 | ||||
-rw-r--r-- | mysql-test/main/ps.test | 68 | ||||
-rw-r--r-- | sql/item_cmpfunc.cc | 13 | ||||
-rw-r--r-- | tests/mysql_client_test.c | 97 |
5 files changed, 268 insertions, 2 deletions
diff --git a/libmariadb b/libmariadb -Subproject 7fdb3eab66384a355475704332d11cc1ab82499 +Subproject ab7a81e79e4be4324a2d09d19d4f5249801ef66 diff --git a/mysql-test/main/ps.result b/mysql-test/main/ps.result index e3180175eac..7b9ccf303c8 100644 --- a/mysql-test/main/ps.result +++ b/mysql-test/main/ps.result @@ -5602,3 +5602,95 @@ a.a a.b 10 20 DEALLOCATE PREPARE stmt; DROP PROCEDURE p1; +# +# MDEV-16128: Server crash in Item_func::print_op on 2nd execution of PS +# +CREATE TABLE t1 (a varchar(10)); +CREATE TABLE t2 (b varchar(10) CHARACTER SET utf8 ); +CREATE TABLE t3 (c varchar(10) CHARACTER SET utf8); +INSERT INTO t1 VALUES ('b'); +INSERT INTO t2 VALUES ('b'); +INSERT INTO t3 VALUES ('b'); +PREPARE stmt FROM "SELECT t1.* FROM (t1 JOIN t2 ON (t2.b = t1.a)) WHERE (EXISTS (SELECT 1 FROM t3 WHERE t3.c = t1.a))"; +EXECUTE stmt; +a +b +# Without the patch second execution of the prepared statement +# would lead to server crash. +EXECUTE stmt; +a +b +# Clean up +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t2, t3; +CREATE TABLE t1 (a varchar(10)); +CREATE TABLE t2 (b varchar(10) CHARACTER SET utf8); +INSERT INTO t1 VALUES ('b'); +INSERT INTO t2 VALUES ('b'); +PREPARE stmt FROM 'SELECT STRAIGHT_JOIN 1 FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a)'; +EXECUTE stmt; +1 +1 +# Without the patch second execution of the prepared statement +# would lead to server crash. +EXECUTE stmt; +1 +1 +# Clean up +DEALLOCATE PREPARE stmt; +# Check that EXECUTE USING is run correctly +PREPARE stmt FROM 'SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t2 WHERE t2.b = ?)'; +EXECUTE stmt USING 'b'; +300 +300 +EXECUTE stmt USING 'b'; +300 +300 +EXECUTE stmt USING 'd'; +300 +EXECUTE stmt USING 'd'; +300 +EXECUTE stmt USING _binary 'b'; +300 +300 +EXECUTE stmt USING _binary 'b'; +300 +300 +EXECUTE stmt USING _binary 'd'; +300 +EXECUTE stmt USING _binary 'd'; +300 +EXECUTE stmt USING _ucs2 'b'; +300 +300 +EXECUTE stmt USING _ucs2 'b'; +300 +300 +EXECUTE stmt USING _ucs2 'd'; +300 +EXECUTE stmt USING _ucs2 'd'; +300 +EXECUTE stmt USING _latin1 'b'; +300 +300 +EXECUTE stmt USING _latin1 'b'; +300 +300 +EXECUTE stmt USING _latin1 'd'; +300 +EXECUTE stmt USING _latin1 'd'; +300 +CREATE TABLE t3 (c VARCHAR(10) CHARACTER SET ucs2); +INSERT INTO t3 VALUES ('b'); +PREPARE stmt FROM 'SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t3 WHERE t3.c = ?)'; +EXECUTE stmt USING 'b'; +300 +300 +EXECUTE stmt USING 'b'; +300 +300 +EXECUTE stmt USING 'd'; +300 +EXECUTE stmt USING 'd'; +300 +DROP TABLE t1, t2, t3; diff --git a/mysql-test/main/ps.test b/mysql-test/main/ps.test index 8c59f1e0840..4041e855799 100644 --- a/mysql-test/main/ps.test +++ b/mysql-test/main/ps.test @@ -5045,3 +5045,71 @@ EXECUTE stmt; DEALLOCATE PREPARE stmt; DROP PROCEDURE p1; + +--echo # +--echo # MDEV-16128: Server crash in Item_func::print_op on 2nd execution of PS +--echo # + +CREATE TABLE t1 (a varchar(10)); +CREATE TABLE t2 (b varchar(10) CHARACTER SET utf8 ); +CREATE TABLE t3 (c varchar(10) CHARACTER SET utf8); +INSERT INTO t1 VALUES ('b'); +INSERT INTO t2 VALUES ('b'); +INSERT INTO t3 VALUES ('b'); + +PREPARE stmt FROM "SELECT t1.* FROM (t1 JOIN t2 ON (t2.b = t1.a)) WHERE (EXISTS (SELECT 1 FROM t3 WHERE t3.c = t1.a))"; +EXECUTE stmt; +--echo # Without the patch second execution of the prepared statement +--echo # would lead to server crash. +EXECUTE stmt; +--echo # Clean up +DEALLOCATE PREPARE stmt; +DROP TABLE t1, t2, t3; + +CREATE TABLE t1 (a varchar(10)); +CREATE TABLE t2 (b varchar(10) CHARACTER SET utf8); +INSERT INTO t1 VALUES ('b'); +INSERT INTO t2 VALUES ('b'); +PREPARE stmt FROM 'SELECT STRAIGHT_JOIN 1 FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a)'; +EXECUTE stmt; +--echo # Without the patch second execution of the prepared statement +--echo # would lead to server crash. +EXECUTE stmt; + +--echo # Clean up +DEALLOCATE PREPARE stmt; + +--echo # Check that EXECUTE USING is run correctly +PREPARE stmt FROM 'SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t2 WHERE t2.b = ?)'; +EXECUTE stmt USING 'b'; +EXECUTE stmt USING 'b'; + +EXECUTE stmt USING 'd'; +EXECUTE stmt USING 'd'; + +EXECUTE stmt USING _binary 'b'; +EXECUTE stmt USING _binary 'b'; + +EXECUTE stmt USING _binary 'd'; +EXECUTE stmt USING _binary 'd'; + +EXECUTE stmt USING _ucs2 'b'; +EXECUTE stmt USING _ucs2 'b'; + +EXECUTE stmt USING _ucs2 'd'; +EXECUTE stmt USING _ucs2 'd'; + +EXECUTE stmt USING _latin1 'b'; +EXECUTE stmt USING _latin1 'b'; + +EXECUTE stmt USING _latin1 'd'; +EXECUTE stmt USING _latin1 'd'; + +CREATE TABLE t3 (c VARCHAR(10) CHARACTER SET ucs2); +INSERT INTO t3 VALUES ('b'); +PREPARE stmt FROM 'SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t3 WHERE t3.c = ?)'; +EXECUTE stmt USING 'b'; +EXECUTE stmt USING 'b'; +EXECUTE stmt USING 'd'; +EXECUTE stmt USING 'd'; +DROP TABLE t1, t2, t3; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index fe6b8feb4de..b7b0c981c2d 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -442,9 +442,18 @@ bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp) if (args[0]->cmp_type() == STRING_RESULT && args[1]->cmp_type() == STRING_RESULT) { + Query_arena *arena, backup; + arena= thd->activate_stmt_arena_if_needed(&backup); + DTCollation tmp; - if (agg_arg_charsets_for_comparison(tmp, args, 2)) - return true; + bool ret= agg_arg_charsets_for_comparison(tmp, args, 2); + + if (arena) + thd->restore_active_arena(arena, &backup); + + if (ret) + return ret; + cmp->m_compare_collation= tmp.collation; } // Convert constants when compared to int/year field diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 482bb106af7..fd94e580647 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -21018,6 +21018,102 @@ static void test_explain_meta() } +static void test_mdev_16128() +{ + int rc, res; + MYSQL_STMT *stmt; + MYSQL_BIND bind, bind_res; + char bind_arg_1[]="d", bind_arg_2[]="b"; + ulong length= 0; + const char *query= + "SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t2 WHERE t2.b = ?)"; + + myheader("test_mdev_16128"); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TABLE t1 (a VARCHAR(10))"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TABLE t2 (b VARCHAR(10) CHARACTER SET utf8)"); + myquery(rc); + + rc= mysql_query(mysql, "INSERT INTO t1 VALUES('b')"); + myquery(rc); + + rc= mysql_query(mysql, "INSERT INTO t2 VALUES('d')"); + myquery(rc); + + stmt= mysql_stmt_init(mysql); + check_stmt(stmt); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_execute(stmt, rc); + + memset(&bind, 0, sizeof(bind)); + bind.buffer_type= MYSQL_TYPE_STRING; + bind.buffer_length= strlen(bind_arg_1); + bind.buffer= bind_arg_1; + + rc= mysql_stmt_bind_param(stmt, &bind); + DIE_UNLESS(rc == 0); + + memset(&bind_res, 0, sizeof(bind_res)); + bind_res.buffer_type= MYSQL_TYPE_LONG; + bind_res.buffer= &res; + bind_res.is_null= NULL; + bind_res.length= &length; + + rc= mysql_stmt_bind_result(stmt, &bind_res); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_store_result(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + + /** + It's expected that the query + SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t2 WHERE t2.b = ?)" + executed in PS-mode and bound with the value 'd' returns exactly + one row containing the value (300). + */ + DIE_UNLESS(rc == 0); + DIE_UNLESS(bind_res.buffer_type == MYSQL_TYPE_LONG); + DIE_UNLESS(*((long *) bind_res.buffer) == 300); + + memset(&bind, 0, sizeof(bind)); + bind.buffer_type= MYSQL_TYPE_STRING; + bind.buffer_length= strlen(bind_arg_2); + bind.buffer= bind_arg_2; + + rc= mysql_stmt_bind_param(stmt, &bind); + DIE_UNLESS(rc == 0); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_store_result(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + /** + It's expected that the query + SELECT 300 FROM t1 WHERE EXISTS (SELECT 100 FROM t2 WHERE t2.b = ?)" + executed in PS-mode and bound with the value 'd' returns empty result set. + */ + DIE_UNLESS(rc == MYSQL_NO_DATA); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + myquery(rc); +} + + #ifndef EMBEDDED_LIBRARY #define MDEV19838_MAX_PARAM_COUNT 32 #define MDEV19838_FIELDS_COUNT 17 @@ -21169,6 +21265,7 @@ static void test_mdev19838() static struct my_tests_st my_tests[]= { + { "test_mdev_16128", test_mdev_16128 }, { "test_mdev_20516", test_mdev_20516 }, { "test_mdev24827", test_mdev24827 }, { "test_mdev_26145", test_mdev_26145 }, |