void mysql_parse(THD *thd, char *rawbuf, uint length,
                 Parser_state *parser_state,
                 bool is_com_multi,
                 bool is_next_command)
{
  DBUG_ENTER("mysql_parse");
  DBUG_EXECUTE_IF("parser_debug", turn_parser_debug_on(););

  /*
    Warning.
    The purpose of query_cache_send_result_to_client() is to lookup the
    query in the query cache first, to avoid parsing and executing it.
    So, the natural implementation would be to:
    - first, call query_cache_send_result_to_client,
    - second, if caching failed, initialise the lexical and syntactic parser.
    The problem is that the query cache depends on a clean initialization
    of (among others) lex->safe_to_cache_query and thd->server_status,
    which are reset respectively in
    - lex_start()
    - THD::reset_for_next_command()
    So, initializing the lexical analyser *before* using the query cache
    is required for the cache to work properly.
    FIXME: cleanup the dependencies in the code to simplify this.
  */
  lex_start(thd);
  thd->reset_for_next_command();
  if (is_next_command)
  {
    thd->server_status|= SERVER_MORE_RESULTS_EXISTS;
    if (is_com_multi)
      thd->get_stmt_da()->set_skip_flush();
  }

  if (query_cache_send_result_to_client(thd, rawbuf, length) <= 0)
  {
    LEX *lex= thd->lex;

    bool err= parse_sql(thd, parser_state, NULL, true);

    if (likely(!err))
    {
      thd->m_statement_psi=
        MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
                               sql_statement_info[thd->lex->sql_command].
                               m_key);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
      if (mqh_used && thd->user_connect &&
	  check_mqh(thd, lex->sql_command))
      {
	thd->net.error = 0;
      }
      else
#endif
      {
	if (likely(! thd->is_error()))
	{
          const char *found_semicolon= parser_state->m_lip.found_semicolon;
          /*
            Binlog logs a string starting from thd->query and having length
            thd->query_length; so we set thd->query_length correctly (to not
            log several statements in one event, when we executed only first).
            We set it to not see the ';' (otherwise it would get into binlog
            and Query_log_event::print() would give ';;' output).
            This also helps display only the current query in SHOW
            PROCESSLIST.
          */
          if (found_semicolon && (ulong) (found_semicolon - thd->query()))
            thd->set_query(thd->query(),
                           (uint32) (found_semicolon - thd->query() - 1),
                           thd->charset());
          /* Actually execute the query */
          if (found_semicolon)
          {
            lex->safe_to_cache_query= 0;
            thd->server_status|= SERVER_MORE_RESULTS_EXISTS;
          }
          lex->set_trg_event_type_for_tables();
          MYSQL_QUERY_EXEC_START(thd->query(),
                                 thd->thread_id,
                                 thd->get_db(),
                                 &thd->security_ctx->priv_user[0],
                                 (char *) thd->security_ctx->host_or_ip,
                                 0);

          int error __attribute__((unused));
          error= mysql_execute_command(thd);
          MYSQL_QUERY_EXEC_DONE(error);
	}
      }
    }
    else
    {
      /* Instrument this broken statement as "statement/sql/error" */
      thd->m_statement_psi=
        MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
                               sql_statement_info[SQLCOM_END].m_key);
      DBUG_ASSERT(thd->is_error());
      DBUG_PRINT("info",("Command aborted. Fatal_error: %d",
			 thd->is_fatal_error));

      query_cache_abort(thd, &thd->query_cache_tls);
    }
    THD_STAGE_INFO(thd, stage_freeing_items);
    sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size);
    sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size);
    sp_cache_enforce_limit(thd->sp_package_spec_cache, stored_program_cache_size);
    sp_cache_enforce_limit(thd->sp_package_body_cache, stored_program_cache_size);
    thd->end_statement();
    thd->Item_change_list::rollback_item_tree_changes();
    thd->cleanup_after_query();
  }
  else
  {
    /* Update statistics for getting the query from the cache */
    thd->lex->sql_command= SQLCOM_SELECT;
    thd->m_statement_psi=
      MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
                             sql_statement_info[SQLCOM_SELECT].m_key);
    status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]);
    thd->update_stats();
#ifdef WITH_WSREP
    if (WSREP_CLIENT(thd))
    {
      thd->wsrep_sync_wait_gtid= WSREP_GTID_UNDEFINED;
    }
#endif /* WITH_WSREP */
  }
  DBUG_VOID_RETURN;
}