diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index 39ff1cdd8dc..67c7c09859c 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -2000,7 +2000,7 @@ struct my_option xb_server_options[] = 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0 }, {"check-privileges", OPT_XTRA_CHECK_PRIVILEGES, "Check database user " - "privileges fro the backup user", + "privileges for the backup user", &opt_check_privileges, &opt_check_privileges, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0 }, diff --git a/mysql-test/include/not_valgrind_build.inc b/mysql-test/include/not_valgrind_build.inc new file mode 100644 index 00000000000..b62a1bc953b --- /dev/null +++ b/mysql-test/include/not_valgrind_build.inc @@ -0,0 +1,4 @@ +if (`select version() like '%valgrind%' || version() like '%asan%'`) +{ + skip Does not run with binaries built with valgrind or asan; +} diff --git a/mysql-test/main/item_types.result b/mysql-test/main/item_types.result index 40db01f2609..0193d33be6d 100644 --- a/mysql-test/main/item_types.result +++ b/mysql-test/main/item_types.result @@ -27,4 +27,20 @@ a DROP TABLE t1; DROP VIEW v1, v2; +# +# MDEV-34771: Types mismatch when cloning items causes debug assertion +# +CREATE VIEW t AS SELECT 1 AS a; +SELECT * FROM t WHERE a=b''; +a +drop view t; +# +# MDEV-34776: Assertion failure in Item_string::do_build_clone +# +CREATE VIEW v AS SELECT version() AS f; +SELECT * FROM v WHERE f = '10.5.20'; +f +drop view v; +# # End of 10.5 tests +# diff --git a/mysql-test/main/item_types.test b/mysql-test/main/item_types.test index 6f10d6bf71a..2818ae582af 100644 --- a/mysql-test/main/item_types.test +++ b/mysql-test/main/item_types.test @@ -30,4 +30,22 @@ SELECT * FROM v2 WHERE a='' AND CASE '' WHEN '' THEN '' ELSE a END=''; DROP TABLE t1; DROP VIEW v1, v2; +--echo # +--echo # MDEV-34771: Types mismatch when cloning items causes debug assertion +--echo # + +CREATE VIEW t AS SELECT 1 AS a; +SELECT * FROM t WHERE a=b''; +drop view t; + +--echo # +--echo # MDEV-34776: Assertion failure in Item_string::do_build_clone +--echo # + +CREATE VIEW v AS SELECT version() AS f; +SELECT * FROM v WHERE f = '10.5.20'; +drop view v; + +--echo # --echo # End of 10.5 tests +--echo # diff --git a/mysql-test/main/sp-no-valgrind.test b/mysql-test/main/sp-no-valgrind.test index 1b4a0f84f1e..6bacc7b150c 100644 --- a/mysql-test/main/sp-no-valgrind.test +++ b/mysql-test/main/sp-no-valgrind.test @@ -1,5 +1,5 @@ --source include/not_msan.inc ---source include/not_valgrind.inc +--source include/not_valgrind_build.inc --echo # MDEV-20699 do not cache SP in SHOW CREATE --echo # Warmup round, this might allocate some memory for session variable diff --git a/mysql-test/main/subselect.test b/mysql-test/main/subselect.test index badf55ca958..1d42bcb3a84 100644 --- a/mysql-test/main/subselect.test +++ b/mysql-test/main/subselect.test @@ -5942,7 +5942,9 @@ WHERE SLEEP(0.1) OR c < 'p' OR b = ( SELECT MIN(b) FROM t2 ); --enable_ps2_protocol --echo # The following shows that t2 was indeed scanned with a full scan. +--sorted_result show table_statistics; +--sorted_result show index_statistics; set global userstat=@tmp_mdev410; diff --git a/sql/item.h b/sql/item.h index 2e2af859f3d..1723a1bf3fe 100644 --- a/sql/item.h +++ b/sql/item.h @@ -4925,6 +4925,9 @@ public: Item_string_sys(THD *thd, const char *str): Item_string(thd, str, (uint) strlen(str), system_charset_info) { } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + Item *do_build_clone(THD *thd) const override { return get_copy(thd); } }; @@ -4939,6 +4942,9 @@ public: Item_string(thd, str, (uint) strlen(str), &my_charset_latin1, DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII) { } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + Item *do_build_clone(THD *thd) const override { return get_copy(thd); } }; @@ -4975,6 +4981,9 @@ public: // require fix_fields() to be re-run for every statement. return mark_unsupported_function(func_name.str, arg, VCOL_TIME_FUNC); } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + Item *do_build_clone(THD *thd) const override { return get_copy(thd); } }; @@ -4992,6 +5001,9 @@ public: { return mark_unsupported_function("safe_string", arg, VCOL_IMPOSSIBLE); } + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + Item *do_build_clone(THD *thd) const override { return get_copy(thd); } }; @@ -5153,6 +5165,9 @@ class Item_bin_string: public Item_hex_hybrid public: Item_bin_string(THD *thd, const char *str, size_t str_length); void print(String *str, enum_query_type query_type) override; + Item *do_get_copy(THD *thd) const override + { return get_item_copy(thd, this); } + Item *do_build_clone(THD *thd) const override { return get_copy(thd); } }; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index b9509fe66d8..328dbcdc3e7 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -9206,10 +9206,22 @@ 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 c55b8789c8f..9bdadf10fc0 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -830,13 +830,17 @@ 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) @@ -867,13 +871,16 @@ 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_error.cc b/sql/sql_error.cc index 76ed0ca2248..2fb1133892f 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -309,14 +309,27 @@ Diagnostics_area::reset_diagnostics_area() m_message[0]= '\0'; Sql_state_errno::clear(); Sql_user_condition_identity::clear(); - m_affected_rows= 0; m_last_insert_id= 0; - m_statement_warn_count= 0; + if (!is_bulk_op()) + { + m_affected_rows= 0; + m_statement_warn_count= 0; + } #endif get_warning_info()->clear_error_condition(); set_is_sent(false); /** Tiny reset in debug mode to see garbage right away */ - m_status= DA_EMPTY; + if (!is_bulk_op()) + /* + 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. + */ + m_status= DA_EMPTY; DBUG_VOID_RETURN; } diff --git a/sql/sql_error.h b/sql/sql_error.h index b5afdf9b372..f8332041862 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -1078,6 +1078,11 @@ public: return m_affected_rows; } + void set_message(const char *msg) + { + strmake_buf(m_message, msg); + } + ulonglong last_insert_id() const { DBUG_ASSERT(m_status == DA_OK || m_status == DA_OK_BULK); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 5b2fe9e24ea..3523e2a52c7 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1053,19 +1053,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++; @@ -1079,7 +1071,6 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list, error=1; break; } - thd->bulk_param= save_bulk_param; } else { @@ -1372,7 +1363,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); @@ -1394,7 +1396,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; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 8e7aa3d3ee3..0dfcb47e310 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1165,13 +1165,19 @@ 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) { @@ -1361,6 +1367,20 @@ 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()) + { + /* + Update the diagnostics message sent to a client with number of actual + rows update by the statement. For bulk UPDATE operation it should be + done after returning from my_ok() since the final number of updated + rows be knows on finishing the entire bulk update statement. + */ + my_snprintf(buff, sizeof(buff), ER_THD(thd, ER_UPDATE_INFO), + (ulong) thd->get_stmt_da()->affected_rows(), + (ulong) thd->get_stmt_da()->affected_rows(), + (ulong) thd->get_stmt_da()->current_statement_warn_count()); + thd->get_stmt_da()->set_message(buff); + } DBUG_PRINT("info",("%ld records updated", (long) updated)); } thd->count_cuted_fields= CHECK_FIELD_IGNORE; /* calc cuted fields */ diff --git a/storage/innobase/buf/buf0lru.cc b/storage/innobase/buf/buf0lru.cc index 6e98cb7c66b..32f6b523b56 100644 --- a/storage/innobase/buf/buf0lru.cc +++ b/storage/innobase/buf/buf0lru.cc @@ -807,7 +807,7 @@ bool buf_LRU_free_page(buf_page_t *bpage, bool zip) break; case 1: mysql_mutex_lock(&buf_pool.flush_list_mutex); - if (const lsn_t om = bpage->oldest_modification()) { + if (ut_d(const lsn_t om =) bpage->oldest_modification()) { ut_ad(om == 1); buf_pool.delete_from_flush_list(bpage); } diff --git a/storage/innobase/include/page0page.h b/storage/innobase/include/page0page.h index 2978656b508..5023e237457 100644 --- a/storage/innobase/include/page0page.h +++ b/storage/innobase/include/page0page.h @@ -421,8 +421,7 @@ inline void page_rec_set_n_owned(buf_block_t *block, rec_t *rec, ulint n_owned, ut_ad(block->page.frame == page_align(rec)); ut_ad(comp == (page_is_comp(block->page.frame) != 0)); - if (page_zip_des_t *page_zip= compressed - ? buf_block_get_page_zip(block) : nullptr) + if (compressed && is_buf_block_get_page_zip(block)) { ut_ad(comp); rec_set_bit_field_1(rec, n_owned, REC_NEW_N_OWNED, diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 457c3b49c46..0a4845f7763 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -1822,7 +1822,7 @@ trx_print_low( /*!< in: mem_heap_get_size(trx->lock.lock_heap) */ { if (const trx_id_t id = trx->id) { - fprintf(f, "TRANSACTION " TRX_ID_FMT, trx->id); + fprintf(f, "TRANSACTION " TRX_ID_FMT, id); } else { fprintf(f, "TRANSACTION (%p)", trx); } diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index bf41df3baf6..28d5dfb392e 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -22062,6 +22062,452 @@ static void test_mdev_30159() myquery(rc); } + +#ifndef EMBEDDED_LIBRARY +/** + Test case for bulk UPDATE against a table with an active AFTER UPDATE + trigger. +*/ + +static void test_mdev_34718_au() +{ + int rc; + MYSQL_STMT *stmt_update; + MYSQL_BIND bind[2]; + unsigned int vals[]= { 1, 2, 3}; + unsigned int new_vals[]= { 5, 6, 7}; + unsigned int vals_array_len= 3; + my_ulonglong row_count; + MYSQL_RES *result; + MYSQL_ROW row; + const char *update_stmt= "UPDATE t1 SET a = ? WHERE a = ?"; + const char *update_stmt_state_info; + + myheader("test_mdev_34718_au"); + + /* 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, "INSERT INTO t1 VALUES (1), (2), (3)"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TRIGGER t1_au AFTER UPDATE ON t1 " + "FOR EACH ROW BEGIN INSERT INTO t2 (a) VALUES (NEW.a); END;"); + + stmt_update= mysql_stmt_init(mysql); + check_stmt(stmt_update); + + rc= mysql_stmt_prepare(stmt_update, update_stmt, strlen(update_stmt)); + check_execute(stmt_update, rc); + + memset(&bind[0], 0, sizeof(MYSQL_BIND)); + memset(&bind[1], 0, sizeof(MYSQL_BIND)); + + bind[0].buffer_type= MYSQL_TYPE_LONG; + bind[0].buffer= new_vals; + + bind[1].buffer_type= MYSQL_TYPE_LONG; + bind[1].buffer= vals; + + /* + Every input positional parameter is bound with array of 3 elements + containing actual values for positional parameters + */ + rc= mysql_stmt_attr_set(stmt_update, STMT_ATTR_ARRAY_SIZE, &vals_array_len); + check_execute(stmt_update, rc); + + rc= mysql_stmt_bind_param(stmt_update, bind); + check_execute(stmt_update, rc); + + /* + Execution of this prepared statement replaces the table rows (1), (2), (3) + with values (5), (6), (7) + */ + rc= mysql_stmt_execute(stmt_update); + check_execute(stmt_update, rc); + + /* + Check that the BULK UPDATE statement affects exactly 3 rows + */ + row_count = mysql_stmt_affected_rows(stmt_update); + DIE_UNLESS(row_count == 3); + + update_stmt_state_info= mysql_info(mysql); + + /* + Check that information about executed operation is matched with + the expected result + */ + DIE_UNLESS(!strcmp("Rows matched: 3 Changed: 3 Warnings: 0", + update_stmt_state_info)); + + /* + * Check that the AFTER UPDATE trigger of the table t1 does work correctly + * and inserts the rows (5), (6), (7) 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]) == 5); + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 6); + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 7); + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 5); + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 6); + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 7); + + row= mysql_fetch_row(result); + DIE_UNLESS(row == NULL); + + mysql_free_result(result); + + mysql_stmt_close(stmt_update); + + /* Clean up */ + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + myquery(rc); +} + + +/** + Test case for bulk UPDATE against a table with an active BEFORE UPDATE + trigger. +*/ + +static void test_mdev_34718_bu() +{ + int rc; + MYSQL_STMT *stmt_update; + MYSQL_BIND bind[2]; + unsigned int vals[]= { 1, 2, 3}; + unsigned int new_vals[]= { 5, 6, 7}; + unsigned int vals_array_len= 3; + my_ulonglong row_count; + MYSQL_RES *result; + MYSQL_ROW row; + const char *update_stmt= "UPDATE t1 SET a = ? WHERE a = ?"; + const char *update_stmt_state_info; + + myheader("test_mdev_34718_bu"); + + /* 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, "INSERT INTO t1 VALUES (1), (2), (3)"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TRIGGER t1_au BEFORE UPDATE ON t1 " + "FOR EACH ROW BEGIN INSERT INTO t2 (a) VALUES (NEW.a); END;"); + + /* Initialize the prepared statement and set it up for bulk operations */ + stmt_update= mysql_stmt_init(mysql); + check_stmt(stmt_update); + + rc= mysql_stmt_prepare(stmt_update, update_stmt, strlen(update_stmt)); + check_execute(stmt_update, rc); + + memset(&bind[0], 0, sizeof(MYSQL_BIND)); + memset(&bind[1], 0, sizeof(MYSQL_BIND)); + + bind[0].buffer_type= MYSQL_TYPE_LONG; + bind[0].buffer= new_vals; + + bind[1].buffer_type= MYSQL_TYPE_LONG; + bind[1].buffer= vals; + + /* + Every input positional parameter is bound with array of 3 elements + containing actual values for positional parameters + */ + rc= mysql_stmt_attr_set(stmt_update, STMT_ATTR_ARRAY_SIZE, &vals_array_len); + check_execute(stmt_update, rc); + + rc= mysql_stmt_bind_param(stmt_update, bind); + check_execute(stmt_update, rc); + + /* + Execution of this prepared statement replaces the table rows (1), (2), (3) + with values (5), (6), (7) + */ + rc= mysql_stmt_execute(stmt_update); + check_execute(stmt_update, rc); + + /* + Check that the BULK UPDATE statement affects exactly 3 rows + */ + row_count= mysql_stmt_affected_rows(stmt_update); + DIE_UNLESS(row_count == 3); + + update_stmt_state_info= mysql_info(mysql); + + /* + Check that information about executed operation is matched with + the expected result + */ + DIE_UNLESS(!strcmp("Rows matched: 3 Changed: 3 Warnings: 0", + update_stmt_state_info)); + + /* + * Check that the BEFORE UPDATE trigger of the table t1 does work correctly + * and inserts the rows (5), (6), (7) 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]) == 5); + + row= mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 6); + + row= mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t1") == 0 && atoi(row[1]) == 7); + + row= mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 5); + + row= mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 6); + + row= mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "t2") == 0 && atoi(row[1]) == 7); + + row= mysql_fetch_row(result); + DIE_UNLESS(row == NULL); + + mysql_free_result(result); + + mysql_stmt_close(stmt_update); + + /* Clean up */ + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + myquery(rc); +} + + +/** + Test case for bulk DELETE against a table with an active BEFORE DELETE + trigger. +*/ + +static void test_mdev_34718_bd() +{ + int rc; + MYSQL_STMT *stmt_delete; + MYSQL_BIND bind[1]; + unsigned int vals[]= { 1, 2, 3}; + unsigned int vals_array_len= 3; + my_ulonglong row_count; + MYSQL_RES *result; + MYSQL_ROW row; + const char *delete_stmt= "DELETE FROM t1 WHERE a = ?"; + + myheader("test_mdev_34718_bd"); + + /* 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, "INSERT INTO t1 VALUES (1), (2), (3)"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TRIGGER t1_bd BEFORE DELETE ON t1 " + "FOR EACH ROW BEGIN INSERT INTO t2 (a) VALUES (OLD.a); END;"); + + /* Initialize the prepared statement and set it up for bulk operations */ + stmt_delete= mysql_stmt_init(mysql); + check_stmt(stmt_delete); + + rc= mysql_stmt_prepare(stmt_delete, delete_stmt, strlen(delete_stmt)); + check_execute(stmt_delete, rc); + + memset(&bind[0], 0, sizeof(MYSQL_BIND)); + + bind[0].buffer_type= MYSQL_TYPE_LONG; + bind[0].buffer= vals; + + /* + Input positional parameter is bound with array of 3 elements + containing actual values for the positional parameter + */ + rc= mysql_stmt_attr_set(stmt_delete, STMT_ATTR_ARRAY_SIZE, &vals_array_len); + check_execute(stmt_delete, rc); + + rc= mysql_stmt_bind_param(stmt_delete, bind); + check_execute(stmt_delete, rc); + + /* + Execution of this prepared statement deletes the rows (1), (2), (3) + from the table t1 and inserts the rows (1), (2), (3) into the table t2 + in result of firing the BEFORE DELETE trigger + */ + rc= mysql_stmt_execute(stmt_delete); + check_execute(stmt_delete, rc); + + /* + Check that the BULK DELETE statement affects exactly 3 rows + */ + row_count= mysql_stmt_affected_rows(stmt_delete); + DIE_UNLESS(row_count == 3); + + /* + * Check that the BEFORE DELETE trigger of the table t1 does work correctly + * and inserts 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], "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_delete); + + /* Clean up */ + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + myquery(rc); +} + + +/** + Test case for bulk DELETE against a table with an active AFTER DELETE + trigger. +*/ +static void test_mdev_34718_ad() +{ + int rc; + MYSQL_STMT *stmt_delete; + MYSQL_BIND bind[1]; + unsigned int vals[]= { 1, 2, 3}; + unsigned int vals_array_len= 3; + my_ulonglong row_count; + MYSQL_RES *result; + MYSQL_ROW row; + const char *delete_stmt= "DELETE FROM t1 WHERE a = ?"; + + myheader("test_mdev_34718_bd"); + + /* 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, "INSERT INTO t1 VALUES (1), (2), (3)"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TRIGGER t1_bd AFTER DELETE ON t1 " + "FOR EACH ROW BEGIN INSERT INTO t2 (a) VALUES (OLD.a); END;"); + + /* Initialize the prepared statement and set it up for bulk operations */ + stmt_delete= mysql_stmt_init(mysql); + check_stmt(stmt_delete); + + rc= mysql_stmt_prepare(stmt_delete, delete_stmt, strlen(delete_stmt)); + check_execute(stmt_delete, rc); + + memset(&bind[0], 0, sizeof(MYSQL_BIND)); + + bind[0].buffer_type= MYSQL_TYPE_LONG; + bind[0].buffer= vals; + + /* + Input positional parameter is bound with array of 3 elements + containing actual values for the positional parameter + */ + rc= mysql_stmt_attr_set(stmt_delete, STMT_ATTR_ARRAY_SIZE, &vals_array_len); + check_execute(stmt_delete, rc); + + rc= mysql_stmt_bind_param(stmt_delete, bind); + check_execute(stmt_delete, rc); + + /* + Execution of this prepared statement deletes the rows (1), (2), (3) + from the table t1 and inserts the rows (1), (2), (3) into the table t2 + in result of firing the BEFORE DELETE trigger + */ + rc= mysql_stmt_execute(stmt_delete); + check_execute(stmt_delete, rc); + + /* + Check that the BULK DELETE statement affects exactly 3 rows + */ + row_count= mysql_stmt_affected_rows(stmt_delete); + DIE_UNLESS(row_count == 3); + + /* + * Check that the AFTER DELETE trigger of the table t1 does work correctly + * and inserts 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], "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_delete); + + /* Clean up */ + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + myquery(rc); +} +#endif // EMBEDDED_LIBRARY + /* Check that server_status returned after connecting to server is consistent with the value of autocommit variable. @@ -22630,6 +23076,10 @@ static struct my_tests_st my_tests[]= { { "test_cache_metadata", test_cache_metadata}, #ifndef EMBEDDED_LIBRARY { "test_mdev_24411", test_mdev_24411}, + { "test_mdev_34718_bu", test_mdev_34718_bu }, + { "test_mdev_34718_au", test_mdev_34718_au }, + { "test_mdev_34718_bd", test_mdev_34718_bd }, + { "test_mdev_34718_ad", test_mdev_34718_ad }, #endif { "test_mdev_10075", test_mdev_10075}, { 0, 0 }