int mysql_rm_table_no_locks()

in sql/sql_table.cc [2282:2795]


int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
                            bool drop_temporary, bool drop_view,
                            bool dont_log_query)
{
  TABLE_LIST *table;
  char path[FN_REFLEN + 1], *alias= NULL;
  uint path_length= 0;
  String wrong_tables;
  int error= 0;
  int non_temp_tables_count= 0;
  bool foreign_key_error=0;
  bool non_tmp_error= 0;
  bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0;
  bool non_tmp_table_deleted= 0;
  bool have_nonexistent_tmp_table= 0;
  bool is_drop_tmp_if_exists_with_no_defaultdb= 0;
  String built_query;
  String built_trans_tmp_query, built_non_trans_tmp_query;
  String nonexistent_tmp_tables;
  DBUG_ENTER("mysql_rm_table_no_locks");

  /*
    Prepares the drop statements that will be written into the binary
    log as follows:

    1 - If we are not processing a "DROP TEMPORARY" it prepares a
    "DROP".

    2 - A "DROP" may result in a "DROP TEMPORARY" but the opposite is
    not true.

    3 - If the current format is row, the IF EXISTS token needs to be
    appended because one does not know if CREATE TEMPORARY was previously
    written to the binary log.

    4 - Add the IF_EXISTS token if necessary, i.e. if_exists is TRUE.

    5 - For temporary tables, there is a need to differentiate tables
    in transactional and non-transactional storage engines. For that,
    reason, two types of drop statements are prepared.

    The need to different the type of tables when dropping a temporary
    table stems from the fact that such drop does not commit an ongoing
    transaction and changes to non-transactional tables must be written
    ahead of the transaction in some circumstances.

    6 - At the time of writing 'DROP TEMPORARY TABLE IF EXISTS'
    statements into the binary log if the default database specified in
    thd->db is present then they are binlogged as
    'USE `default_db`; DROP TEMPORARY TABLE IF EXISTS `t1`;'
    otherwise they will be binlogged with the actual database name to
    which the table belongs to.
    'DROP TEMPORARY TABLE IF EXISTS `actual_db`.`t1`
  */
  if (!dont_log_query)
  {
    if (!drop_temporary)
    {
      built_query.set_charset(thd->charset());
      if (if_exists)
        built_query.append("DROP TABLE IF EXISTS ");
      else
        built_query.append("DROP TABLE ");
    }

    if (thd->is_current_stmt_binlog_format_row() || if_exists)
    {
      /*
        If default database doesnot exist in those cases set
        'is_drop_tmp_if_exists_with_no_defaultdb flag to 'true' so that the
        'DROP TEMPORARY TABLE IF EXISTS' command is logged with a qualified
        table name.
      */
      if (thd->db != NULL && check_db_dir_existence(thd->db))
        is_drop_tmp_if_exists_with_no_defaultdb= true;
      built_trans_tmp_query.set_charset(thd->charset());
      built_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS ");
      built_non_trans_tmp_query.set_charset(thd->charset());
      built_non_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS ");
    }
    else
    {
      built_trans_tmp_query.set_charset(thd->charset());
      built_trans_tmp_query.append("DROP TEMPORARY TABLE ");
      built_non_trans_tmp_query.set_charset(thd->charset());
      built_non_trans_tmp_query.append("DROP TEMPORARY TABLE ");
    }
    nonexistent_tmp_tables.set_charset(thd->charset());
  }

  for (table= tables; table; table= table->next_local)
  {
    bool is_trans;
    char *db=table->db;
    int db_len= table->db_length;
    handlerton *table_type;
    enum legacy_db_type frm_db_type= DB_TYPE_UNKNOWN;

    DBUG_PRINT("table", ("table_l: '%s'.'%s'  table: 0x%lx  s: 0x%lx",
                         table->db, table->table_name, (long) table->table,
                         table->table ? (long) table->table->s : (long) -1));

    /*
      If we are in locked tables mode and are dropping a temporary table,
      the ticket should be NULL to ensure that we don't release a lock
      on a base table later.
    */
    DBUG_ASSERT(!(thd->locked_tables_mode &&
                  table->open_type != OT_BASE_ONLY &&
                  find_temporary_table(thd, table) &&
                  table->mdl_request.ticket != NULL));

    thd->add_to_binlog_accessed_dbs(table->db);

    /*
      drop_temporary_table may return one of the following error codes:
      .  0 - a temporary table was successfully dropped.
      .  1 - a temporary table was not found.
      . -1 - a temporary table is used by an outer statement.
    */
    if (table->open_type == OT_BASE_ONLY)
      error= 1;
    else if ((error= drop_temporary_table(thd, table, &is_trans)) == -1)
    {
      DBUG_ASSERT(thd->in_sub_stmt);
      goto err;
    }

    if ((drop_temporary && if_exists) || !error)
    {
      /*
        This handles the case of temporary tables. We have the following cases:

          . "DROP TEMPORARY" was executed and a temporary table was affected
          (i.e. drop_temporary && !error) or the if_exists was specified (i.e.
          drop_temporary && if_exists).

          . "DROP" was executed but a temporary table was affected (.i.e
          !error).
      */
      if (!dont_log_query)
      {
        /*
          If there is an error, we don't know the type of the engine
          at this point. So, we keep it in the nonexistent_tmp_table list.
        */
        if (error == 1)
          have_nonexistent_tmp_table= true;
        else if (is_trans)
          trans_tmp_table_deleted= TRUE;
        else
          non_trans_tmp_table_deleted= TRUE;

        String *built_ptr_query=
          (error == 1 ? &nonexistent_tmp_tables :
           (is_trans ? &built_trans_tmp_query : &built_non_trans_tmp_query));
        /*
          Write the database name if it is not the current one or if
          thd->db is NULL or 'IF EXISTS' clause is present in 'DROP TEMPORARY'
          query.
        */
        if (thd->db == NULL || strcmp(db,thd->db) != 0
            || is_drop_tmp_if_exists_with_no_defaultdb )
        {
          append_identifier(thd, built_ptr_query, db, db_len,
                            system_charset_info, thd->charset());
          built_ptr_query->append(".");
        }
        append_identifier(thd, built_ptr_query, table->table_name,
                          strlen(table->table_name), system_charset_info,
                          thd->charset());
        built_ptr_query->append(",");
      }
      /*
        This means that a temporary table was droped and as such there
        is no need to proceed with the code that tries to drop a regular
        table.
      */
      if (!error) continue;
    }
    else if (!drop_temporary)
    {
      non_temp_tables_count++;

      if (thd->locked_tables_mode)
      {
        if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN))
        {
          error= -1;
          goto err;
        }
        close_all_tables_for_name(thd, table->table->s, true, NULL);
        table->table= 0;
      }

      /* Check that we have an exclusive lock on the table to be dropped. */
      DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db,
                                                 table->table_name,
                                                 MDL_EXCLUSIVE));
      if (thd->killed)
      {
        error= -1;
        goto err;
      }
      alias= (lower_case_table_names == 2) ? table->alias : table->table_name;
      /* remove .frm file and engine files */
      path_length= build_table_filename(path, sizeof(path) - 1, db, alias,
                                        reg_ext,
                                        table->internal_tmp_table ?
                                        FN_IS_TMP : 0);

      /*
        This handles the case where a "DROP" was executed and a regular
        table "may be" dropped as drop_temporary is FALSE and error is
        TRUE. If the error was FALSE a temporary table was dropped and
        regardless of the status of drop_tempoary a "DROP TEMPORARY"
        must be used.
      */
      if (!dont_log_query)
      {
        /*
          Note that unless if_exists is TRUE or a temporary table was deleted, 
          there is no means to know if the statement should be written to the
          binary log. See further information on this variable in what follows.
        */
        non_tmp_table_deleted= (if_exists ? TRUE : non_tmp_table_deleted);
        /*
          Don't write the database name if it is the current one (or if
          thd->db is NULL).
        */
        if (thd->db == NULL || strcmp(db,thd->db) != 0)
        {
          append_identifier(thd, &built_query, db, db_len,
                            system_charset_info, thd->charset());
          built_query.append(".");
        }

        append_identifier(thd, &built_query, table->table_name,
                          strlen(table->table_name), system_charset_info,
                          thd->charset());
        built_query.append(",");
      }
    }
    DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table");
    DBUG_EXECUTE_IF("sleep_before_no_locks_delete_table",
                    my_sleep(100000););
    error= 0;
    if (drop_temporary ||
        ((access(path, F_OK) &&
          ha_create_table_from_engine(thd, db, alias)) ||
         (!drop_view &&
          dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE)))
    {
      /*
        One of the following cases happened:
          . "DROP TEMPORARY" but a temporary table was not found.
          . "DROP" but table was not found on disk and table can't be
            created from engine.
          . ./sql/datadict.cc +32 /Alfranio - TODO: We need to test this.
      */
      if (if_exists)
      {
        String tbl_name;
        tbl_name.append(String(db,system_charset_info));
        tbl_name.append('.');
        tbl_name.append(String(table->table_name,system_charset_info));

        push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
                            ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR),
                            tbl_name.c_ptr());
      }
      else
      {
        non_tmp_error = (drop_temporary ? non_tmp_error : TRUE);
        error= 1;
      }
    }
    else
    {
      char *end;
      if (frm_db_type == DB_TYPE_UNKNOWN)
      {
        dd_frm_type(thd, path, &frm_db_type);
        DBUG_PRINT("info", ("frm_db_type %d from %s", frm_db_type, path));
      }
      table_type= ha_resolve_by_legacy_type(thd, frm_db_type);
      // Remove extension for delete
      *(end= path + path_length - reg_ext_length)= '\0';
      DBUG_PRINT("info", ("deleting table of type %d",
                          (table_type ? table_type->db_type : 0)));
      error= ha_delete_table(thd, table_type, path, db, table->table_name,
                             !dont_log_query);

      /* No error if non existent table and 'IF EXIST' clause or view */
      if ((error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) && 
          (if_exists || table_type == NULL))
      {
        error= 0;
        thd->clear_error();
      }
      if (error == HA_ERR_ROW_IS_REFERENCED)
      {
        /* the table is referenced by a foreign key constraint */
        foreign_key_error= 1;
      }
      if (!error || error == ENOENT || error == HA_ERR_NO_SUCH_TABLE)
      {
        int new_error;
        /* Delete the table definition file */
        strmov(end,reg_ext);
        if (!(new_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME))))
        {
          non_tmp_table_deleted= TRUE;
          new_error= Table_triggers_list::drop_all_triggers(thd, db,
                                                            table->table_name);
        }
        error|= new_error;
      }
       non_tmp_error= error ? TRUE : non_tmp_error;
    }
    if (error)
    {
      if (error == HA_ERR_TOO_MANY_CONCURRENT_TRXS)
      {
        my_error(HA_ERR_TOO_MANY_CONCURRENT_TRXS, MYF(0));
        wrong_tables.free();
        error= 1;
        goto err;
      }

      if (wrong_tables.length())
        wrong_tables.append(',');

      wrong_tables.append(String(db,system_charset_info));
      wrong_tables.append('.');
      wrong_tables.append(String(table->table_name,system_charset_info));
    }
    DBUG_PRINT("table", ("table: 0x%lx  s: 0x%lx", (long) table->table,
                         table->table ? (long) table->table->s : (long) -1));

    DBUG_EXECUTE_IF("bug43138",
                    my_printf_error(ER_BAD_TABLE_ERROR,
                                    ER(ER_BAD_TABLE_ERROR), MYF(0),
                                    table->table_name););
#ifdef HAVE_PSI_TABLE_INTERFACE
    if (drop_temporary && likely(error == 0))
      PSI_TABLE_CALL(drop_table_share)
        (true, table->db, table->db_length, table->table_name, table->table_name_length);
#endif
  }
  DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog");
  thd->thread_specific_used|= (trans_tmp_table_deleted ||
                               non_trans_tmp_table_deleted);
  error= 0;
err:
  if (wrong_tables.length())
  {
    if (!foreign_key_error)
      my_printf_error(ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), MYF(0),
                      wrong_tables.c_ptr());
    else
      my_message(ER_ROW_IS_REFERENCED, ER(ER_ROW_IS_REFERENCED), MYF(0));
    error= 1;
  }

  if (have_nonexistent_tmp_table || non_trans_tmp_table_deleted ||
      trans_tmp_table_deleted || non_tmp_table_deleted)
  {
    query_cache_invalidate3(thd, tables, 0);

    if (have_nonexistent_tmp_table || non_trans_tmp_table_deleted ||
        trans_tmp_table_deleted)
      thd->transaction.stmt.mark_dropped_temp_table();

    /*
      The statement may contain up to three types of temporary tables:
      transactional, non-transactional, and non-existent tables.

      The statement is logged using up to two statements: non-transactional
      and transactional tables are logged in different statements.

      The non-existing tables are logged together with transactional ones, if
      any transactional tables exist or if there is only non-existing tables;
      otherwise are logged together with non-transactional ones.

      This logic ensures that:
      - On master, transactional and non-transactional tables are written to
        different statements.
      - Therefore, slave will never see statements containing both transactional
        and non-transactional tables.
      - Since non-existing temporary tables are logged together with whatever
        type of temporary tables that exist, the slave thus writes any statement
        as just one statement. I.e., the slave never splits a statement into two.
        This is crucial when GTIDs are enabled, since otherwise the statement,
        which already has a GTID, would need two different GTIDs.
    */
    if (!dont_log_query && mysql_bin_log.is_open())
    {
      if (non_trans_tmp_table_deleted)
      {
        /*
          Add the list of nonexistent tmp tables here only if there is no
          trans tmp table deleted.
        */
        if (!trans_tmp_table_deleted && have_nonexistent_tmp_table)
          built_non_trans_tmp_query.append(nonexistent_tmp_tables);
        /* Chop of the last comma */
        built_non_trans_tmp_query.chop();
        built_non_trans_tmp_query.append(" /* generated by server */");
        error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
                                   built_non_trans_tmp_query.ptr(),
                                   built_non_trans_tmp_query.length(),
                                   FALSE, FALSE,
                                   is_drop_tmp_if_exists_with_no_defaultdb,
                                   0);
        /*
          When temporary and regular tables or temporary tables with
          different storage engines are dropped on a single
          statement, the original statement is split in two or more.
          These statements are logged in distinct events to binary
          logs, when gtid_mode is ON each DDL event must have its own
          GTID. Since drop temporary table does not implicitly
          commit, in these cases we must force a commit.
        */
        if (gtid_mode > 0 && (trans_tmp_table_deleted || non_tmp_table_deleted))
          error |= mysql_bin_log.commit(thd, true, false);
      }
      if (trans_tmp_table_deleted ||
          (have_nonexistent_tmp_table && !non_trans_tmp_table_deleted))
      {
        if (have_nonexistent_tmp_table)
          built_trans_tmp_query.append(nonexistent_tmp_tables);
        /* Chop of the last comma */
        built_trans_tmp_query.chop();
        built_trans_tmp_query.append(" /* generated by server */");
        error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
                                   built_trans_tmp_query.ptr(),
                                   built_trans_tmp_query.length(),
                                   TRUE, FALSE,
                                   is_drop_tmp_if_exists_with_no_defaultdb,
                                   0);
        /*
          When temporary and regular tables are dropped on a single
          statement, the original statement is split in two or more.
          These statements are logged in distinct events to binary
          logs, when gtid_mode is ON each DDL event must have its own
          GTID. Since drop temporary table does not implicitly
          commit, in these cases we must force a commit.
        */
        if (gtid_mode > 0 && non_tmp_table_deleted)
          error |= mysql_bin_log.commit(thd, true, false);
      }
      /*
        when the DROP TABLE command is used to DROP a single table and if that
        command fails then the query cannot generate 'partial results'. In
        that case the query will not be written to the binary log.
      */
      if (non_tmp_table_deleted &&
          (thd->lex->select_lex.table_list.elements > 1 || !error))
      {
        /* Chop of the last comma */
        built_query.chop();
        built_query.append(" /* generated by server */");
        int error_code = (non_tmp_error ?
          (foreign_key_error ? ER_ROW_IS_REFERENCED : ER_BAD_TABLE_ERROR) : 0);
        error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
                                   built_query.ptr(),
                                   built_query.length(),
                                   TRUE, FALSE, FALSE,
                                   error_code);
      }
    }
  }

  if (!drop_temporary)
  {
    /*
      Under LOCK TABLES we should release meta-data locks on the tables
      which were dropped.

      Leave LOCK TABLES mode if we managed to drop all tables which were
      locked. Additional check for 'non_temp_tables_count' is to avoid
      leaving LOCK TABLES mode if we have dropped only temporary tables.
    */
    if (thd->locked_tables_mode)
    {
      if (thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0)
      {
        thd->locked_tables_list.unlock_locked_tables(thd);
        goto end;
      }
      for (table= tables; table; table= table->next_local)
      {
        /* Drop locks for all successfully dropped tables. */
        if (table->table == NULL && table->mdl_request.ticket)
        {
          /*
            Under LOCK TABLES we may have several instances of table open
            and locked and therefore have to remove several metadata lock
            requests associated with them.
          */
          thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket);
        }
      }
    }
    /*
      Rely on the caller to implicitly commit the transaction
      and release metadata locks.
    */
  }

end:
  DBUG_RETURN(error);
}