Skip to content

Commit

Permalink
MDEV-24411: Trigger doesn't work correctly with bulk insert
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dmitryshulga committed Jun 25, 2024
1 parent d513a4c commit 8b16994
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
21 changes: 21 additions & 0 deletions sql/sql_insert.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand All @@ -1038,6 +1047,7 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
error=1;
break;
}
thd->bulk_param= save_bulk_param;
}
else
{
Expand Down Expand Up @@ -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++;
Expand All @@ -1081,6 +1101,7 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
error=1;
break;
}
thd->bulk_param= save_bulk_param;
}

/*
Expand Down
100 changes: 100 additions & 0 deletions tests/mysql_client_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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 }
};

Expand Down

0 comments on commit 8b16994

Please sign in to comment.