1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

MDEV-34718: Trigger doesn't work correctly with bulk update

Running an UPDATE statement in PS mode and having positional
parameter(s) bound with an array of actual values (that is
prepared to be run in bulk mode) results in incorrect behaviour
in presence of on update trigger that also executes an UPDATE
statement. The same is true for handling a DELETE statement in
presence of on delete trigger. Typically, the visible effect of
such incorrect behaviour is expressed in a wrong number of
updated/deleted rows of a target table. Additionally, in case UPDATE
statement, a number of modified rows and a state message returned
by a statement contains wrong information about a number of modified rows.

The reason for incorrect number of updated/deleted 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/UPDATE/DELETE statement. It leads to
consuming actual values bound with top-level UPDATE/DELETE statement
by other DML 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.

The second part of the problem relating with wrong value of affected
rows reported by Connector/C API is caused by the fact that diagnostics
area is reused by an original DML statement and a statement invoked
by a trigger. This fact should be take into account on finalizing a
state of diagnostics area on completion running of a statement.

Important remark: in case the macros DBUG_OFF is on, call of the method
  Diagnostics_area::reset_diagnostics_area()
results in reset of the data members
  m_affected_rows, m_statement_warn_count.
Values of these data members of the class Diagnostics_area are used on
sending OK and EOF messages. In case DML statement is executed in PS bulk
mode such resetting results in sending wrong result values to a client
for affected rows in case the DML statement fires a triggers. So, reset
these data members only in case the current statement being processed
is not run in bulk mode.
This commit is contained in:
Dmitry Shulga
2024-08-16 12:43:35 +07:00
parent f41a120298
commit ba5482ffc2
7 changed files with 534 additions and 14 deletions

View File

@ -1021,19 +1021,11 @@ 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++;
@ -1047,7 +1039,6 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
error=1;
break;
}
thd->bulk_param= save_bulk_param;
}
else
{
@ -1334,7 +1325,18 @@ values_loop_end:
*/
if (returning)
result->send_eof();
else
else if (!(thd->in_sub_stmt & SUB_STMT_TRIGGER))
/*
Set the status and the number of affected rows in Diagnostics_area
only in case the INSERT statement is not processed as part of a trigger
invoked by some other DML statement. Else we would result in incorrect
number of affected rows for bulk DML operations, e.g. the UPDATE
statement (called via PS protocol). It would happen since the data
member Diagnostics_area::m_affected_rows modified twice per DML
statement - first time at the end of handling the INSERT statement
invoking by a trigger fired on handling the original DML statement,
and the second time at the end of handling the original DML statement.
*/
my_ok(thd, info.copied + info.deleted +
((thd->client_capabilities & CLIENT_FOUND_ROWS) ?
info.touched : info.updated), id);
@ -1356,7 +1358,18 @@ values_loop_end:
(long) thd->get_stmt_da()->current_statement_warn_count());
if (returning)
result->send_eof();
else
else if (!(thd->in_sub_stmt & SUB_STMT_TRIGGER))
/*
Set the status and the number of affected rows in Diagnostics_area
only in case the INSERT statement is not processed as part of a trigger
invoked by some other DML statement. Else we would result in incorrect
number of affected rows for bulk DML operations, e.g. the UPDATE
statement (called via PS protocol). It would happen since the data
member Diagnostics_area::m_affected_rows modified twice per DML
statement - first time at the end of handling the INSERT statement
invoking by a trigger fired on handling the original DML statement,
and the second time at the end of handling the original DML statement.
*/
::my_ok(thd, info.copied + info.deleted + updated, id, buff);
}
thd->abort_on_warning= 0;