# ==== Purpose ====
#
# A few special SQL statements/constructs can generate multiple
# transactions in the binary log.  This poses interesting problems
# especially when it comes to generating and preserving GTIDs.
#
# There are four cases to consider:
#
# - When GTID_MODE=ON/ON_PERMISSIVE and GTID_NEXT=AUTOMATIC, a
#   separate GTID should be generated for each of the statements.
#
# - When GTID_MODE=ON and GTID_NEXT=UUID:NUMBER, an error should be
#   generated since it is impossible to log the statement using just
#   the given GTID.  The exact point when error can be generated
#   (before starting to execute, after executing the first transaction
#   of the statement, or after executing all transactions of the
#   statement) depends on the statement.
#
# - When GTID_MODE=OFF/OFF_PERMISSIVE and GTID_NEXT=AUTOMATIC, an
#   Anonymous_gtid_log_event should be generated for each of the
#   statements.
#
# - When GTID_MODE=OFF and GTID_NEXT=ANONYMOUS, an
#   Anonymous_gtid_log_event should be generated for each of the
#   statements.  Moreover, anonymous ownership should not be released
#   until the last transaction generated by the statement is written
#   to the binary log.
#
# The following statements can generate multiple transactions in the
# binary log:
#
#  1. CALL: when a stored procedure executes multiple statements in
#     autocommit mode, each statement will be logged as a separate
#     transaction.
#
#  2. DROP TABLE: when a single statement drops multiple tables, then
#     there will be a separate statement for all dropped non-temporary
#     tables, a separate statement for all dropped transactional
#     temporary tables, and a separate statement for all dropped
#     non-transactional tables.
#
#  3. DROP DATABASE: this statement will first drop all non-temporary
#     tables in the database, and then try to remove the database
#     directory.  If removing the directory fails (e.g., because there
#     is an unrelated file in the directory), then the statement will
#     be logged as a single DROP TABLE statement listing all tables.
#     If there are many tables, the statement becomes big.  To avoid
#     too big statements, the statement is split into multiple DROP
#     TABLE statements, each with length at most 1024 bytes.
#
#     If GTID_NEXT='UUID:NUMBER', the statement will not be logged and
#     the gtid will not be added to GTID_EXECUTED, if the statement
#     fails. This is regardless of whether it would result in multiple
#     DROP statements or just one.
#
#  4. CREATE TABLE ... SELECT: this statement contains both DDL and
#     DML.  When binlog_format=ROW, all DML must be logged in row
#     format, but at the same time DDL can only be logged in statement
#     format.  Therefore this statement is logged as a CREATE TABLE
#     followed by row events.  This statement is not allowed when
#     GTID_MODE = ON.
#
# This test verifies that all these statements work, for all values of
# GTID_MODE/GTID_NEXT, and both on a client connection and in a slave
# connection.
#
# ==== Implementation ====
#
#  1. CALL. We execute a multi-transaction CALL:
#
#     - On master with GTID_MODE='AUTOMATIC';
#     - On master with GTID_MODE!='AUTOMATIC' ('ANONYMOUS'/'UUID:NUMBER',
#       depending on GTID_MODE)
#     - Not on slave. Since CALL does not get logged as CALL, there is
#       no way to get it in the binary log.
#
#  2. DROP TABLE. We drop all combinations of two or three tables,
#     from the different categories non-temporary, temporary
#     transactional, and temporary non-transactional.  This is done in
#     three ways:
#
#     - On master with GTID_MODE='AUTOMATIC'
#     - On master with GTID_MODE!='AUTOMATIC' ('ANONYMOUS'/'UUID:NUMBER',
#       depending on GTID_MODE)
#     - On slave. This can be done using different table definitions
#       on master and slave. On master we use only non-temporary
#       tables, so that any DROP TABLE statement will succeed and make
#       it to the binary log.  In order to make the tables temporary
#       on slave, we first create the non-temporary table on master,
#       then sync it to slave, then drop it on slave, and then use a
#       stored procedure on master that creates a temporary table with
#       the same name *only* when it executes on the slave.
#
#  3. DROP DATABASE. We try five different error scenarios:
#
#     3.1. Database exists and is empty.
#     3.2. Database exists and contains one table.
#     3.3. Database exists and contains many tables with long names, so
#          that the statement gets split into multiple transactions in
#          the binary log.
#     3.4. Database has not been created, but the directory exists.
#     3.5. Database has not created and the directory does not exist.
#
#     We run these five scenarios in three ways:
#
#     - On master with GTID_MODE='AUTOMATIC'
#     - On master with GTID_MODE!='AUTOMATIC' ('ANONYMOUS'/'UUID:NUMBER',
#       depending on GTID_MODE).
#     - On slave.
#
#     In scenarios 3.1-3.4, we create an extra file in the database
#     directory to trigger the error: to trigger the error on master
#     we create the file in master's database directory; to trigger on
#     slave we create it in slave's database directory. In scenario
#     3.5 we create the directory on master
#
#  4. CREATE TABLE ... SELECT.  This is tested in a different test
#     (rpl_gtid_create_select.test), since the statement cannot be
#     executed if gtid_mode=on.
#
# ==== Reference ====
#
# WL#7592: GTIDs: Generate Gtid_log_event and Previous_gtids_log_event always
# - Test was introduced by this worklog.

# It suffices to test one binlog_format.
--source include/have_binlog_format_statement.inc

--let $rpl_gtid_utils= 1
--source include/master-slave.inc

CALL mtr.add_suppression('Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT.');
if ($gtid_mode_on)
{
  CALL mtr.add_suppression('Cannot execute statement because it needs to be written to the binary log as multiple statements');
  CALL mtr.add_suppression('DROP DATABASE failed; some tables may have been dropped but the database directory remains.');
}
--connection slave
CALL mtr.add_suppression("Error dropping database");
CALL mtr.add_suppression("Can't drop database '.*'; database doesn't exist");
CALL mtr.add_suppression("Slave SQL for channel '': ... The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state. .* Error_code: 1756");
--connection master

--let $gtid_step_gtid_mode_agnostic= 1

--echo ==== Case 1: CALL is split on master ====

# Note: CALL cannot be executed on a slave, since CALL statements are
# not written as CALL in the binary log.

--echo ---- Initialize ----

CREATE TABLE t (a INT);
--delimiter |
CREATE PROCEDURE proc ()
BEGIN
  INSERT INTO t VALUES (1);
  INSERT INTO t VALUES (2);
END|
--delimiter ;

--echo ---- GTID_NEXT=AUTOMATIC ----

--source include/gtid_step_reset.inc

CALL proc();

--let $gtid_step_count= 2
--source include/gtid_step_assert.inc

--let $assert_cond= COUNT(*) = 2 FROM t
--let $assert_text= Both rows were inserted
--source include/assert.inc

DELETE FROM t;

--echo ---- GTID_NEXT=non-automatic ----

--source include/gtid_step_reset.inc

--source include/set_gtid_next_gtid_mode_agnostic.inc

if ($gtid_mode_on)
{
  --replace_result $server_1_uuid MASTER_UUID
  --error ER_GTID_NEXT_TYPE_UNDEFINED_GROUP
  CALL proc();
}
if (!$gtid_mode_on)
{
  CALL proc();
}
SET GTID_NEXT= 'AUTOMATIC';

--let $gtid_step_count= 1
--source include/gtid_step_assert.inc

if ($gtid_mode_on)
{
  --let $assert_cond= COUNT(*) = 1 FROM t
}
if (!$gtid_mode_on)
{
  --let $assert_cond= COUNT(*) = 2 FROM t
}
--let $assert_text= One row inserted if GTID_MODE=ON, two if GTID_MODE=OFF
--source include/assert.inc

DROP TABLE t;
DROP PROCEDURE proc;

--echo ==== Case 2A: DROP TABLE is split on master ====

--echo ---- Initialize ----

--delimiter |
CREATE PROCEDURE create_tables()
BEGIN
  CREATE TABLE base (a INT) ENGINE = InnoDB;
  CREATE TEMPORARY TABLE temp_t (a INT) ENGINE = InnoDB;
  CREATE TEMPORARY TABLE temp_n (a INT) ENGINE = MyISAM;
END|
CREATE PROCEDURE drop_tables()
BEGIN
  DROP TABLE IF EXISTS base;
  DROP TABLE IF EXISTS temp_t;
  DROP TABLE IF EXISTS temp_n;
END|
--delimiter ;

--source include/rpl_sync.inc

--echo ---- GTID_MODE=AUTOMATIC ----

--let $automatic= 1
--source extra/rpl_tests/rpl_drop_multiple_tables_in_multiple_ways.inc

--echo ---- GTID_MODE=non-automatic ----

--let $automatic= 0
--source extra/rpl_tests/rpl_drop_multiple_tables_in_multiple_ways.inc

--echo ==== Case 2B: DROP TABLE is split on slave ====

--echo ---- Initialize ----

CREATE TABLE dummy (a INT);
DROP PROCEDURE create_tables;

--delimiter |
CREATE FUNCTION create_tables_func() RETURNS INT
BEGIN
  IF @@GLOBAL.SERVER_ID = 2 THEN
    CREATE TEMPORARY TABLE temp_t (a INT) ENGINE = InnoDB;
    CREATE TEMPORARY TABLE temp_n (a INT) ENGINE = MyISAM;
  END IF;
  RETURN 0;
END|
CREATE PROCEDURE create_tables()
BEGIN
  CREATE TABLE base (a INT);
  SET @@SESSION.SQL_LOG_BIN = 0;
  CREATE TABLE temp_t (a INT);
  CREATE TABLE temp_n (a INT);
  SET @@SESSION.SQL_LOG_BIN = 1;
  INSERT INTO dummy VALUES (create_tables_func());
END|
--delimiter ;

--source include/rpl_sync.inc

--echo ---- GTID_MODE=AUTOMATIC ----

--let $automatic= 1
if ($gtid_mode_on)
{
  --let $expect_slave_error= 1
}
--let $transaction_count= 1
--source extra/rpl_tests/rpl_drop_multiple_tables_in_multiple_ways.inc

--echo ---- Clean up ----

DROP FUNCTION create_tables_func;
DROP PROCEDURE create_tables;
DROP PROCEDURE drop_tables;
DROP TABLE dummy;

--echo ==== Case 3: DROP DATABASE ====

--echo ---- Initialize ----

--echo # db1, db2, db3: no tables.
CREATE DATABASE db1;
CREATE DATABASE db2;
CREATE DATABASE db3;
--source include/rpl_sync.inc
--write_file $server_1_datadir/db1/file.txt
EOF
--write_file $server_1_datadir/db2/file.txt
EOF
--write_file $server_2_datadir/db3/file.txt
EOF

--echo # db4, db5, db6: one table.
CREATE DATABASE db4;
CREATE DATABASE db5;
CREATE DATABASE db6;
CREATE TABLE db4.t1 (a INT);
CREATE TABLE db5.t1 (a INT);
CREATE TABLE db6.t1 (a INT);
--source include/rpl_sync.inc
--write_file $server_1_datadir/db4/file.txt
EOF
--write_file $server_1_datadir/db5/file.txt
EOF
--write_file $server_2_datadir/db6/file.txt
EOF

--echo # db7, db8, db9: many tables with long names.
CREATE DATABASE db7;
CREATE DATABASE db8;
CREATE DATABASE db9;
let $i= 0;
--let $long_text= `SELECT REPEAT('a', 60)`
--disable_query_log
while ($i < 20)
{
  # This is very slow for some reason, so do three tables in parallel.
  eval CREATE TABLE db7.$long_text$i (a INT);
  eval CREATE TABLE db8.$long_text$i (a INT);
  eval CREATE TABLE db9.$long_text$i (a INT);
  --inc $i
}
--enable_query_log
--source include/rpl_sync.inc
--write_file $server_1_datadir/db7/file.txt
EOF
--write_file $server_1_datadir/db8/file.txt
EOF
--write_file $server_2_datadir/db9/file.txt
EOF

--echo # db10, db11, db12: not a database, but the directory exists.
--mkdir $server_1_datadir/db10
--mkdir $server_1_datadir/db11
--mkdir $server_1_datadir/db12
--mkdir $server_2_datadir/db12
--write_file $server_1_datadir/db10/file.txt
EOF
--write_file $server_1_datadir/db11/file.txt
EOF
--write_file $server_2_datadir/db12/file.txt
EOF

--echo # db13, db14, db15: not a database. db15 is a database on master.
--mkdir $server_1_datadir/db15

--source include/rpl_sync.inc

--echo ---- DROP DATABASE is split on master; GTID_NEXT=AUTOMATIC ----

--echo # db1: no table.
--source include/save_binlog_position.inc
SET GTID_NEXT = 'AUTOMATIC';

--replace_result \\ /
--error ER_DB_DROP_RMDIR
DROP DATABASE db1;

--let $event_sequence= ()
--source include/assert_binlog_events.inc

--echo # db4: one table.
--source include/save_binlog_position.inc
SET GTID_NEXT = 'AUTOMATIC';

--replace_result \\ /
--error ER_DB_DROP_RMDIR
DROP DATABASE db4;

if ($gtid_mode_on)
{
  --let $event_sequence= Gtid # !Q(DROP TABLE.*)
}
if (!$gtid_mode_on)
{
  --let $event_sequence= Anonymous_Gtid # !Q(DROP TABLE.*)
}
--source include/assert_binlog_events.inc

--echo # db7: many tables with long names.
--source include/save_binlog_position.inc
SET GTID_NEXT = 'AUTOMATIC';

--replace_result \\ /
--error ER_DB_DROP_RMDIR
DROP DATABASE db7;

if ($gtid_mode_on)
{
  --let $event_sequence= Gtid # !Q(DROP TABLE.*) # Gtid # !Q(DROP TABLE.*)
}
if (!$gtid_mode_on)
{
  --let $event_sequence= Anonymous_Gtid # !Q(DROP TABLE.*) # Anonymous_Gtid # !Q(DROP TABLE.*)
}
--source include/assert_binlog_events.inc

--echo # db10: not a database, but directory exists.
--source include/save_binlog_position.inc
SET GTID_NEXT = 'AUTOMATIC';

--replace_result \\ /
--error ER_DB_DROP_RMDIR
DROP DATABASE db10;

--let $event_sequence= ()
--source include/assert_binlog_events.inc

--echo # db13: not a database.
--source include/save_binlog_position.inc
SET GTID_NEXT = 'AUTOMATIC';

--replace_result \\ /
--error ER_DB_DROP_EXISTS
DROP DATABASE db13;

--let $event_sequence= ()
--source include/assert_binlog_events.inc

--echo ---- DROP DATABASE is split on master; GTID_NEXT=non-automatic ----

--echo # db2: no table.
--source include/save_binlog_position.inc
--source include/set_gtid_next_gtid_mode_agnostic.inc
if (!$gtid_mode_on)
{
  --replace_result \\ /
  --error ER_DB_DROP_RMDIR
  DROP DATABASE db2;

  --let $event_sequence= ()
  --source include/assert_binlog_events.inc
}
if ($gtid_mode_on)
{
  --replace_result $server_1_uuid MASTER_UUID \\ /
  --error ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID
  DROP DATABASE db2;

  --let $event_sequence= ()
  --source include/assert_binlog_events.inc
}
SET GTID_NEXT = 'AUTOMATIC';

--echo # db5: one table.
--source include/save_binlog_position.inc
--source include/set_gtid_next_gtid_mode_agnostic.inc
if (!$gtid_mode_on)
{
  --replace_result \\ /
  --error ER_DB_DROP_RMDIR
  DROP DATABASE db5;

  --let $event_sequence= Anonymous_Gtid # !Q(DROP TABLE.*)
  --source include/assert_binlog_events.inc
}
if ($gtid_mode_on)
{
  --replace_result $server_1_uuid MASTER_UUID \\ /
  --error ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID
  DROP DATABASE db5;

  --let $event_sequence= ()
  --source include/assert_binlog_events.inc
}
SET GTID_NEXT = 'AUTOMATIC';

--echo # db8: many tables with long names.
--source include/save_binlog_position.inc
--source include/set_gtid_next_gtid_mode_agnostic.inc
if (!$gtid_mode_on)
{
  --replace_result \\ /
  --error ER_DB_DROP_RMDIR
  DROP DATABASE db8;

  --let $event_sequence= Anonymous_Gtid # !Q(DROP TABLE.*) # Anonymous_Gtid # !Q(DROP TABLE.*)
  --source include/assert_binlog_events.inc
}
if ($gtid_mode_on)
{
  --replace_result $server_1_uuid MASTER_UUID \\ /
  --error ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID
  DROP DATABASE db8;

  --let $event_sequence= ()
  --source include/assert_binlog_events.inc
}
SET GTID_NEXT = 'AUTOMATIC';

--echo # db11: not a database, but directory exists.
--source include/save_binlog_position.inc
--source include/set_gtid_next_gtid_mode_agnostic.inc
if (!$gtid_mode_on)
{
  --replace_result \\ /
  --error ER_DB_DROP_RMDIR
  DROP DATABASE db11;
}
if ($gtid_mode_on)
{
  --replace_result $server_1_uuid MASTER_UUID \\ /
  --error ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID
  DROP DATABASE db11;
}
--let $event_sequence= ()
--source include/assert_binlog_events.inc
SET GTID_NEXT = 'AUTOMATIC';

--echo # db14: not a database.
--source include/save_binlog_position.inc
--source include/set_gtid_next_gtid_mode_agnostic.inc
 --error ER_DB_DROP_EXISTS
DROP DATABASE db14;
--let $event_sequence= ()
--source include/assert_binlog_events.inc
SET GTID_NEXT = 'AUTOMATIC';

--echo ---- DROP DATABASE is split on slave ----

SET GTID_NEXT = 'AUTOMATIC';

if (!$gtid_mode_on)
{
  --let $slave_sql_errno= convert_error(ER_DB_DROP_RMDIR)
}
if ($gtid_mode_on)
{
  --let $slave_sql_errno= convert_error(ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID)
}

--echo # db3: no table.
DROP DATABASE db3;
--source include/sync_slave_io_with_master.inc
--source include/wait_for_slave_sql_error.inc
--source include/rpl_skip_to_end_of_relay_log.inc
--let $rpl_connection_name= master
--source include/rpl_connection.inc

--echo # db6: one table.
DROP DATABASE db6;
--source include/sync_slave_io_with_master.inc
--source include/wait_for_slave_sql_error.inc
--source include/rpl_skip_to_end_of_relay_log.inc
--let $rpl_connection_name= master
--source include/rpl_connection.inc

--echo # db9: many tables with long names.
DROP DATABASE db9;
--source include/sync_slave_io_with_master.inc
--source include/wait_for_slave_sql_error.inc
--source include/rpl_skip_to_end_of_relay_log.inc
--let $rpl_connection_name= master
--source include/rpl_connection.inc

--echo # db12: not a database, but directory exists.
DROP DATABASE db12;
--source include/sync_slave_io_with_master.inc
--source include/wait_for_slave_sql_error.inc
--source include/rpl_skip_to_end_of_relay_log.inc
--let $rpl_connection_name= master
--source include/rpl_connection.inc

--echo # db15: not a database (on slave).
--let $slave_sql_errno= convert_error(ER_DB_DROP_EXISTS)
DROP DATABASE db15;
--source include/sync_slave_io_with_master.inc
--source include/wait_for_slave_sql_error.inc
--source include/rpl_skip_to_end_of_relay_log.inc
--let $rpl_connection_name= master
--source include/rpl_connection.inc

--echo ---- Clean up ----

--remove_file $server_1_datadir/db1/file.txt
--remove_file $server_1_datadir/db2/file.txt
--remove_file $server_2_datadir/db3/file.txt
--remove_file $server_1_datadir/db4/file.txt
--remove_file $server_1_datadir/db5/file.txt
--remove_file $server_2_datadir/db6/file.txt
--remove_file $server_1_datadir/db7/file.txt
--remove_file $server_1_datadir/db8/file.txt
--remove_file $server_2_datadir/db9/file.txt
--remove_file $server_1_datadir/db10/file.txt
--remove_file $server_1_datadir/db11/file.txt
--remove_file $server_2_datadir/db12/file.txt

DROP DATABASE db1;
DROP DATABASE db2;
DROP DATABASE IF EXISTS db3;
DROP DATABASE db4;
DROP DATABASE db5;
DROP DATABASE IF EXISTS db6;
DROP DATABASE db7;
DROP DATABASE db8;
DROP DATABASE IF EXISTS db9;
DROP DATABASE IF EXISTS db10;
DROP DATABASE IF EXISTS db11;
DROP DATABASE IF EXISTS db12;
DROP DATABASE IF EXISTS db15;

--source include/rpl_sync.inc

--echo ==== Case 4: CREATE TABLE ... SELECT ====

--echo See rpl_gtid_create_select.test

--source include/rpl_end.inc
