bool dispatch_command()

in sql/sql_parse.cc [1314:2021]


bool dispatch_command(THD *thd, const COM_DATA *com_data,
                      enum enum_server_command command)
{
  bool error= 0;
  Global_THD_manager *thd_manager= Global_THD_manager::get_instance();
  DBUG_ENTER("dispatch_command");
  DBUG_PRINT("info", ("command: %d", command));

  /* SHOW PROFILE instrumentation, begin */
#if defined(ENABLED_PROFILING)
  thd->profiling.start_new_query();
#endif

  /* Performance Schema Interface instrumentation, begin */
  thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
                                               com_statement_info[command].m_key);

  thd->set_command(command);
  /*
    Commands which always take a long time are logged into
    the slow log only if opt_log_slow_admin_statements is set.
  */
  thd->enable_slow_log= TRUE;
  thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */
  thd->set_time();
  if (IS_TIME_T_VALID_FOR_TIMESTAMP(thd->query_start_in_secs()) == false)
  {
    /*
      If the time has gone past 2038 we need to shutdown the server. But
      there is possibility of getting invalid time value on some platforms.
      For example, gettimeofday() might return incorrect value on solaris
      platform. Hence validating the current time with 5 iterations before
      initiating the normal server shutdown process because of time getting
      past 2038.
    */
    const int max_tries= 5;
    LogErr(WARNING_LEVEL, ER_CONFIRMING_THE_FUTURE, max_tries);

    int tries= 0;
    while (++tries <= max_tries)
    {
      thd->set_time();
      if (IS_TIME_T_VALID_FOR_TIMESTAMP(thd->query_start_in_secs()) == true)
      {
        LogErr(WARNING_LEVEL, ER_BACK_IN_TIME, tries);
        break;
      }
      LogErr(WARNING_LEVEL, ER_FUTURE_DATE, tries);
    }
    if (tries > max_tries)
    {
      /*
        If the time has got past 2038 we need to shut this server down
        We do this by making sure every command is a shutdown and we
        have enough privileges to shut the server down

        TODO: remove this when we have full 64 bit my_time_t support
      */
      LogErr(ERROR_LEVEL, ER_UNSUPPORTED_DATE);
      ulong master_access= thd->security_context()->master_access();
      thd->security_context()->set_master_access(master_access | SHUTDOWN_ACL);
      error= TRUE;
      kill_mysql();
    }
  }
  thd->set_query_id(next_query_id());
  thd->rewritten_query.mem_free();
  thd_manager->inc_thread_running();

  if (!(server_command_flags[command] & CF_SKIP_QUESTIONS))
    thd->status_var.questions++;

  /**
    Clear the set of flags that are expected to be cleared at the
    beginning of each command.
  */
  thd->server_status&= ~SERVER_STATUS_CLEAR_SET;

  if (thd->get_protocol()->type() == Protocol::PROTOCOL_PLUGIN &&
      !(server_command_flags[command] & CF_ALLOW_PROTOCOL_PLUGIN))
  {
    my_error(ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED, MYF(0));
    thd->killed= THD::KILL_CONNECTION;
    error= true;
    goto done;
  }

  /**
    Enforce password expiration for all RPC commands, except the
    following:

    COM_QUERY/COM_STMT_PREPARE and COM_STMT_EXECUTE do a more
    fine-grained check later.
    COM_STMT_CLOSE and COM_STMT_SEND_LONG_DATA don't return anything.
    COM_PING only discloses information that the server is running,
       and that's available through other means.
    COM_QUIT should work even for expired statements.
  */
  if (unlikely(thd->security_context()->password_expired() &&
               command != COM_QUERY &&
               command != COM_STMT_CLOSE &&
               command != COM_STMT_SEND_LONG_DATA &&
               command != COM_PING &&
               command != COM_QUIT &&
               command != COM_STMT_PREPARE &&
               command != COM_STMT_EXECUTE))
  {
    my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
    goto done;
  }

  if (mysql_audit_notify(thd,
                         AUDIT_EVENT(MYSQL_AUDIT_COMMAND_START),
                         command, command_name[command].str))
  {
    goto done;
  }

  switch (command) {
  case COM_INIT_DB:
  {
    LEX_STRING tmp;
    thd->status_var.com_stat[SQLCOM_CHANGE_DB]++;
    thd->convert_string(&tmp, system_charset_info,
                        com_data->com_init_db.db_name,
                        com_data->com_init_db.length, thd->charset());

    LEX_CSTRING tmp_cstr= {tmp.str, tmp.length};
    if (!mysql_change_db(thd, tmp_cstr, FALSE))
    {
      query_logger.general_log_write(thd, command,
                                     thd->db().str, thd->db().length);
      my_ok(thd);
    }
    break;
  }
  case COM_REGISTER_SLAVE:
  {
    // TODO: access of protocol_classic should be removed
    if (!register_slave(thd,
      thd->get_protocol_classic()->get_raw_packet(),
      thd->get_protocol_classic()->get_packet_length()))
      my_ok(thd);
    break;
  }
  case COM_RESET_CONNECTION:
  {
    thd->status_var.com_other++;
    thd->cleanup_connection();
    my_ok(thd);
    break;
  }
  case COM_CHANGE_USER:
  {
    int auth_rc;
    thd->status_var.com_other++;

    thd->cleanup_connection();
    USER_CONN *save_user_connect=
      const_cast<USER_CONN*>(thd->get_user_connect());
    LEX_CSTRING save_db= thd->db();
    Security_context save_security_ctx(*(thd->security_context()));

    auth_rc= acl_authenticate(thd, COM_CHANGE_USER);
    auth_rc|= mysql_audit_notify(thd,
                             AUDIT_EVENT(MYSQL_AUDIT_CONNECTION_CHANGE_USER));
    if (auth_rc)
    {
      *thd->security_context()= save_security_ctx;
      thd->set_user_connect(save_user_connect);
      thd->reset_db(save_db);

      my_error(ER_ACCESS_DENIED_CHANGE_USER_ERROR, MYF(0),
               thd->security_context()->user().str,
               thd->security_context()->host_or_ip().str,
               (thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)));
      thd->killed= THD::KILL_CONNECTION;
      error=true;
    }
    else
    {
      /* we've authenticated new user */
      PSI_THREAD_CALL(notify_session_change_user)(thd->get_psi());

      if (save_user_connect)
        decrease_user_connections(save_user_connect);
      mysql_mutex_lock(&thd->LOCK_thd_data);
      my_free(const_cast<char*>(save_db.str));
      save_db= NULL_CSTR;
      mysql_mutex_unlock(&thd->LOCK_thd_data);
    }
    break;
  }
  case COM_STMT_EXECUTE:
  {
    /* Clear possible warnings from the previous command */
    thd->reset_for_next_command();

    Prepared_statement *stmt= nullptr;
    if (!mysql_stmt_precheck(thd, com_data, command, &stmt))
    {
      PS_PARAM *parameters= com_data->com_stmt_execute.parameters;
      mysqld_stmt_execute(thd, stmt, com_data->com_stmt_execute.has_new_types,
                          com_data->com_stmt_execute.open_cursor, parameters);

      DBUG_EXECUTE_IF("parser_stmt_to_error_log", {
        LogErr(INFORMATION_LEVEL, ER_PARSER_TRACE, thd->query().str);
      });
    }
    break;
  }
  case COM_STMT_FETCH:
  {
    /* Clear possible warnings from the previous command */
    thd->reset_for_next_command();

    Prepared_statement *stmt= nullptr;
    if (!mysql_stmt_precheck(thd, com_data, command, &stmt))
      mysqld_stmt_fetch(thd, stmt, com_data->com_stmt_fetch.num_rows);

    break;
  }
  case COM_STMT_SEND_LONG_DATA:
  {
    Prepared_statement *stmt;
    thd->get_stmt_da()->disable_status();
    if (!mysql_stmt_precheck(thd, com_data, command, &stmt))
      mysql_stmt_get_longdata(thd, stmt,
                              com_data->com_stmt_send_long_data.param_number,
                              com_data->com_stmt_send_long_data.longdata,
                              com_data->com_stmt_send_long_data.length);
    break;
  }
  case COM_STMT_PREPARE:
  {
    /* Clear possible warnings from the previous command */
    thd->reset_for_next_command();
    Prepared_statement *stmt= nullptr;
    if (!mysql_stmt_precheck(thd, com_data, command, &stmt))
      mysqld_stmt_prepare(thd, com_data->com_stmt_prepare.query,
                          com_data->com_stmt_prepare.length, stmt);
    break;
  }
  case COM_STMT_CLOSE:
  {
    Prepared_statement *stmt= nullptr;
    thd->get_stmt_da()->disable_status();
    if (!mysql_stmt_precheck(thd, com_data, command, &stmt))
      mysqld_stmt_close(thd, stmt);
    break;
  }
  case COM_STMT_RESET:
  {
    /* Clear possible warnings from the previous command */
    thd->reset_for_next_command();

    Prepared_statement *stmt= nullptr;
    if (!mysql_stmt_precheck(thd, com_data, command, &stmt))
      mysqld_stmt_reset(thd, stmt);
    break;
  }
  case COM_QUERY:
  {
    DBUG_ASSERT(thd->m_digest == NULL);
    thd->m_digest= & thd->m_digest_state;
    thd->m_digest->reset(thd->m_token_array, max_digest_length);

    if (alloc_query(thd, com_data->com_query.query,
                    com_data->com_query.length))
      break;					// fatal error is set

    const char *packet_end= thd->query().str + thd->query().length;

    if (opt_general_log_raw)
      query_logger.general_log_write(thd, command, thd->query().str,
                                     thd->query().length);

    DBUG_PRINT("query",("%-.4096s", thd->query().str));

#if defined(ENABLED_PROFILING)
    thd->profiling.set_query_source(thd->query().str, thd->query().length);
#endif

    MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query().str,
                             thd->query().length);

    Parser_state parser_state;
    if (parser_state.init(thd, thd->query().str, thd->query().length))
      break;

    mysql_parse(thd, &parser_state);

    DBUG_EXECUTE_IF("parser_stmt_to_error_log", {
        LogErr(INFORMATION_LEVEL, ER_PARSER_TRACE, thd->query().str);
      });

    while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) &&
           ! thd->is_error())
    {
      /*
        Multiple queries exits, execute them individually
      */
      const char *beginning_of_next_stmt= parser_state.m_lip.found_semicolon;

      /* Finalize server status flags after executing a statement. */
      thd->update_slow_query_status();
      thd->send_statement_status();
      query_cache.end_of_result(thd);

      mysql_audit_notify(thd, AUDIT_EVENT(MYSQL_AUDIT_GENERAL_STATUS),
                         thd->get_stmt_da()->is_error() ?
                         thd->get_stmt_da()->mysql_errno() : 0,
                         command_name[command].str,
                         command_name[command].length);

      size_t length= static_cast<size_t>(packet_end - beginning_of_next_stmt);

      log_slow_statement(thd);

      /* Remove garbage at start of query */
      while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt))
      {
        beginning_of_next_stmt++;
        length--;
      }

/* PSI end */
      MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
      thd->m_statement_psi= NULL;
      thd->m_digest= NULL;

/* SHOW PROFILE end */
#if defined(ENABLED_PROFILING)
      thd->profiling.finish_current_query();
#endif

/* SHOW PROFILE begin */
#if defined(ENABLED_PROFILING)
      thd->profiling.start_new_query("continuing");
      thd->profiling.set_query_source(beginning_of_next_stmt, length);
#endif

/* PSI begin */
      thd->m_digest= & thd->m_digest_state;
      thd->m_digest->reset(thd->m_token_array, max_digest_length);

      thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
                                          com_statement_info[command].m_key,
                                          thd->db().str, thd->db().length,
                                          thd->charset(), NULL);
      THD_STAGE_INFO(thd, stage_starting);
      MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, beginning_of_next_stmt, length);

      thd->set_query(beginning_of_next_stmt, length);
      thd->set_query_id(next_query_id());
      /*
        Count each statement from the client.
      */
      thd->status_var.questions++;
      thd->set_time(); /* Reset the query start time. */
      parser_state.reset(beginning_of_next_stmt, length);
      /* TODO: set thd->lex->sql_command to SQLCOM_END here */
      mysql_parse(thd, &parser_state);
    }

    /* Need to set error to true for graceful shutdown */
    if((thd->lex->sql_command == SQLCOM_SHUTDOWN) && (thd->get_stmt_da()->is_ok()))
      error= TRUE;

    DBUG_PRINT("info",("query ready"));
    break;
  }
  case COM_FIELD_LIST:				// This isn't actually needed
  {
    char *fields;
    /* Locked closure of all tables */
    TABLE_LIST table_list;
    LEX_STRING table_name;
    LEX_STRING db;
    push_deprecated_warn(thd, "COM_FIELD_LIST",
                         "SHOW COLUMNS FROM statement");
    /*
      SHOW statements should not add the used tables to the list of tables
      used in a transaction.
    */
    MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();

    thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]++;
    if (thd->copy_db_to(&db.str, &db.length))
      break;
    thd->convert_string(&table_name, system_charset_info,
                        (char *) com_data->com_field_list.table_name,
                        com_data->com_field_list.table_name_length,
                        thd->charset());
    Ident_name_check ident_check_status=
      check_table_name(table_name.str, table_name.length);
    if (ident_check_status == Ident_name_check::WRONG)
    {
      /* this is OK due to convert_string() null-terminating the string */
      my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
      break;
    }
    else if (ident_check_status == Ident_name_check::TOO_LONG)
    {
      my_error(ER_TOO_LONG_IDENT, MYF(0), table_name.str);
      break;
    }
    mysql_reset_thd_for_next_command(thd);
    lex_start(thd);
    /* Must be before we init the table list. */
    if (lower_case_table_names && !is_infoschema_db(db.str, db.length))
      table_name.length= my_casedn_str(files_charset_info, table_name.str);
    table_list.init_one_table(db.str, db.length, table_name.str,
                              table_name.length, table_name.str, TL_READ);
    /*
      Init TABLE_LIST members necessary when the undelrying
      table is view.
    */
    table_list.select_lex= thd->lex->select_lex;
    thd->lex->
      select_lex->table_list.link_in_list(&table_list,
                                         &table_list.next_local);
    thd->lex->add_to_query_tables(&table_list);

    if (is_infoschema_db(table_list.db, table_list.db_length))
    {
      ST_SCHEMA_TABLE *schema_table= find_schema_table(thd, table_list.alias);
      if (schema_table)
        table_list.schema_table= schema_table;
    }

    if (!(fields=
        (char *) thd->memdup(com_data->com_field_list.query,
                             com_data->com_field_list.query_length)))
      break;
    // Don't count end \0
    thd->set_query(fields, com_data->com_field_list.query_length - 1);
    query_logger.general_log_print(thd, command, "%s %s",
                                   table_list.table_name, fields);

    if (open_temporary_tables(thd, &table_list))
      break;

    if (check_table_access(thd, SELECT_ACL, &table_list,
                           TRUE, UINT_MAX, FALSE))
      break;

    // See comment in opt_trace_disable_if_no_security_context_access()
    Opt_trace_start ots(thd, &table_list, thd->lex->sql_command, NULL,
                        NULL, 0, NULL, NULL);

    mysqld_list_fields(thd,&table_list,fields);

    thd->lex->unit->cleanup(true);
    /* No need to rollback statement transaction, it's not started. */
    DBUG_ASSERT(thd->get_transaction()->is_empty(Transaction_ctx::STMT));
    close_thread_tables(thd);
    thd->mdl_context.rollback_to_savepoint(mdl_savepoint);

    if (thd->transaction_rollback_request)
    {
      /*
        Transaction rollback was requested since MDL deadlock was
        discovered while trying to open tables. Rollback transaction
        in all storage engines including binary log and release all
        locks.
      */
      trans_rollback_implicit(thd);
      thd->mdl_context.release_transactional_locks();
    }

    thd->cleanup_after_query();
    break;
  }
  case COM_QUIT:
    /* We don't calculate statistics for this command */
    query_logger.general_log_print(thd, command, NullS);
    // Don't give 'abort' message
    // TODO: access of protocol_classic should be removed
    if (thd->is_classic_protocol())
      thd->get_protocol_classic()->get_net()->error= 0;
    thd->get_stmt_da()->disable_status();       // Don't send anything back
    error=TRUE;					// End server
    break;
  case COM_BINLOG_DUMP_GTID:
    // TODO: access of protocol_classic should be removed
    error=
      com_binlog_dump_gtid(thd,
        (char *)thd->get_protocol_classic()->get_raw_packet(),
        thd->get_protocol_classic()->get_packet_length());
    break;
  case COM_BINLOG_DUMP:
    // TODO: access of protocol_classic should be removed
    error=
      com_binlog_dump(thd,
        (char*)thd->get_protocol_classic()->get_raw_packet(),
        thd->get_protocol_classic()->get_packet_length());
    break;
  case COM_REFRESH:
  {
    int not_used;
    push_deprecated_warn(thd, "COM_REFRESH", "FLUSH statement");
    /*
      Initialize thd->lex since it's used in many base functions, such as
      open_tables(). Otherwise, it remains uninitialized and may cause crash
      during execution of COM_REFRESH.
    */
    lex_start(thd);
    
    thd->status_var.com_stat[SQLCOM_FLUSH]++;
    ulong options= (ulong) com_data->com_refresh.options;
    if (trans_commit_implicit(thd))
      break;
    thd->mdl_context.release_transactional_locks();
    if (check_global_access(thd,RELOAD_ACL))
      break;
    query_logger.general_log_print(thd, command, NullS);
#ifndef DBUG_OFF
    bool debug_simulate= FALSE;
    DBUG_EXECUTE_IF("simulate_detached_thread_refresh", debug_simulate= TRUE;);
    if (debug_simulate)
    {
      /*
        Simulate a reload without a attached thread session.
        Provides a environment similar to that of when the
        server receives a SIGHUP signal and reloads caches
        and flushes tables.
      */
      bool res;
      current_thd= nullptr;
      res= reload_acl_and_cache(NULL, options | REFRESH_FAST,
                                NULL, &not_used);
      current_thd= thd;
      if (res)
        break;
    }
    else
#endif
    if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, &not_used))
      break;
    if (trans_commit_implicit(thd))
      break;
    close_thread_tables(thd);
    thd->mdl_context.release_transactional_locks();
    my_ok(thd);
    break;
  }
  case COM_STATISTICS:
  {
    System_status_var current_global_status_var;
    ulong uptime;
    size_t length MY_ATTRIBUTE((unused));
    ulonglong queries_per_second1000;
    char buff[250];
    size_t buff_len= sizeof(buff);

    query_logger.general_log_print(thd, command, NullS);
    thd->status_var.com_stat[SQLCOM_SHOW_STATUS]++;
    mysql_mutex_lock(&LOCK_status);
    calc_sum_of_all_status(&current_global_status_var);
    mysql_mutex_unlock(&LOCK_status);
    if (!(uptime= (ulong) (thd->query_start_in_secs() - server_start_time)))
      queries_per_second1000= 0;
    else
      queries_per_second1000= thd->query_id * 1000LL / uptime;

    length= my_snprintf(buff, buff_len - 1,
                        "Uptime: %lu  Threads: %d  Questions: %lu  "
                        "Slow queries: %llu  Opens: %llu  Flush tables: %lu  "
                        "Open tables: %u  Queries per second avg: %u.%03u",
                        uptime,
                        (int) thd_manager->get_thd_count(), (ulong) thd->query_id,
                        current_global_status_var.long_query_count,
                        current_global_status_var.opened_tables,
                        refresh_version,
                        table_cache_manager.cached_tables(),
                        (uint) (queries_per_second1000 / 1000),
                        (uint) (queries_per_second1000 % 1000));
    // TODO: access of protocol_classic should be removed.
    // should be rewritten using store functions
    thd->get_protocol_classic()->write((uchar*) buff, length);
    thd->get_protocol()->flush();
    thd->get_stmt_da()->disable_status();
    break;
  }
  case COM_PING:
    thd->status_var.com_other++;
    my_ok(thd);				// Tell client we are alive
    break;
  case COM_PROCESS_INFO:
    thd->status_var.com_stat[SQLCOM_SHOW_PROCESSLIST]++;
    push_deprecated_warn(thd, "COM_PROCESS_INFO",
                         "SHOW PROCESSLIST statement");
    if (!thd->security_context()->priv_user().str[0] &&
        check_global_access(thd, PROCESS_ACL))
      break;
    query_logger.general_log_print(thd, command, NullS);
    mysqld_list_processes(
      thd,
      thd->security_context()->check_access(PROCESS_ACL) ?
      NullS : thd->security_context()->priv_user().str, 0);
    break;
  case COM_PROCESS_KILL:
  {
    push_deprecated_warn(thd, "COM_PROCESS_KILL",
                         "KILL CONNECTION/QUERY statement");
    if (thd_manager->get_thread_id() & (~0xfffffffful))
      my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "thread_id", "mysql_kill()");
    else
    {
    thd->status_var.com_stat[SQLCOM_KILL]++;
      sql_kill(thd, com_data->com_kill.id, false);
    }
    break;
  }
  case COM_SET_OPTION:
  {
    thd->status_var.com_stat[SQLCOM_SET_OPTION]++;

    switch (com_data->com_set_option.opt_command) {
    case (int) MYSQL_OPTION_MULTI_STATEMENTS_ON:
      //TODO: access of protocol_classic should be removed
      thd->get_protocol_classic()->add_client_capability(
          CLIENT_MULTI_STATEMENTS);
      my_eof(thd);
      break;
    case (int) MYSQL_OPTION_MULTI_STATEMENTS_OFF:
      thd->get_protocol_classic()->remove_client_capability(
        CLIENT_MULTI_STATEMENTS);
      my_eof(thd);
      break;
    default:
      my_error(ER_UNKNOWN_COM_ERROR, MYF(0));
      break;
    }
    break;
  }
  case COM_DEBUG:
    thd->status_var.com_other++;
    if (check_global_access(thd, SUPER_ACL))
      break;					/* purecov: inspected */
    mysql_print_status();
    query_logger.general_log_print(thd, command, NullS);
    my_eof(thd);
    break;
  case COM_SLEEP:
  case COM_CONNECT:				// Impossible here
  case COM_TIME:				// Impossible from client
  case COM_DELAYED_INSERT: // INSERT DELAYED has been removed.
  case COM_END:
  default:
    my_error(ER_UNKNOWN_COM_ERROR, MYF(0));
    break;
  }

done:
  DBUG_ASSERT(thd->open_tables == NULL ||
              (thd->locked_tables_mode == LTM_LOCK_TABLES));

  /* Finalize server status flags after executing a command. */
  thd->update_slow_query_status();
  if (thd->killed)
    thd->send_kill_message();
  thd->send_statement_status();
  thd->rpl_thd_ctx.session_gtids_ctx().notify_after_response_packet(thd);
  query_cache.end_of_result(thd);

  if (!thd->is_error() && !thd->killed)
    mysql_audit_notify(thd,
                       AUDIT_EVENT(MYSQL_AUDIT_GENERAL_RESULT), 0, NULL, 0);

  mysql_audit_notify(thd, AUDIT_EVENT(MYSQL_AUDIT_GENERAL_STATUS),
                     thd->get_stmt_da()->is_error() ?
                     thd->get_stmt_da()->mysql_errno() : 0,
                     command_name[command].str,
                     command_name[command].length);

  /* command_end is informational only. The plugin cannot abort
     execution of the command at thie point. */
  mysql_audit_notify(thd, AUDIT_EVENT(MYSQL_AUDIT_COMMAND_END), command,
                     command_name[command].str);

  log_slow_statement(thd);

  THD_STAGE_INFO(thd, stage_cleaning_up);

  thd->reset_query();
  thd->set_command(COM_SLEEP);
  thd->proc_info= 0;

  /* Performance Schema Interface instrumentation, end */
  MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
  thd->m_statement_psi= NULL;
  thd->m_digest= NULL;

  thd_manager->dec_thread_running();

  /* Freeing the memroot will leave the THD::work_part_info invalid. */
  thd->work_part_info= nullptr;
  free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));

  /* SHOW PROFILE instrumentation, end */
#if defined(ENABLED_PROFILING)
  thd->profiling.finish_current_query();
#endif

  DBUG_RETURN(error);
}