From 54c1031b74e88300103aeeef2e8990204a8584f5 Mon Sep 17 00:00:00 2001 From: Dmitry Shulga Date: Fri, 13 Dec 2024 09:29:23 +0700 Subject: [PATCH] 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. --- sql/sql_base.cc | 9 --- sql/sql_delete.cc | 7 --- sql/sql_insert.cc | 10 --- sql/sql_trigger.cc | 9 +++ sql/sql_update.cc | 5 -- tests/mysql_client_test.c | 124 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 31 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 0bfae16b23e..de744edfbae 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8784,22 +8784,13 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, 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, TRUE) || not_null_fields_have_null_values(table)) { - thd->bulk_param= save_bulk_param; return TRUE; } - thd->bulk_param= save_bulk_param; /* Re-calculate virtual fields to cater for cases when base columns are diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 7823350d7a3..25b3aef3ebe 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -812,17 +812,13 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, delete_history); if (delete_record) { - void *save_bulk_param= thd->bulk_param; - thd->bulk_param= nullptr; if (!delete_history && table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE)) { error= 1; - thd->bulk_param= save_bulk_param; break; } - thd->bulk_param= save_bulk_param; // no LIMIT / OFFSET 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)) { deleted++; - thd->bulk_param= nullptr; if (!delete_history && table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE)) { error= 1; - thd->bulk_param= save_bulk_param; break; } - thd->bulk_param= save_bulk_param; if (!--limit && using_limit) { error= -1; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 2dccf7af587..18c364c7fea 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1069,21 +1069,12 @@ 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++; @@ -1092,7 +1083,6 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list, error=1; break; } - thd->bulk_param= save_bulk_param; } /* diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index c788d66744a..f35a07347fe 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -2288,6 +2288,14 @@ bool Table_triggers_list::process_triggers(THD *thd, */ 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 { thd->lex->current_select= NULL; err_status= @@ -2297,6 +2305,7 @@ bool Table_triggers_list::process_triggers(THD *thd, &trigger->subject_table_grants); status_var_increment(thd->status_var.executed_triggers); } while (!err_status && (trigger= trigger->next)); + thd->bulk_param= save_bulk_param; thd->lex->current_select= save_current_select; thd->restore_sub_statement_state(&statement_state); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 7bc7041e161..4a13d83d52f 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1161,19 +1161,14 @@ error: rows_inserted++; } - void *save_bulk_param= thd->bulk_param; - thd->bulk_param= nullptr; - if (table->triggers && unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE))) { error= 1; - thd->bulk_param= save_bulk_param; break; } - thd->bulk_param= save_bulk_param; if (!--limit && using_limit) { diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 138eae10ab1..f06566cf208 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -22503,6 +22503,129 @@ static void test_mdev_34718_ad() rc= mysql_query(mysql, "DROP TABLE t1, t2"); 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 /* @@ -22856,6 +22979,7 @@ static struct my_tests_st my_tests[]= { { "test_mdev_34718_au", test_mdev_34718_au }, { "test_mdev_34718_bd", test_mdev_34718_bd }, { "test_mdev_34718_ad", test_mdev_34718_ad }, + { "test_mdev_34958", test_mdev_34958 }, #endif { 0, 0 } };