1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

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

This bug has the same nature as the issues
  MDEV-34718: Trigger doesn't work correctly with bulk update
  MDEV-24411: Trigger doesn't work correctly with bulk insert

To fix the issue covering all use cases, resetting the thd->bulk_param
temporary to the value nullptr before invoking triggers and restoring
its original value on finishing execution of a trigger is moved to the method
  Table_triggers_list::process_triggers
that be invoked ultimately for any kind of triggers.
This commit is contained in:
Dmitry Shulga
2024-12-13 09:29:23 +07:00
parent 0b7fa4c267
commit 54c1031b74
6 changed files with 133 additions and 31 deletions

View File

@ -8784,22 +8784,13 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
if (!result && triggers) if (!result && triggers)
{ {
void *save_bulk_param= thd->bulk_param;
/*
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.
*/
thd->bulk_param= nullptr;
if (triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, if (triggers->process_triggers(thd, event, TRG_ACTION_BEFORE,
TRUE) || TRUE) ||
not_null_fields_have_null_values(table)) not_null_fields_have_null_values(table))
{ {
thd->bulk_param= save_bulk_param;
return TRUE; return TRUE;
} }
thd->bulk_param= save_bulk_param;
/* /*
Re-calculate virtual fields to cater for cases when base columns are Re-calculate virtual fields to cater for cases when base columns are

View File

@ -812,17 +812,13 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
delete_history); delete_history);
if (delete_record) if (delete_record)
{ {
void *save_bulk_param= thd->bulk_param;
thd->bulk_param= nullptr;
if (!delete_history && table->triggers && if (!delete_history && table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE, FALSE)) TRG_ACTION_BEFORE, FALSE))
{ {
error= 1; error= 1;
thd->bulk_param= save_bulk_param;
break; break;
} }
thd->bulk_param= save_bulk_param;
// no LIMIT / OFFSET // no LIMIT / OFFSET
if (returning && result->send_data(returning->item_list) < 0) if (returning && result->send_data(returning->item_list) < 0)
@ -853,16 +849,13 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
if (likely(!error)) if (likely(!error))
{ {
deleted++; deleted++;
thd->bulk_param= nullptr;
if (!delete_history && table->triggers && if (!delete_history && table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, FALSE)) TRG_ACTION_AFTER, FALSE))
{ {
error= 1; error= 1;
thd->bulk_param= save_bulk_param;
break; break;
} }
thd->bulk_param= save_bulk_param;
if (!--limit && using_limit) if (!--limit && using_limit)
{ {
error= -1; error= -1;

View File

@ -1069,21 +1069,12 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
} }
table->reset_default_fields(); 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, if (unlikely(fill_record_n_invoke_before_triggers(thd, table,
table-> table->
field_to_fill(), field_to_fill(),
*values, 0, *values, 0,
TRG_EVENT_INSERT))) TRG_EVENT_INSERT)))
{ {
thd->bulk_param= save_bulk_param;
if (values_list.elements != 1 && ! thd->is_error()) if (values_list.elements != 1 && ! thd->is_error())
{ {
info.records++; info.records++;
@ -1092,7 +1083,6 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
error=1; error=1;
break; break;
} }
thd->bulk_param= save_bulk_param;
} }
/* /*

View File

@ -2288,6 +2288,14 @@ bool Table_triggers_list::process_triggers(THD *thd,
*/ */
save_current_select= thd->lex->current_select; save_current_select= thd->lex->current_select;
/*
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 a DML statement.
*/
void *save_bulk_param= thd->bulk_param;
thd->bulk_param= nullptr;
do { do {
thd->lex->current_select= NULL; thd->lex->current_select= NULL;
err_status= err_status=
@ -2297,6 +2305,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
&trigger->subject_table_grants); &trigger->subject_table_grants);
status_var_increment(thd->status_var.executed_triggers); status_var_increment(thd->status_var.executed_triggers);
} while (!err_status && (trigger= trigger->next)); } while (!err_status && (trigger= trigger->next));
thd->bulk_param= save_bulk_param;
thd->lex->current_select= save_current_select; thd->lex->current_select= save_current_select;
thd->restore_sub_statement_state(&statement_state); thd->restore_sub_statement_state(&statement_state);

View File

@ -1161,19 +1161,14 @@ error:
rows_inserted++; rows_inserted++;
} }
void *save_bulk_param= thd->bulk_param;
thd->bulk_param= nullptr;
if (table->triggers && if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, TRUE))) TRG_ACTION_AFTER, TRUE)))
{ {
error= 1; error= 1;
thd->bulk_param= save_bulk_param;
break; break;
} }
thd->bulk_param= save_bulk_param;
if (!--limit && using_limit) if (!--limit && using_limit)
{ {

View File

@ -22503,6 +22503,129 @@ static void test_mdev_34718_ad()
rc= mysql_query(mysql, "DROP TABLE t1, t2"); rc= mysql_query(mysql, "DROP TABLE t1, t2");
myquery(rc); myquery(rc);
} }
/* Test case for bulk INSERT in presence of AFTER INSERT trigger */
static void test_mdev_34958()
{
int rc;
MYSQL_STMT *stmt_insert;
MYSQL_BIND bind[2];
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 (?)";
/* Set up test's environment */
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_ai AFTER INSERT ON t1 "
"FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);");
stmt_insert = mysql_stmt_init(mysql);
if (!stmt_insert)
{
fprintf(stderr, "mysql_stmt_init failed: Error: %s\n",
mysql_error(mysql));
exit(1);
}
rc= mysql_stmt_prepare(stmt_insert, insert_stmt, strlen(insert_stmt));
if (rc)
{
fprintf(stderr, "mysql_stmt_prepare failed: %s\n",
mysql_stmt_error(stmt_insert));
exit(1);
}
memset(&bind[0], 0, sizeof(MYSQL_BIND));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= vals;
rc= mysql_stmt_attr_set(stmt_insert, STMT_ATTR_ARRAY_SIZE, &vals_array_len);
if (rc)
{
fprintf(stderr, "mysql_stmt_prepare failed: %s\n",
mysql_stmt_error(stmt_insert));
exit(1);
}
rc= mysql_stmt_bind_param(stmt_insert, bind);
if (rc)
{
fprintf(stderr, "mysql_stmt_bind_param failed: %s\n",
mysql_stmt_error(stmt_insert));
exit(1);
}
rc= mysql_stmt_execute(stmt_insert);
if (rc)
{
fprintf(stderr, "mysql_stmt_execute failed: %s\n",
mysql_stmt_error(stmt_insert));
exit(1);
}
/*
It's expected that the INSERT statement adds three rows into
the table t1
*/
row_count = mysql_stmt_affected_rows(stmt_insert);
if (row_count != 3)
{
fprintf(stderr, "Wrong number of affected rows (%llu), expected 3\n",
row_count);
exit(1);
}
/*
* Check that the AFTER 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");
if (rc)
{
fprintf(stderr, "Query failed: %s\n", mysql_error(mysql));
}
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_insert);
/* Clean up */
rc= mysql_query(mysql, "DROP TABLE t1, t2");
myquery(rc);
}
#endif // EMBEDDED_LIBRARY #endif // EMBEDDED_LIBRARY
/* /*
@ -22856,6 +22979,7 @@ static struct my_tests_st my_tests[]= {
{ "test_mdev_34718_au", test_mdev_34718_au }, { "test_mdev_34718_au", test_mdev_34718_au },
{ "test_mdev_34718_bd", test_mdev_34718_bd }, { "test_mdev_34718_bd", test_mdev_34718_bd },
{ "test_mdev_34718_ad", test_mdev_34718_ad }, { "test_mdev_34718_ad", test_mdev_34718_ad },
{ "test_mdev_34958", test_mdev_34958 },
#endif #endif
{ 0, 0 } { 0, 0 }
}; };