diff --git a/sql/sql_class.h b/sql/sql_class.h index de9fbe15ac6..0022b97a85b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4668,6 +4668,14 @@ public: const Diagnostics_area *get_stmt_da() const { return m_stmt_da; } + /* + Bulk operation sums affected rows and warnings of all executions in + the diagnostics area, so statements of triggers and stored functions + called by it must not set the status + */ + bool in_bulk_sub_stmt() const + { return in_sub_stmt && m_stmt_da->is_bulk_op(); } + /// Sets Diagnostics-area for the current statement. void set_stmt_da(Diagnostics_area *da) { m_stmt_da= da; } @@ -6010,6 +6018,8 @@ my_ok(THD *thd, ulonglong affected_rows_arg= 0, ulonglong id= 0, { thd->set_row_count_func(affected_rows_arg); thd->set_affected_rows(affected_rows_arg); + if (thd->in_bulk_sub_stmt()) + return; thd->get_stmt_da()->set_ok_status(affected_rows_arg, id, message); } diff --git a/sql/sql_error.cc b/sql/sql_error.cc index a99750871e9..06de6c695d6 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -275,7 +275,7 @@ void Sql_condition::assign_defaults(THD *thd, const Sql_state_errno *from) Diagnostics_area::Diagnostics_area(bool initialize) - : is_bulk_execution(0), m_main_wi(0, false, initialize) + : m_bulk_status(DA_EMPTY), is_bulk_execution(0), m_main_wi(0, false, initialize) { push_warning_info(&m_main_wi); @@ -285,7 +285,7 @@ Diagnostics_area::Diagnostics_area(bool initialize) Diagnostics_area::Diagnostics_area(ulonglong warning_info_id, bool allow_unlimited_warnings, bool initialize) - : is_bulk_execution(0), + : m_bulk_status(DA_EMPTY), is_bulk_execution(0), m_main_wi(warning_info_id, allow_unlimited_warnings, initialize) { push_warning_info(&m_main_wi); @@ -319,15 +319,13 @@ Diagnostics_area::reset_diagnostics_area() get_warning_info()->clear_error_condition(); set_is_sent(false); /* - For BULK DML operations (e.g. UPDATE) the data member m_status - has the value DA_OK_BULK. Keep this value in order to handle - m_affected_rows, m_statement_warn_count in correct way. Else, - the number of rows and the number of warnings affected by - the last statement executed as part of a trigger fired by the dml - (e.g. UPDATE statement fires a trigger on AFTER UPDATE) would counts - rows modified by trigger's statement. + A bulk operation sums m_affected_rows and m_statement_warn_count of + all its executions, so the status stays DA_OK_BULK/DA_EOF_BULK once + set, also after an error handled in a trigger or stored function or + a reprepare. For RETURNING of non-optimized array binding the status + also shows that result set metadata was already sent. */ - m_status= is_bulk_op() ? DA_OK_BULK : DA_EMPTY; + m_status= is_bulk_op() ? m_bulk_status : DA_EMPTY; DBUG_VOID_RETURN; } @@ -350,10 +348,6 @@ Diagnostics_area::set_ok_status(ulonglong affected_rows, */ if (unlikely(is_error() || is_disabled())) DBUG_VOID_RETURN; - /* - When running a bulk operation, m_status will be DA_OK for the first - operation and set to DA_OK_BULK for all following operations. - */ if (m_status == DA_OK_BULK) { m_statement_warn_count+= current_statement_warn_count(); @@ -364,6 +358,8 @@ Diagnostics_area::set_ok_status(ulonglong affected_rows, m_statement_warn_count= current_statement_warn_count(); m_affected_rows= affected_rows; m_status= (is_bulk_op() ? DA_OK_BULK : DA_OK); + if (is_bulk_op()) + m_bulk_status= DA_OK_BULK; } m_last_insert_id= last_insert_id; if (message) @@ -411,6 +407,8 @@ Diagnostics_area::set_eof_status(THD *thd) else m_statement_warn_count= current_statement_warn_count(); m_status= (is_bulk_op() ? DA_EOF_BULK : DA_EOF); + if (is_bulk_op()) + m_bulk_status= DA_EOF_BULK; } DBUG_VOID_RETURN; diff --git a/sql/sql_error.h b/sql/sql_error.h index aa891b40f7a..c3dccb31294 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -1049,19 +1049,15 @@ public: bool is_disabled() const { return m_status == DA_DISABLED; } - void set_bulk_execution(bool bulk) { is_bulk_execution= bulk; } + void set_bulk_execution(bool bulk) + { + is_bulk_execution= bulk; + m_bulk_status= DA_EMPTY; + } bool is_bulk_op() const { return is_bulk_execution; } enum_diagnostics_status status() const { return m_status; } - void restore_bulk_status(enum_diagnostics_status status) - { - DBUG_ASSERT(is_bulk_op()); - DBUG_ASSERT(m_status == DA_OK_BULK); - DBUG_ASSERT(status == DA_EMPTY || status == DA_OK_BULK || - status == DA_EOF_BULK); - m_status= status; - } const char *message() const { @@ -1320,6 +1316,12 @@ private: enum_diagnostics_status m_status; + /* + DA_EMPTY until the bulk operation sets m_status to DA_OK_BULK or + DA_EOF_BULK, reset by set_bulk_execution() + */ + enum_diagnostics_status m_bulk_status; + my_bool is_bulk_execution; Warning_info m_main_wi; diff --git a/sql/sql_get_diagnostics.cc b/sql/sql_get_diagnostics.cc index 240975d2974..3dd7eae72be 100644 --- a/sql/sql_get_diagnostics.cc +++ b/sql/sql_get_diagnostics.cc @@ -59,7 +59,8 @@ Sql_cmd_get_diagnostics::execute(THD *thd) /* Bail out early if statement succeeded. */ if (! rv) { - thd->get_stmt_da()->set_ok_status(0, 0, NULL); + if (!thd->in_bulk_sub_stmt()) + thd->get_stmt_da()->set_ok_status(0, 0, NULL); DBUG_RETURN(false); } @@ -81,7 +82,7 @@ Sql_cmd_get_diagnostics::execute(THD *thd) message); /* Appending might have failed. */ - if (unlikely(!(rv= thd->is_error()))) + if (unlikely(!(rv= thd->is_error())) && !thd->in_bulk_sub_stmt()) thd->get_stmt_da()->set_ok_status(0, 0, NULL); DBUG_RETURN(rv); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index f12c590a045..3a1be412943 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -4962,8 +4962,6 @@ reexecute: /* items must not cache values of the parameters of the first row */ if (bulk_optimized) set_params_changing_value(true); - Diagnostics_area::enum_diagnostics_status da_status= - thd->get_stmt_da()->status(); error= execute(expanded_query, open_cursor) || thd->is_error(); if (!bulk_optimized) thd->get_stmt_da()->set_bulk_warn_count(warn_count); @@ -4994,11 +4992,6 @@ reexecute: { DBUG_ASSERT(thd->get_stmt_da()->sql_errno() == ER_NEED_REPREPARE); thd->clear_error(); - /* - The failed attempt must not change the status, because it shows - whether result set metadata was already sent - */ - thd->get_stmt_da()->restore_bulk_status(da_status); error= reprepare(); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index a50b5245647..cd6dff0cbac 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1395,7 +1395,7 @@ update_end: (ulong) thd->get_stmt_da()->current_statement_warn_count()); my_ok(thd, (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated, id, buff); - if (thd->get_stmt_da()->is_bulk_op()) + if (thd->get_stmt_da()->is_bulk_op() && !thd->in_bulk_sub_stmt()) { /* Update the diagnostics message sent to a client with number of actual diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 28eeaf7ad00..d7e8e67cd6a 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -23805,13 +23805,111 @@ static void test_mdev_23864() myquery(rc); test_mdev_23864_query("UPDATE t1 SET val= f1(10 * ?) WHERE id= ? / 10", "INSERT INTO t1 VALUES (1, 0), (2, 0), (3, 0)", - same, 0, FALSE); + same, 3, FALSE); test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES " "(?, f1((SELECT b FROM t2 WHERE a = ?)))", - "DELETE FROM t4", looked_up, 0, FALSE); + "DELETE FROM t4", looked_up, 3, FALSE); rc= mysql_query(mysql, "DROP FUNCTION f1"); myquery(rc); + /* rows changed by a trigger are not counted as affected */ + rc= mysql_query(mysql, "CREATE TRIGGER t1_bi BEFORE INSERT ON t1 " + "FOR EACH ROW INSERT INTO t4 (v) VALUES (NEW.val)"); + myquery(rc); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES (?, ?)", NULL, + same, 3, FALSE); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES " + "(?, (SELECT b FROM t2 WHERE a = ?))", NULL, + looked_up, 3, FALSE); + rc= mysql_query(mysql, "DROP TRIGGER t1_bi"); + myquery(rc); + rc= mysql_query(mysql, "CREATE TRIGGER t1_ai AFTER INSERT ON t1 " + "FOR EACH ROW UPDATE t4 SET v= v + 1"); + myquery(rc); + rc= mysql_query(mysql, "CREATE TRIGGER t1_au AFTER UPDATE ON t1 " + "FOR EACH ROW UPDATE t4 SET v= v + 1"); + myquery(rc); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES (?, ?)", NULL, + same, 3, FALSE); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES " + "(?, (SELECT b FROM t2 WHERE a = ?))", NULL, + looked_up, 3, FALSE); + test_mdev_23864_query("UPDATE t1 SET val= 10 * ? WHERE id= ? / 10", + "INSERT INTO t1 VALUES (1, 0), (2, 0), (3, 0)", + same, 3, FALSE); + rc= mysql_query(mysql, "DROP TRIGGER t1_ai"); + myquery(rc); + rc= mysql_query(mysql, "DROP TRIGGER t1_au"); + myquery(rc); + + /* an error handled in a trigger does not reset the affected rows */ + rc= mysql_query(mysql, "CREATE TABLE t7 (id INT PRIMARY KEY)"); + myquery(rc); + rc= mysql_query(mysql, "CREATE TRIGGER t1_ai AFTER INSERT ON t1 " + "FOR EACH ROW BEGIN " + "DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END; " + "INSERT INTO t7 VALUES (NEW.id); END"); + myquery(rc); + rc= mysql_query(mysql, "CREATE TRIGGER t1_au AFTER UPDATE ON t1 " + "FOR EACH ROW BEGIN " + "DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END; " + "INSERT INTO t7 VALUES (NEW.id); END"); + myquery(rc); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES (?, ?)", + "REPLACE INTO t7 VALUES (2)", same, 3, FALSE); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES " + "(?, (SELECT b FROM t2 WHERE a = ?))", + "REPLACE INTO t7 VALUES (2)", looked_up, 3, FALSE); + test_mdev_23864_query("UPDATE t1 SET val= 10 * ? WHERE id= ? / 10", + "INSERT INTO t1 VALUES (1, 0), (2, 0), (3, 0)", + same, 3, FALSE); + rc= mysql_query(mysql, "DROP TRIGGER t1_ai"); + myquery(rc); + rc= mysql_query(mysql, "DROP TRIGGER t1_au"); + myquery(rc); + rc= mysql_query(mysql, "DROP TABLE t7"); + myquery(rc); + + /* statements of a trigger which set the diagnostics area status */ + rc= mysql_query(mysql, "CREATE TRIGGER t1_ai AFTER INSERT ON t1 " + "FOR EACH ROW BEGIN DECLARE n INT; " + "GET DIAGNOSTICS n= NUMBER; " + "SELECT COUNT(*) INTO @a FROM t2; END"); + myquery(rc); + rc= mysql_query(mysql, "CREATE TRIGGER t1_au AFTER UPDATE ON t1 " + "FOR EACH ROW BEGIN DECLARE n INT; " + "GET DIAGNOSTICS n= NUMBER; " + "SELECT COUNT(*) INTO @a FROM t2; END"); + myquery(rc); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES (?, ?)", NULL, + same, 3, FALSE); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES (?, ?) " + "RETURNING id, val", NULL, same, 3, TRUE); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES " + "(?, (SELECT b FROM t2 WHERE a = ?))", NULL, + looked_up, 3, FALSE); + test_mdev_23864_query("INSERT INTO t1 (id, val) VALUES " + "(?, (SELECT b FROM t2 WHERE a = ?)) " + "RETURNING id, val", NULL, looked_up, 3, TRUE); + test_mdev_23864_query("UPDATE t1 SET val= 10 * ? WHERE id= ? / 10", + "INSERT INTO t1 VALUES (1, 0), (2, 0), (3, 0)", + same, 3, FALSE); + rc= mysql_query(mysql, "DROP TRIGGER t1_ai"); + myquery(rc); + rc= mysql_query(mysql, "DROP TRIGGER t1_au"); + myquery(rc); + + /* the statement is killed in not the first execution */ + test_mdev_23864_exec("UPDATE t1 SET val= ? + SLEEP(id = 3) " + "WHERE id= ? / 10", + (const char *[]) {"DELETE FROM t1", + "INSERT INTO t1 VALUES " + "(1, 0), (2, 0), (3, 0)", + "SET max_statement_time= 0.5", + NULL}, ER_STATEMENT_TIMEOUT, 0, 0); + rc= mysql_query(mysql, "SET max_statement_time= DEFAULT"); + myquery(rc); + /* The first generated id is reported, as for the optimized execution */ rc= mysql_query(mysql, "CREATE TABLE t3 (id INT AUTO_INCREMENT PRIMARY KEY," " val INT)");