/* Copyright (c) 2022, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#include "mysql_transaction_delegate_control_imp.h"  // THIS FILE

#include "mutex_lock.h"              // MUTEX_LOCK
#include "sql/mysqld_thd_manager.h"  // Global_THD_manager
#include "sql/rpl_handler.h"         // RUN_HOOK
#include "sql/sql_class.h"           // THD_CHECK

DEFINE_METHOD(void, mysql_new_transaction_control_imp::stop, ()) {
  (void)RUN_HOOK(transaction, set_transactions_at_begin_must_fail, ());
}

DEFINE_METHOD(void, mysql_new_transaction_control_imp::allow, ()) {
  (void)RUN_HOOK(transaction, set_no_restrictions_at_transaction_begin, ());
}

class Close_connection_all_transactions_that_begin : public Do_THD_Impl {
 public:
  void operator()(THD *thd_to_close) override {
    MUTEX_LOCK(lock, &thd_to_close->LOCK_thd_data);
    THD *thd = thd_to_close;  // TX_TRACKER_GET has a embedded thd
    TX_TRACKER_GET(is_transaction_explicit);
    /**
      Super user connection is also disconnected.
      1. If THD killed flag is already set, do not over-ride it because query
         was supposed to be rollbacked, we will end up overiding the decision
         resulting in closing the connection.
      2. If THD life cycle has finished do not kill the transaction.
      3. If THD has error, do not kill the transaction, it will be rolledback.
      4. REPLICA thread should not be running but yet check it. Do not kill
         REPLICA transactions.
      5. Transaction should not be binloggable, so check we are >
         TX_RPL_STAGE_CACHE_CREATED
      6. Do not close connection of committing transaction, so check <
         TX_RPL_STAGE_BEFORE_COMMIT
      7. TX_RPL_STAGE_BEFORE_ROLLBACK is not required, transaction is being
         rolledback, no need to close connection
      8. Kill all explicit transactions which are not committing because change
         primary UDF blocks on explicit transactions
    */
    if (thd_to_close->killed == THD::NOT_KILLED &&
        !thd_to_close->slave_thread && !thd->is_being_disposed() &&
        !thd->is_error() &&
        ((thd_to_close->rpl_thd_ctx.get_tx_rpl_delegate_stage_status() >=
              Rpl_thd_context::TX_RPL_STAGE_CACHE_CREATED &&
          thd_to_close->rpl_thd_ctx.get_tx_rpl_delegate_stage_status() <
              Rpl_thd_context::TX_RPL_STAGE_BEFORE_COMMIT) ||
         (((is_transaction_explicit->get_trx_state() & TX_EXPLICIT) > 0) &&
          thd_to_close->rpl_thd_ctx.get_tx_rpl_delegate_stage_status() <
              Rpl_thd_context::TX_RPL_STAGE_BEFORE_COMMIT))) {
      thd_to_close->awake(THD::KILL_CONNECTION);
    }
  }
};

DEFINE_METHOD(
    void,
    mysql_close_connection_of_binloggable_transaction_not_reached_commit_imp::
        close,
    ()) {
  Close_connection_all_transactions_that_begin close;
  Global_THD_manager *thd_manager = Global_THD_manager::get_instance();
  /**
    Close all the client connections which is running a binloggable transaction
    that have yet not reached the before_commit stage.
  */
  thd_manager->do_for_all_thd(&close);
}

DEFINE_METHOD(void, mysql_before_commit_transaction_control_imp::stop, ()) {
  /**
    Rollback the transactions that have passed the begin hook but have yet not
    reached the before_commit stage. There can be some transactions that are yet
    not known as binloggable, so the THD::KILL_CONNECTION is not set.
    This function sets a flag to rollback the transactions.
  */
  RUN_HOOK(transaction, set_transactions_not_reached_before_commit_must_fail,
           ());
}

DEFINE_METHOD(void, mysql_before_commit_transaction_control_imp::allow, ()) {
  RUN_HOOK(transaction, set_no_restrictions_at_transactions_before_commit, ());
}
