/* Copyright (c) 2022, 2024, 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 designed to work 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 either included with
the program or referenced in the documentation.

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_server_event_tracking_bridge_imp.h"

#include "sql/command_mapping.h"
#include "sql/current_thd.h"
#include "sql/mysqld.h" /* sql_statement_names */
#include "sql/sql_audit.h"
#include "sql/sql_class.h"
#include "sql/sql_lex.h"
#include "sql/sql_plugin.h"
#include "sql/sql_rewrite.h"

#include "my_sys.h"
#include "mysql/components/services/log_builtins.h" /* LogErr */
#include "mysql/plugin_audit.h"

#include <memory>

/**
  @file sql/server_component/mysql_server_event_tracking_bridge_imp.cc
  A bridge implementation that translates event tracking APIs to
  audit plugin APIs.
*/

// clang-format off

/**
  @page page_event_tracking_services Event Tracking Services

  @section section_introduction Introduction

  Event tracking services are a group of services that allow implementor
  to monitor various database activities. Two main actors involved are:

  - Producers: Those who emit one or more events
  - Consumers: Those who are interesting in processing events
               generated by consumers

  Following services are part of this groups of services.
  - Events related to user authentication (@ref s_mysql_event_tracking_authentication)
  - Events related to COM command execution (@ref s_mysql_event_tracking_command)
  - Events related to a session connection (@ref s_mysql_event_tracking_connection)
  - Events related to execution status (@ref s_mysql_event_tracking_general)
  - Events related to global variable access (@ref s_mysql_event_tracking_global_variable)
  - Events related to program lifecycle (@ref s_mysql_event_tracking_lifecycle)
  - Events to emit special message to log (@ref s_mysql_event_tracking_message)
  - Events to perform query rewrite (@ref s_mysql_event_tracking_parse)
  - Events to track query execution (@ref s_mysql_event_tracking_query)
  - Events to track stored program execution (@ref s_mysql_event_tracking_stored_program)
  - Events to track table access (@ref s_mysql_event_tracking_table_access)

  When a consumer component is installed, it registeres its interest in receiving
  notification for any subset of events by implementing corresponding services.

  A producer, as a part of generating an event will inform all consumers who
  have registered interest of a given event.

  Through some of the above mentioned events, a consumer can indicate producer to
  either generate an error/warning or stop the execution or both.

  server component, which is part of MySQL server is one example of producer component.
  At different point of execution, it emits events of all classes of events mentioned above.

  Please take a look at following pages for more details:

  - @subpage page_event_tracking_event_details
  - @subpage page_event_tracking_new_consumer
  - @subpage page_event_tracking_new_producer
*/

/**
  @page page_event_tracking_event_details Details of existing event tracking services

  @section section_event_tracking_user_management Events related to user management

  User management events are produced by server component as part of various
  user management DDL.
  @sa @ref s_mysql_event_tracking_authentication

  - EVENT_TRACKING_AUTHENTICATION_FLUSH: Emitted as a part of FLUSH PRIVILEGES
  - EVENT_TRACKING_AUTHENTICATION_AUTHID_CREATE: Emitted as a part of user creation through CREATE USER
  - EVENT_TRACKING_AUTHENTICATION_CREDENTIAL_CHANGE: Emitted as a part of credential change through
                                                     ALTER USER or SET PASSWORD
  - EVENT_TRACKING_AUTHENTICATION_AUTHID_RENAME: Emitted as a part of RENAME USER
  - EVENT_TRACKING_AUTHENTICATION_AUTHID_DROP: Emitted as a part of DROP USER

  Refer to @ref mysql_event_tracking_authentication_data to know more about data passed to consumer.

  In order to provide additional information for above events, server component implements
  event_tracking_authentication_information (@ref s_mysql_event_tracking_authentication_information) service that a consumer
  component can use.


  @section section_event_tracking_command Events related to COM command execution

  Commands events are executed whenever COM_* commands are executed.
  @sa @ref s_mysql_event_tracking_command

  - EVENT_TRACKING_COMMAND_START: Emitted whenever server component starts processing of COM_* command
  - EVENT_TRACKING_COMMAND_END: Emitted whenever server component is done processing a given COM_* command

  Refer to @ref mysql_event_tracking_command_data to know more about data passed to consumer.

  @section section_event_tracking_connection Events related to a session connection

  Connection events are generated at various stages of connection lifecycle.
  @sa @ref s_mysql_event_tracking_connection

  - EVENT_TRACKING_CONNECTION_PRE_AUTHENTICATE: Emitted whenever a new connection request from a client is received.
  - EVENT_TRACKING_CONNECTION_CONNECT: Emitted once authentication is completed for a new incoming connection
  - EVENT_TRACKING_CONNECTION_CHANGE_USER: Emitted when server receives CHANGE USER request on an existing connection
  - EVENT_TRACKING_CONNECTION_DISCONNECT: Emitted when an establish session is diconnecting.

  Refer to @ref mysql_event_tracking_connection_data to know more about data passed to consumer.


  @section section_event_tracking_general Events related to execution status

  Execution status events occur at various stages of SQL execution.
  @sa @ref s_mysql_event_tracking_general

  - EVENT_TRACKING_GENERAL_LOG: Emitted whenever an SQL statement is logged into server's general log
  - EVENT_TRACKING_GENERAL_ERROR: Emitted whenever an error is raised
  - EVENT_TRACKING_GENERAL_RESULT: Emitted once result is transmitted to user
  - EVENT_TRACKING_GENERAL_STATUS: Emitted once query execution is complete

  Refer to @ref mysql_event_tracking_general_data to know more about data passed to consumer.

  In order to provide additional information for above events, server component implements
  event_tracking_general_information (@ref s_mysql_event_tracking_general_information) service that a consumer
  component can use.


  @section section_event_tracking_global_variable Events related to global variable access

  Global variable events occur whenever a global system variable is queried or is set.
  @sa @ref s_mysql_event_tracking_global_variable

  - EVENT_TRACKING_GLOBAL_VARIABLE_GET: Emitted whenever a global system variable value is queried.
  - EVENT_TRACKING_GLOBAL_VARIABLE_SET: Emitted whenever a global system variable value is set.

  Refer to @ref mysql_event_tracking_global_variable_data to know more about data passed to consumer.


  @section section_event_tracking_lifecycle Events related to program lifecycle

  Lifecycle events occur at program start-up/shutdown.
  @sa @ref s_mysql_event_tracking_lifecycle

  - EVENT_TRACKING_STARTUP_STARTUP: Emitted after program startup
  - EVENT_TRACKING_SHUTDOWN_SHUTDOWN: Emitted before program shutdown

  Refer to @ref mysql_event_tracking_startup_data and @ref mysql_event_tracking_shutdown_data to know
  more about data passed to cunsumer.


  @section section_event_tracking_message Events to emit special message to log

  Message events are special events to inform a consumer about certain messages that producer want
  to deliver. A usecase of such a message is to add special markers in e.g. audit log files.
  @sa @ref s_mysql_event_tracking_message

  - EVENT_TRACKING_MESSAGE_INTERNAL: Denotes system generated messages
  - EVENT_TRACKING_MESSAGE_USER: Denotes user generated messages

  Refer to @ref mysql_event_tracking_message_data to know more about data passed to consumer.


  @section section_event_tracking_parse Events to perform query rewrite or intercept queries

  These events allow consumer to either abort an operation or rewrite a query to suite the need.
  @sa @ref s_mysql_event_tracking_parse

  - EVENT_TRACKING_PARSE_PREPARSE: Emitted right after query is received but before execution begins
  - EVENT_TRACKING_PARSE_POSTPARSE: Emitted after parsing phase of the query

  Refer to @ref mysql_event_tracking_message_data to know more about data passed to consumer.


  @section section_event_tracking_query Events to track query execution

  These events occur at the beginning and end of query execution.
  @sa @ref s_mysql_event_tracking_query

  - EVENT_TRACKING_QUERY_START: Emitted at the start of top level query execution
  - EVENT_TRACKING_QUERY_STATUS_END: Emitted at the end of top level query execution
  - EVENT_TRACKING_QUERY_NESTED_START: Emitted at the start of subquery execution
  - EVENT_TRACKING_QUERY_NESTED_STATUS_END: Emitted at the end of subquery execution

  Refer to @ref mysql_event_tracking_query_data to know more about data passed to consumer.


  @section section_event_tracking_stored_program Events to track stored program execution

  These events occur whenever a stored program is executed.
  @sa @ref s_mysql_event_tracking_stored_program

  - EVENT_TRACKING_STORED_PROGRAM_EXECUTE: Emitted whenever a stored program is executed.

  Refer to @ref mysql_event_tracking_stored_program_data to know more about data passed to consumer.


  @section section_event_tracking_table_access Events to track table access

  These events track different types of table access.
  @sa @ref s_mysql_event_tracking_table_access

  - EVENT_TRACKING_TABLE_ACCESS_READ: Emitted whenever a table is read
  - EVENT_TRACKING_TABLE_ACCESS_INSERT: Emitted whenever data is inserted in a table
  - EVENT_TRACKING_TABLE_ACCESS_UPDATE: Emitted whenever data is updated in a table
  - EVENT_TRACKING_TABLE_ACCESS_DELETE: Emitted whenever data is deleted from a table

  Refer to @ref mysql_event_tracking_table_access_data to know more about data passed to consumer.
*/

/**
  @page page_event_tracking_new_consumer How to create a new consumer component

  A consumer of events essentially need to implement a subset of event tracking services.
  Once such a consumer is installed, producer will use service APIs to notify it about
  given set of events that the consumer is interested in.

  In order to simplify writing the core part of the functionality, we have helper headers
  with skeleton implementation of each class of events.

  @sa @ref EVENT_TRACKING_AUTHENTICATION_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_COMMAND_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_CONNECTION_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_GENERAL_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_GLOBAL_VARIABLE_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_LIFECYCLE_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_MESSAGE_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_PARSE_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_QUERY_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_STORED_PROGRAM_CONSUMER_EXAMPLE
  @sa @ref EVENT_TRACKING_TABLE_ACCESS_CONSUMER_EXAMPLE

  Following is an example of a consumer that implements various
  event tracking services, increments simple counters for each
  event and then displays them through status variables.

  @code
  #include "mysql/components/services/component_status_var_service.h"
  #include "mysql/components/util/event_tracking_authentication_consumer_helper.h"
  #include "mysql/components/util/event_tracking_command_consumer_helper.h"
  #include "mysql/components/util/event_tracking_connection_consumer_helper.h"
  #include "mysql/components/util/event_tracking_general_consumer_helper.h"
  #include "mysql/components/util/event_tracking_global_variable_consumer_helper.h"
  #include "mysql/components/util/event_tracking_lifecycle_consumer_helper.h"
  #include "mysql/components/util/event_tracking_message_consumer_helper.h"
  #include "mysql/components/util/event_tracking_parse_consumer_helper.h"
  #include "mysql/components/util/event_tracking_query_consumer_helper.h"
  #include "mysql/components/util/event_tracking_stored_program_consumer_helper.h"
  #include "mysql/components/util/event_tracking_table_access_consumer_helper.h"

  REQUIRES_SERVICE_PLACEHOLDER_AS(status_variable_registration,
                                mysql_status_var_service);

  namespace Event_tracking_implementation {

  enum class Event_types {
    AUTHENTICATION = 0,
    COMMAND,
    CONNECTION,
    GENERAL,
    GLOBAL_VARIABLE,
    MESSAGE,
    PARSE,
    QUERY,
    SHUTDOWN,
    STARTUP,
    STORED_PROGRAM,
    TABLE_ACCESS,
    LAST
  };

  // For the sake of simplicity, locking is ignored
  // Ideally one should use atomic variables and use
  // status variable of type SHOW_FUNC to fetch and
  // display the value

  unsigned long g_counters[static_cast<unsigned int>(Event_types::LAST)] = {0};

  static void increment_counter(Event_types event_type) {
    if (event_type < Event_types::LAST) {
      ++g_counters[static_cast<unsigned int>(event_type)];
    }
  }

    // Status variables
  static SHOW_VAR status_vars[] = {
    {"component_event_tracking_example.counter_event_tracking_authentication",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::AUTHENTICATION)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_command",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::COMMAND)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_connection",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::CONNECTION)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_general",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::GENERAL)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_global_"
     "variable",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::GLOBAL_VARIABLES)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_message",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::MESSAGE)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_parse",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::PARSE)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_query",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::QUERY)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_"
     "shutdown",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::SHUTDOWN)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_"
     "startup",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::STARTUP)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_tracking_stored_"
     "program",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::STORED_PROGRAM)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    {"component_event_tracking_example.counter_event_track_table_"
     "access",
     (char
          *)&g_counters[static_cast<unsigned int>(Event_types::TABLE_ACCESS)],
     SHOW_LONG, SHOW_SCOPE_GLOBAL},
    // null terminator required
    {nullptr, nullptr, SHOW_UNDEF, SHOW_SCOPE_UNDEF}};

  // START: Event tracking implementation

  mysql_event_tracking_authentication_subclass_t
      Event_tracking_authentication_implementation::filtered_sub_events = 0;
  bool Event_tracking_authentication_implementation::callback(
      const mysql_event_tracking_authentication_data *data [[maybe_unused]]) {
    increment_counter(Event_types::AUTHENTICATION);
    return false;
  }

  mysql_event_tracking_command_subclass_t
      Event_tracking_command_implementation::filtered_sub_events = 0;
  bool Event_tracking_command_implementation::callback(
      const mysql_event_tracking_command_data *data [[maybe_unused]]) {
    increment_counter(Event_types::COMMAND);
    return false;
  }

  mysql_event_tracking_connection_subclass_t
      Event_tracking_connection_implementation::filtered_sub_events = 0;
  bool Event_tracking_connection_implementation::callback(
      const mysql_event_tracking_connection_data *data [[maybe_unused]]) {
    increment_counter(Event_types::CONNECTION);
    return false;
  }

  mysql_event_tracking_general_subclass_t
      Event_tracking_general_implementation::filtered_sub_events = 0;
  bool Event_tracking_general_implementation::callback(
      const mysql_event_tracking_general_data *data [[maybe_unused]]) {
    increment_counter(Event_types::GENERAL);
    return false;
  }

  mysql_event_tracking_global_variable_subclass_t
      Event_tracking_global_variable_implementation::filtered_sub_events = 0;
  bool Event_tracking_global_variable_implementation::callback(
      const mysql_event_tracking_global_variable_data *data [[maybe_unused]]) {
    increment_counter(Event_types::GLOBAL_VARIABLE);
    return false;
  }

  mysql_event_tracking_startup_subclass_t
      Event_tracking_lifecycle_implementation::startup_filtered_sub_events = 0;
  bool Event_tracking_lifecycle_implementation::callback(
      const mysql_event_tracking_startup_data *data [[maybe_unused]]) {
    increment_counter(Event_types::STARTUP);
    return false;
  }

  mysql_event_tracking_shutdown_subclass_t
      Event_tracking_lifecycle_implementation::shutdown_filtered_sub_events = 0;
  bool Event_tracking_lifecycle_implementation::callback(
      const mysql_event_tracking_shutdown_data *data [[maybe_unused]]) {
    increment_counter(Event_types::SHUTDOWN);
    return false;
  }

  mysql_event_tracking_message_subclass_t
      Event_tracking_message_implementation::filtered_sub_events = 0;

  bool Event_tracking_message_implementation::callback(
      const mysql_event_tracking_message_data *data [[maybe_unused]]) {
    increment_counter(Event_types::MESSAGE);
    return false;
  }

  mysql_event_tracking_parse_subclass_t
      Event_tracking_parse_implementation::filtered_sub_events = 0;
  bool Event_tracking_parse_implementation::callback(
      mysql_event_tracking_parse_data *data [[maybe_unused]]) {
    increment_counter(Event_types::PARSE);
    return false;
  }

  mysql_event_tracking_query_subclass_t
      Event_tracking_query_implementation::filtered_sub_events = 0;
  bool Event_tracking_query_implementation::callback(
      const mysql_event_tracking_query_data *data [[maybe_unused]]) {
    increment_counter(Event_types::QUERY);
    return false;
  }

  mysql_event_tracking_stored_program_subclass_t
      Event_tracking_stored_program_implementation::filtered_sub_events = 0;
  bool Event_tracking_stored_program_implementation::callback(
      const mysql_event_tracking_stored_program_data *data [[maybe_unused]]) {
    increment_counter(Event_types::STORED_PROGRAM);
    return false;
  }

  mysql_event_tracking_table_access_subclass_t
      Event_tracking_table_access_implementation::filtered_sub_events = 0;
  bool Event_tracking_table_access_implementation::callback(
      const mysql_event_tracking_table_access_data *data [[maybe_unused]]) {
    increment_counter(Event_types::TABLE_ACCESS);
    return false;
  }

  // STOP: Event tracking implementation

    static mysql_service_status_t init() {

    if ((variables_registered =
           mysql_status_var_service->register_variable(status_vars))) {
      return 1;
    }

    return 0;
  }

  static mysql_service_status_t deinit() {

    if (mysql_status_var_service->unregister_variable(status_vars)) return 1;
    return 0;
  }

  }  // namespace Event_tracking_implementation

  // =======================================================================

  // Component declaration related stuff

  // This component provides implementation of following component services
  IMPLEMENTS_SERVICE_EVENT_TRACKING_AUTHENTICATION(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_COMMAND(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_CONNECTION(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_GENERAL(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_GLOBAL_VARIABLE(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_LIFECYCLE(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_MESSAGE(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_PARSE(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_QUERY(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_STORED_PROGRAM(
      component_event_tracking_example);
  IMPLEMENTS_SERVICE_EVENT_TRACKING_TABLE_ACCESS(
      component_event_tracking_example);

  // This component provides following services
  BEGIN_COMPONENT_PROVIDES(component_event_tracking_example)
  PROVIDES_SERVICE_EVENT_TRACKING_AUTHENTICATION(
      component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_COMMAND(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_CONNECTION(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_GENERAL(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_GLOBAL_VARIABLE(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_LIFECYCLE(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_MESSAGE(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_PARSE(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_QUERY(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_STORED_PROGRAM(
          component_event_tracking_example),
      PROVIDES_SERVICE_EVENT_TRACKING_TABLE_ACCESS(
          component_event_tracking_example),
      END_COMPONENT_PROVIDES();

  // List of dependencies
  BEGIN_COMPONENT_REQUIRES(component_event_tracking_example)
  REQUIRES_SERVICE_AS(status_variable_registration, mysql_status_var_service),
    END_COMPONENT_REQUIRES();

  // Component description
  BEGIN_COMPONENT_METADATA(component_event_tracking_example)
  METADATA("mysql.author", "Oracle Corporation"),
      METADATA("mysql.license", "GPL"),
      METADATA("component_event_tracking_example", "1"),
      END_COMPONENT_METADATA();

  // Component declaration
  DECLARE_COMPONENT(component_event_tracking_example,
                    "component_event_tracking_example")
  Event_tracking_consumer_a::init,
      Event_tracking_consumer_a::deinit END_DECLARE_COMPONENT();

  // Component contained in this library
  DECLARE_LIBRARY_COMPONENTS &COMPONENT_REF(
      component_event_tracking_example) END_DECLARE_LIBRARY_COMPONENTS

  @endcode
*/

/**
  @page page_event_tracking_new_producer How to create a new producer component


  A new producer component would typically mean new set of events being introduced.
  In such a case, new services should be introduced - just like existing set of
  event tracking services. E.g. something like following:

  @code
  BEGIN_SERVICE_DEFINITION(event_tracking_<new_service_name>)

  DECLARE_BOOL_METHOD(notify,
                    (const mysql_<new_service_name>_data *data));

  END_SERVICE_DEFINITION(event_tracking_<new_service_name>)
  @endcode

  Once introduced, producer should be able to broadcast the event to all interested
  consumer. To achieve that, a consumer should do following:

  - Define a new reference caching change using @ref s_mysql_reference_caching_channel
  - Create a reference caching cache to obtain references to all interested component
  - Iterate over the reference cache and notify each consumer component

  Further, a consumer component may be uninstalled at any time. To facilitate that, the
  reference cache created by the producer should release corresponding reference.
  This can be achieved by implementing @ref s_mysql_dynamic_loader_services_unload_notification
  and refreshing reference caches as a part of it.
  Reference caching component will take care of informing the producer whenever a component
  is being uinstalled.

  In addition, developer may also think about writing wrappers to facilitate development
  of consumer component. For example see @ref EVENT_TRACKING_GENERAL_CONSUMER_EXAMPLE.
*/

// clang-format on

/** Macro to perform LEX_CSTRING transformation */
#define TO_LEXCSTRING(x) \
  { x.str, x.length }

using event_tracking_authentication_t =
    SERVICE_TYPE_NO_CONST(event_tracking_authentication);
using event_tracking_command_t = SERVICE_TYPE_NO_CONST(event_tracking_command);
using event_tracking_connection_t =
    SERVICE_TYPE_NO_CONST(event_tracking_connection);
using event_tracking_general_t = SERVICE_TYPE_NO_CONST(event_tracking_general);
using event_tracking_global_variable_t =
    SERVICE_TYPE_NO_CONST(event_tracking_global_variable);
using event_tracking_lifecycle_t =
    SERVICE_TYPE_NO_CONST(event_tracking_lifecycle);
using event_tracking_message_t = SERVICE_TYPE_NO_CONST(event_tracking_message);
using event_tracking_parse_t = SERVICE_TYPE_NO_CONST(event_tracking_parse);
using event_tracking_query_t = SERVICE_TYPE_NO_CONST(event_tracking_query);
using event_tracking_stored_program_t =
    SERVICE_TYPE_NO_CONST(event_tracking_stored_program);
using event_tracking_table_access_t =
    SERVICE_TYPE_NO_CONST(event_tracking_table_access);

SERVICE_TYPE(event_tracking_authentication) *srv_event_tracking_authentication =
    nullptr;
SERVICE_TYPE(event_tracking_command) *srv_event_tracking_command = nullptr;
SERVICE_TYPE(event_tracking_connection) *srv_event_tracking_connection =
    nullptr;
SERVICE_TYPE(event_tracking_general) *srv_event_tracking_general = nullptr;
SERVICE_TYPE(event_tracking_global_variable)
*srv_event_tracking_global_variable = nullptr;
SERVICE_TYPE(event_tracking_lifecycle) *srv_event_tracking_lifecycle = nullptr;
SERVICE_TYPE(event_tracking_message) *srv_event_tracking_message = nullptr;
SERVICE_TYPE(event_tracking_parse) *srv_event_tracking_parse = nullptr;
SERVICE_TYPE(event_tracking_query) *srv_event_tracking_query = nullptr;
SERVICE_TYPE(event_tracking_stored_program) *srv_event_tracking_stored_program =
    nullptr;
SERVICE_TYPE(event_tracking_table_access) *srv_event_tracking_table_access =
    nullptr;

static bool inited = false;
void init_srv_event_tracking_handles() {
  srv_registry->acquire("event_tracking_authentication.mysql_server",
                        reinterpret_cast<my_h_service *>(
                            const_cast<event_tracking_authentication_t **>(
                                &srv_event_tracking_authentication)));
  srv_registry->acquire(
      "event_tracking_command.mysql_server",
      reinterpret_cast<my_h_service *>(const_cast<event_tracking_command_t **>(
          &srv_event_tracking_command)));
  srv_registry->acquire("event_tracking_connection.mysql_server",
                        reinterpret_cast<my_h_service *>(
                            const_cast<event_tracking_connection_t **>(
                                &srv_event_tracking_connection)));
  srv_registry->acquire(
      "event_tracking_general.mysql_server",
      reinterpret_cast<my_h_service *>(const_cast<event_tracking_general_t **>(
          &srv_event_tracking_general)));
  srv_registry->acquire("event_tracking_global_variable.mysql_server",
                        reinterpret_cast<my_h_service *>(
                            const_cast<event_tracking_global_variable_t **>(
                                &srv_event_tracking_global_variable)));
  srv_registry->acquire("event_tracking_lifecycle.mysql_server",
                        reinterpret_cast<my_h_service *>(
                            const_cast<event_tracking_lifecycle_t **>(
                                &srv_event_tracking_lifecycle)));
  srv_registry->acquire(
      "event_tracking_message.mysql_server",
      reinterpret_cast<my_h_service *>(const_cast<event_tracking_message_t **>(
          &srv_event_tracking_message)));
  srv_registry->acquire(
      "event_tracking_parse.mysql_server",
      reinterpret_cast<my_h_service *>(
          const_cast<event_tracking_parse_t **>(&srv_event_tracking_parse)));
  srv_registry->acquire(
      "event_tracking_query.mysql_server",
      reinterpret_cast<my_h_service *>(
          const_cast<event_tracking_query_t **>(&srv_event_tracking_query)));
  srv_registry->acquire("event_tracking_stored_program.mysql_server",
                        reinterpret_cast<my_h_service *>(
                            const_cast<event_tracking_stored_program_t **>(
                                &srv_event_tracking_stored_program)));
  srv_registry->acquire("event_tracking_table_access.mysql_server",
                        reinterpret_cast<my_h_service *>(
                            const_cast<event_tracking_table_access_t **>(
                                &srv_event_tracking_table_access)));

  assert(srv_event_tracking_authentication != nullptr &&
         srv_event_tracking_command != nullptr &&
         srv_event_tracking_connection != nullptr &&
         srv_event_tracking_general != nullptr &&
         srv_event_tracking_global_variable != nullptr &&
         srv_event_tracking_lifecycle != nullptr &&
         srv_event_tracking_message != nullptr &&
         srv_event_tracking_parse != nullptr &&
         srv_event_tracking_query != nullptr &&
         srv_event_tracking_stored_program != nullptr &&
         srv_event_tracking_table_access != nullptr);

  inited = true;
}
void deinit_srv_event_tracking_handles() {
  if (inited) {
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_authentication_t *>(
            srv_event_tracking_authentication)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_command_t *>(srv_event_tracking_command)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_connection_t *>(
            srv_event_tracking_connection)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_general_t *>(srv_event_tracking_general)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_global_variable_t *>(
            srv_event_tracking_global_variable)));
    srv_registry->release(
        reinterpret_cast<my_h_service>(const_cast<event_tracking_lifecycle_t *>(
            srv_event_tracking_lifecycle)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_message_t *>(srv_event_tracking_message)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_parse_t *>(srv_event_tracking_parse)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_query_t *>(srv_event_tracking_query)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_stored_program_t *>(
            srv_event_tracking_stored_program)));
    srv_registry->release(reinterpret_cast<my_h_service>(
        const_cast<event_tracking_table_access_t *>(
            srv_event_tracking_table_access)));
  }
  inited = false;
}

namespace {

/**
  Check, whether masks specified by lhs parameter and rhs parameters overlap.

  @param lhs First mask to check.
  @param rhs Second mask to check.

  @return false, when masks overlap, otherwise true.
*/
static inline bool check_audit_mask(const unsigned long lhs,
                                    const unsigned long rhs) {
  return !(lhs & rhs);
}

/**
  Dispatches an event by invoking the plugin's event_notify method.

  @param[in] thd    Session THD containing references to the audit plugins.
  @param[in] plugin Plugin used for dispatching the event.
  @param[in] arg    Opaque event data structure.

  @retval false  always
*/

static int plugins_dispatch(THD *thd, plugin_ref plugin, void *arg) {
  const struct st_mysql_event_plugin_generic *event_generic =
      (const struct st_mysql_event_plugin_generic *)arg;
  unsigned long subclass = static_cast<unsigned long>(
      *static_cast<const int *>(event_generic->event));
  st_mysql_audit *data = plugin_data<st_mysql_audit *>(plugin);

  /* Check to see if the plugin is interested in this event */
  if (check_audit_mask(data->class_mask[event_generic->event_class], subclass))
    return 0;

  /* Actually notify the plugin */
  return data->event_notify(thd, event_generic->event_class,
                            event_generic->event);
}

static bool plugins_dispatch_bool(THD *thd, plugin_ref plugin, void *arg) {
  return plugins_dispatch(thd, plugin, arg) ? true : false;
}

/**
  Distributes an audit event to plug-ins

  @param[in] thd          THD that generated the event.
  @param     event_class  Audit event class.
  @param[in] event        Opaque pointer to the event data.
*/

int event_class_dispatch(THD *thd, mysql_event_class_t event_class,
                         const void *event) {
  int result = 0;
  struct st_mysql_event_plugin_generic event_generic;
  event_generic.event_class = event_class;
  event_generic.event = event;
  /*
    Check if we are doing a slow global dispatch. This event occurs when
    thd == NULL as it is not associated with any particular thread.
  */
  if (unlikely(!thd)) {
    return plugin_foreach(thd, plugins_dispatch_bool, MYSQL_AUDIT_PLUGIN,
                          &event_generic)
               ? 1
               : 0;
  } else {
    plugin_ref *plugins, *plugins_last;

    /* Use the cached set of audit plugins */
    plugins = thd->audit_class_plugins.begin();
    plugins_last = thd->audit_class_plugins.end();

    for (; plugins != plugins_last; plugins++)
      result |= plugins_dispatch(thd, *plugins, &event_generic);
  }

  return result;
}

static const CHARSET_INFO *get_charset_from_thd(THD *thd) {
  if (thd->rewritten_query().length() > 0)
    return thd->rewritten_query().charset();
  return thd->charset();
}

/**
  Fill query info extracted from the thread object and return
  the thread object charset info.

  @param[in]  thd     Thread data.
  @param[out] query   SQL query text.

  @return SQL query charset.
*/
inline const CHARSET_INFO *thd_get_audit_query(THD *thd, LEX_CSTRING *query) {
  /*
    If we haven't tried to rewrite the query to obfuscate passwords
    etc. yet, do so now.
  */
  if (thd->rewritten_query().length() == 0) mysql_rewrite_query(thd);

  /*
    If there was something to rewrite, use the rewritten query;
    otherwise, just use the original as submitted by the client.
  */
  if (thd->rewritten_query().length() > 0) {
    query->str = thd->rewritten_query().ptr();
    query->length = thd->rewritten_query().length();
    return thd->rewritten_query().charset();
  } else {
    query->str = thd->query().str;
    query->length = thd->query().length;
    return thd->charset();
  }
}

}  // namespace

DEFINE_BOOL_METHOD(Event_authentication_bridge_implementation::notify,
                   (const mysql_event_tracking_authentication_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;
    auto event_information =
        static_cast<Event_tracking_authentication_information *>(
            thd->get_event_tracking_data().second);

    mysql_event_authentication plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_AUTHENTICATION_FLUSH:
        plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_FLUSH;
        break;
      case EVENT_TRACKING_AUTHENTICATION_AUTHID_CREATE:
        plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_AUTHID_CREATE;
        break;
      case EVENT_TRACKING_AUTHENTICATION_CREDENTIAL_CHANGE:
        plugin_data.event_subclass =
            MYSQL_AUDIT_AUTHENTICATION_CREDENTIAL_CHANGE;
        break;
      case EVENT_TRACKING_AUTHENTICATION_AUTHID_RENAME:
        plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_AUTHID_RENAME;
        break;
      case EVENT_TRACKING_AUTHENTICATION_AUTHID_DROP:
        plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_AUTHID_DROP;
        break;
      default:
        assert(false);
        break;
    }

    const char *auth_method =
        event_information->authentication_methods_.size()
            ? event_information->authentication_methods_[0]
            : nullptr;
    plugin_data.authentication_plugin = {auth_method,
                                         auth_method ? strlen(auth_method) : 0};
    plugin_data.connection_id = data->connection_id;
    plugin_data.host = TO_LEXCSTRING(data->host);
    plugin_data.is_role = event_information->is_role_;
    plugin_data.new_host = TO_LEXCSTRING(event_information->new_host_);
    plugin_data.new_user = TO_LEXCSTRING(event_information->new_user_);
    plugin_data.query_charset = thd_get_audit_query(thd, &plugin_data.query);
    plugin_data.sql_command_id = thd->lex->sql_command;
    plugin_data.status = data->status;
    plugin_data.user = TO_LEXCSTRING(data->user);

    return event_class_dispatch(thd, MYSQL_AUDIT_AUTHENTICATION_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_command_bridge_implementation::notify,
                   (const mysql_event_tracking_command_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_command plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_COMMAND_START:
        plugin_data.event_subclass = MYSQL_AUDIT_COMMAND_START;
        break;
      case EVENT_TRACKING_COMMAND_END:
        plugin_data.event_subclass = MYSQL_AUDIT_COMMAND_END;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.command_id = get_server_command(data->command.str);
    plugin_data.connection_id = data->connection_id;
    plugin_data.status = data->status;

    return event_class_dispatch(thd, MYSQL_AUDIT_COMMAND_CLASS, &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_connection_bridge_implementation::notify,
                   (const mysql_event_tracking_connection_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_connection plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_CONNECTION_CONNECT:
        plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_CONNECT;
        break;
      case EVENT_TRACKING_CONNECTION_DISCONNECT:
        plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_DISCONNECT;
        break;
      case EVENT_TRACKING_CONNECTION_CHANGE_USER:
        plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_CHANGE_USER;
        break;
      case EVENT_TRACKING_CONNECTION_PRE_AUTHENTICATE:
        plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.status = data->status;
    plugin_data.connection_id = data->connection_id;
    plugin_data.user = TO_LEXCSTRING(data->user);
    plugin_data.priv_user = TO_LEXCSTRING(data->priv_user);
    plugin_data.external_user = TO_LEXCSTRING(data->external_user);
    plugin_data.proxy_user = TO_LEXCSTRING(data->proxy_user);
    plugin_data.host = TO_LEXCSTRING(data->host);
    plugin_data.ip = TO_LEXCSTRING(data->ip);
    plugin_data.database = TO_LEXCSTRING(data->database);
    plugin_data.connection_type = data->connection_type;

    return event_class_dispatch(thd, MYSQL_AUDIT_CONNECTION_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_general_bridge_implementation::notify,
                   (const mysql_event_tracking_general_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    auto event_information = static_cast<Event_tracking_general_information *>(
        thd->get_event_tracking_data().second);

    mysql_event_general plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_GENERAL_LOG:
        plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_LOG;
        break;
      case EVENT_TRACKING_GENERAL_ERROR:
        plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_ERROR;
        break;
      case EVENT_TRACKING_GENERAL_STATUS:
        plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_STATUS;
        break;
      case EVENT_TRACKING_GENERAL_RESULT:
        plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_RESULT;
        break;
      default:
        assert(false);
        break;
    };

    plugin_data.general_error_code = data->error_code;
    plugin_data.general_thread_id = data->connection_id;
    plugin_data.general_user = TO_LEXCSTRING(data->user);
    plugin_data.general_ip = TO_LEXCSTRING(data->ip);
    plugin_data.general_host = TO_LEXCSTRING(data->host);
    plugin_data.general_external_user =
        TO_LEXCSTRING(event_information->external_user_);
    plugin_data.general_rows =
        static_cast<unsigned long long>(event_information->rows_);

    if (event_information->command_.str != nullptr &&
        thd->lex->sql_command == SQLCOM_END &&
        thd->get_command() != COM_QUERY) {
      plugin_data.general_sql_command = {STRING_WITH_LEN("")};
    } else {
      plugin_data.general_sql_command =
          sql_statement_names[thd->lex->sql_command];
    }

    plugin_data.general_charset = const_cast<CHARSET_INFO *>(
        thd_get_audit_query(thd, &plugin_data.general_query));
    plugin_data.general_time =
        static_cast<unsigned long long>(event_information->time_);
    plugin_data.general_command = {event_information->command_.str,
                                   event_information->command_.length};

    return event_class_dispatch(thd, MYSQL_AUDIT_GENERAL_CLASS, &plugin_data);

  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_global_variable_bridge_implementation::notify,
                   (const mysql_event_tracking_global_variable_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_global_variable plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_GLOBAL_VARIABLE_GET:
        plugin_data.event_subclass = MYSQL_AUDIT_GLOBAL_VARIABLE_GET;
        break;
      case EVENT_TRACKING_GLOBAL_VARIABLE_SET:
        plugin_data.event_subclass = MYSQL_AUDIT_GLOBAL_VARIABLE_SET;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.connection_id = data->connection_id;
    plugin_data.sql_command_id = thd->lex->sql_command;
    plugin_data.variable_name = TO_LEXCSTRING(data->variable_name);
    plugin_data.variable_value = TO_LEXCSTRING(data->variable_value);

    return event_class_dispatch(thd, MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_lifecycle_bridge_implementation::notify_shutdown,
                   (const mysql_event_tracking_shutdown_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_server_shutdown plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_SHUTDOWN_SHUTDOWN:
        plugin_data.event_subclass = MYSQL_AUDIT_SERVER_SHUTDOWN_SHUTDOWN;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.exit_code = data->exit_code;

    switch (data->reason) {
      case EVENT_TRACKING_SHUTDOWN_REASON_SHUTDOWN:
        plugin_data.exit_code = MYSQL_AUDIT_SERVER_SHUTDOWN_REASON_SHUTDOWN;
        break;
      case EVENT_TRACKING_SHUTDOWN_REASON_ABORT:
        plugin_data.reason = MYSQL_AUDIT_SERVER_SHUTDOWN_REASON_ABORT;
        break;
      default:
        assert(false);
        break;
    }

    return event_class_dispatch(thd, MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_lifecycle_bridge_implementation::notify_startup,
                   (const mysql_event_tracking_startup_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_server_startup plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_STARTUP_STARTUP:
        plugin_data.event_subclass = MYSQL_AUDIT_SERVER_STARTUP_STARTUP;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.argc = data->argc;
    plugin_data.argv = data->argv;

    return event_class_dispatch(thd, MYSQL_AUDIT_SERVER_STARTUP_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_message_bridge_implementation::notify,
                   (const mysql_event_tracking_message_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_message plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_MESSAGE_INTERNAL:
        plugin_data.event_subclass = MYSQL_AUDIT_MESSAGE_INTERNAL;
        break;
      case EVENT_TRACKING_MESSAGE_USER:
        plugin_data.event_subclass = MYSQL_AUDIT_MESSAGE_USER;
        break;
      default:
        assert(false);
        break;
    }

    std::unique_ptr<mysql_event_message_key_value_t[]> local_key_value_map(
        data->key_value_map_length > 0
            ? new mysql_event_message_key_value_t[data->key_value_map_length]
            : nullptr);

    mysql_event_message_key_value_t *local_kv = local_key_value_map.get();
    mysql_event_tracking_message_key_value_t *kv = data->key_value_map;

    for (size_t i = 0; i < data->key_value_map_length; ++i, ++local_kv, ++kv) {
      local_kv->key = {kv->key.str, kv->key.length};
      switch (kv->value_type) {
        case EVENT_TRACKING_MESSAGE_VALUE_TYPE_STR:
          local_kv->value_type = MYSQL_AUDIT_MESSAGE_VALUE_TYPE_STR;
          local_kv->value.str = {kv->value.str.str, kv->value.str.length};
          break;
        case EVENT_TRACKING_MESSAGE_VALUE_TYPE_NUM:
          local_kv->value_type = MYSQL_AUDIT_MESSAGE_VALUE_TYPE_NUM;
          local_kv->value.num = kv->value.num;
          break;
        default:
          assert(false);
          break;
      }
    }

    plugin_data.component = TO_LEXCSTRING(data->component);
    plugin_data.key_value_map = local_key_value_map.get();
    plugin_data.key_value_map_length = data->key_value_map_length;
    plugin_data.message = TO_LEXCSTRING(data->message);
    plugin_data.producer = TO_LEXCSTRING(data->producer);

    return event_class_dispatch(thd, MYSQL_AUDIT_MESSAGE_CLASS, &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_parse_bridge_implementation::notify,
                   (mysql_event_tracking_parse_data * data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_parse plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_PARSE_PREPARSE:
        plugin_data.event_subclass = MYSQL_AUDIT_PARSE_PREPARSE;
        break;
      case EVENT_TRACKING_PARSE_POSTPARSE:
        plugin_data.event_subclass = MYSQL_AUDIT_PARSE_POSTPARSE;
        break;
      default:
        assert(false);
        break;
    }

    mysql_event_parse_rewrite_plugin_flag plugin_flag;
    switch (*(data->flags)) {
      case EVENT_TRACKING_PARSE_REWRITE_NONE:
        plugin_flag = MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_NONE;
        break;
      case EVENT_TRACKING_PARSE_REWRITE_QUERY_REWRITTEN:
        plugin_flag = MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_QUERY_REWRITTEN;
        break;
      case EVENT_TRACKING_PARSE_REWRITE_IS_PREPARED_STATEMENT:
        plugin_flag = MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_IS_PREPARED_STATEMENT;
        break;
      default:
        assert(false);
        break;
    }

    LEX_CSTRING rewritten_query{nullptr, 0};
    plugin_data.flags = &plugin_flag;
    plugin_data.query = TO_LEXCSTRING(data->query);
    plugin_data.rewritten_query = nullptr;
    if (data->rewritten_query) {
      plugin_data.rewritten_query = &rewritten_query;
    }

    bool retval =
        event_class_dispatch(thd, MYSQL_AUDIT_PARSE_CLASS, &plugin_data);

    if ((int)plugin_flag & EVENT_TRACKING_PARSE_REWRITE_QUERY_REWRITTEN) {
      *(data->flags) |= EVENT_TRACKING_PARSE_REWRITE_QUERY_REWRITTEN;
      if (data->rewritten_query) {
        data->rewritten_query->str = rewritten_query.str;
        data->rewritten_query->length = rewritten_query.length;
      }
    }

    return retval;
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_query_bridge_implementation::notify,
                   (const mysql_event_tracking_query_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_query plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_QUERY_START:
        plugin_data.event_subclass = MYSQL_AUDIT_QUERY_START;
        break;
      case EVENT_TRACKING_QUERY_NESTED_START:
        plugin_data.event_subclass = MYSQL_AUDIT_QUERY_NESTED_START;
        break;
      case EVENT_TRACKING_QUERY_STATUS_END:
        plugin_data.event_subclass = MYSQL_AUDIT_QUERY_STATUS_END;
        break;
      case EVENT_TRACKING_QUERY_NESTED_STATUS_END:
        plugin_data.event_subclass = MYSQL_AUDIT_QUERY_NESTED_STATUS_END;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.connection_id = data->connection_id;
    plugin_data.query = TO_LEXCSTRING(data->query);
    plugin_data.query_charset = get_charset_from_thd(thd);
    plugin_data.sql_command_id = thd->lex->sql_command;
    plugin_data.status = data->status;

    return event_class_dispatch(thd, MYSQL_AUDIT_QUERY_CLASS, &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_stored_program_bridge_implementation::notify,
                   (const mysql_event_tracking_stored_program_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_stored_program plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_STORED_PROGRAM_EXECUTE:
        plugin_data.event_subclass = MYSQL_AUDIT_STORED_PROGRAM_EXECUTE;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.connection_id = data->connection_id;
    plugin_data.database = TO_LEXCSTRING(data->database);
    plugin_data.name = TO_LEXCSTRING(data->name);
    plugin_data.parameters = data->parameters;
    plugin_data.query_charset = thd_get_audit_query(thd, &plugin_data.query);
    plugin_data.sql_command_id = thd->lex->sql_command;

    return event_class_dispatch(thd, MYSQL_AUDIT_STORED_PROGRAM_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}

DEFINE_BOOL_METHOD(Event_table_access_bridge_implementation::notify,
                   (const mysql_event_tracking_table_access_data *data)) {
  try {
    if (data == nullptr) return false;
    THD *thd = current_thd;

    mysql_event_table_access plugin_data;

    switch (data->event_subclass) {
      case EVENT_TRACKING_TABLE_ACCESS_READ:
        plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_READ;
        break;
      case EVENT_TRACKING_TABLE_ACCESS_INSERT:
        plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_INSERT;
        break;
      case EVENT_TRACKING_TABLE_ACCESS_UPDATE:
        plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_UPDATE;
        break;
      case EVENT_TRACKING_TABLE_ACCESS_DELETE:
        plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_DELETE;
        break;
      default:
        assert(false);
        break;
    }

    plugin_data.connection_id = data->connection_id;
    plugin_data.sql_command_id = thd->lex->sql_command;
    plugin_data.query_charset = thd_get_audit_query(thd, &plugin_data.query);
    plugin_data.table_database = TO_LEXCSTRING(data->table_database);
    plugin_data.table_name = TO_LEXCSTRING(data->table_name);

    return event_class_dispatch(thd, MYSQL_AUDIT_TABLE_ACCESS_CLASS,
                                &plugin_data);
  } catch (...) {
    return true;
  }
}
