Commit 8b169949 authored by Dmitry Shulga's avatar Dmitry Shulga

MDEV-24411: Trigger doesn't work correctly with bulk insert

Executing an INSERT statement in PS mode having positional parameter
bound with an array could result in incorrect number of inserted rows
in case there is a BEFORE INSERT trigger that executes yet another
INSERT statement to put a copy of row being inserted into some table.

The reason for incorrect number of inserted rows is that a data structure
used for binding positional argument with its actual values is stored
in THD (this is thd->bulk_param) and reused on processing every INSERT
statement. It leads to consuming actual values bound with top-level
INSERT statement by other INSERT statements used by triggers' body.

To fix the issue, reset the thd->bulk_param temporary to the value nullptr
before invoking triggers and restore its value on finishing its execution.
parent d513a4ce
......@@ -1021,10 +1021,19 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
*/
restore_record(table,s->default_values); // Get empty record
table->reset_default_fields();
/*
Reset the sentinel thd->bulk_param in order not to consume the next
values of a bound array in case one of statement executed by
the trigger's body is INSERT statement.
*/
void *save_bulk_param= thd->bulk_param;
thd->bulk_param= nullptr;
if (unlikely(fill_record_n_invoke_before_triggers(thd, table, fields,
*values, 0,
TRG_EVENT_INSERT)))
{
thd->bulk_param= save_bulk_param;
if (values_list.elements != 1 && ! thd->is_error())
{
info.records++;
......@@ -1038,6 +1047,7 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
error=1;
break;
}
thd->bulk_param= save_bulk_param;
}
else
{
......@@ -1067,12 +1077,22 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
}
}
table->reset_default_fields();
/*
Reset the sentinel thd->bulk_param in order not to consume the next
values of a bound array in case one of statement executed by
the trigger's body is INSERT statement.
*/
void *save_bulk_param= thd->bulk_param;
thd->bulk_param= nullptr;
if (unlikely(fill_record_n_invoke_before_triggers(thd, table,
table->
field_to_fill(),
*values, 0,
TRG_EVENT_INSERT)))
{
thd->bulk_param= save_bulk_param;
if (values_list.elements != 1 && ! thd->is_error())
{
info.records++;
......@@ -1081,6 +1101,7 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
error=1;
break;
}
thd->bulk_param= save_bulk_param;
}
/*
......
......@@ -21870,6 +21870,103 @@ static void test_mdev19838()
rc = mysql_query(mysql, "drop table mdev19838");
myquery(rc);
}
static void test_mdev_24411()
{
int rc;
MYSQL_STMT *stmt;
MYSQL_BIND bind;
MYSQL_RES *result;
MYSQL_ROW row;
my_ulonglong row_count;
unsigned int vals[] = { 1, 2, 3};
unsigned int vals_array_len = 3;
const char *insert_stmt= "INSERT INTO t1 VALUES (?)";
myheader("test_mdev_24411");
rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
myquery(rc);
rc= mysql_query(mysql, "DROP TABLE IF EXISTS t2");
myquery(rc);
rc= mysql_query(mysql, "CREATE TABLE t1 (a INT)");
myquery(rc);
rc= mysql_query(mysql, "CREATE TABLE t2 (a INT)");
myquery(rc);
rc= mysql_query(mysql,
"CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW "
"BEGIN INSERT INTO t2 (a) VALUES (NEW.a); END;");
myquery(rc);
stmt= mysql_stmt_init(mysql);
check_stmt(stmt);
rc= mysql_stmt_prepare(stmt, insert_stmt, strlen(insert_stmt));
check_execute(stmt, rc);
memset(&bind, 0, sizeof(bind));
bind.buffer_type= MYSQL_TYPE_LONG;
bind.buffer= vals;
rc= mysql_stmt_attr_set(stmt, STMT_ATTR_ARRAY_SIZE, &vals_array_len);
check_execute(stmt, rc);
rc= mysql_stmt_bind_param(stmt, &bind);
check_execute(stmt, rc);
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
/*
It's expected that the INSERT statement adds three rows into
the table t1
*/
row_count = mysql_stmt_affected_rows(stmt);
DIE_UNLESS(row_count == 3);
/*
* Check that the BEFORE INSERT trigger of the table t1 does work correct
* and inserted the rows (1), (2), (3) into the table t2.
*/
rc= mysql_query(mysql, "SELECT 't1' tname, a FROM t1 "
"UNION SELECT 't2' tname, a FROM t2 ORDER BY tname,a");
myquery(rc);
result= mysql_store_result(mysql);
row = mysql_fetch_row(result);
DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 1);
row = mysql_fetch_row(result);
DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 2);
row = mysql_fetch_row(result);
DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 3);
row = mysql_fetch_row(result);
DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 1);
row = mysql_fetch_row(result);
DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 2);
row = mysql_fetch_row(result);
DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 3);
row= mysql_fetch_row(result);
DIE_UNLESS(row == NULL);
mysql_free_result(result);
mysql_stmt_close(stmt);
rc= mysql_query(mysql, "DROP TABLE t1, t2");
myquery(rc);
}
#endif // EMBEDDED_LIBRARY
......@@ -22307,6 +22404,9 @@ static struct my_tests_st my_tests[]= {
{ "test_mdev20261", test_mdev20261 },
{ "test_mdev_30159", test_mdev_30159 },
{ "test_connect_autocommit", test_connect_autocommit},
#ifndef EMBEDDED_LIBRARY
{ "test_mdev_24411", test_mdev_24411},
#endif
{ 0, 0 }
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment