Extending MySQL 8.4  /  ...  /  Writing Audit Plugins

4.4.8 Writing Audit Plugins

This section describes how to write a server-side audit plugin, using the example plugin found in the plugin/audit_null directory of MySQL source distributions. The audit_null.c and audit_null_variables.h source files in that directory implement an audit plugin named NULL_AUDIT.

Note

Other examples of plugins that use the audit plugin API are the query rewrite plugin (see The Rewriter Query Rewrite Plugin) and the Version Tokens plugin (see Version Tokens).

Within the server, the pluggable audit interface is implemented in the sql_audit.h and sql_audit.cc files in the sql directory of MySQL source distributions. Additionally, several places in the server call the audit interface when an auditable event occurs, so that registered audit plugins can be notified about the event if necessary. To see where such calls occur, search the server source files for invocations of functions with names of the form mysql_audit_xxx(). Audit notification occurs for server operations such as these:

  • Client connect and disconnect events

  • Writing a message to the general query log (if the log is enabled)

  • Writing a message to the error log

  • Sending a query result to a client

To write an audit plugin, include the following header file in the plugin source file. Other MySQL or general header files might also be needed, depending on the plugin capabilities and requirements.

#include <mysql/plugin_audit.h>

plugin_audit.h includes plugin.h, so you need not include the latter file explicitly. plugin.h defines the MYSQL_AUDIT_PLUGIN server plugin type and the data structures needed to declare the plugin. plugin_audit.h defines data structures specific to audit plugins.

Audit Plugin General Descriptor

An audit plugin, like any MySQL server plugin, has a general plugin descriptor (see Section 4.4.2.1, “Server Plugin Library and Plugin Descriptors”) and a type-specific plugin descriptor. In audit_null.c, the general descriptor for audit_null looks like this:

mysql_declare_plugin(audit_null)
{
  MYSQL_AUDIT_PLUGIN,         /* type                            */
  &audit_null_descriptor,     /* descriptor                      */
  "NULL_AUDIT",               /* name                            */
  "Oracle Corporation",       /* author                          */
  "Simple NULL Audit",        /* description                     */
  PLUGIN_LICENSE_GPL,
  audit_null_plugin_init,     /* init function (when loaded)     */
  audit_null_plugin_deinit,   /* deinit function (when unloaded) */
  0x0003,                     /* version                         */
  simple_status,              /* status variables                */
  system_variables,           /* system variables                */
  NULL,
  0,
}
mysql_declare_plugin_end;

The first member, MYSQL_AUDIT_PLUGIN, identifies this plugin as an audit plugin.

audit_null_descriptor points to the type-specific plugin descriptor, described later.

The name member (NULL_AUDIT) indicates the name to use for references to the plugin in statements such as INSTALL PLUGIN or UNINSTALL PLUGIN. This is also the name displayed by INFORMATION_SCHEMA.PLUGINS or SHOW PLUGINS.

The audit_null_plugin_init initialization function performs plugin initialization when the plugin is loaded. The audit_null_plugin_deinit function performs cleanup when the plugin is unloaded.

The general plugin descriptor also refers to simple_status and system_variables, structures that expose several status and system variables. When the plugin is enabled, these variables can be inspected using SHOW statements (SHOW STATUS, SHOW VARIABLES) or the appropriate Performance Schema tables.

The simple_status structure declares several status variables with names of the form Audit_null_xxx. NULL_AUDIT increments the Audit_null_called status variable for every notification that it receives. The other status variables are more specific and NULL_AUDIT increments them only for notifications of specific events.

system_variables is an array of system variable elements, each of which is defined using a MYSQL_THDVAR_xxx macro. These system variables have names of the form null_audit_xxx. These variables can be used to communicate with the plugin at runtime.

Audit Plugin Type-Specific Descriptor

The audit_null_descriptor value in the general plugin descriptor points to the type-specific plugin descriptor. For audit plugins, this descriptor has the following structure (defined in plugin_audit.h):

struct st_mysql_audit
{
  int interface_version;
  void (*release_thd)(MYSQL_THD);
  int (*event_notify)(MYSQL_THD, mysql_event_class_t, const void *);
  unsigned long class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
};

The type-specific descriptor for audit plugins has these members:

  • interface_version: By convention, type-specific plugin descriptors begin with the interface version for the given plugin type. The server checks interface_version when it loads the plugin to see whether the plugin is compatible with it. For audit plugins, the value of the interface_version member is MYSQL_AUDIT_INTERFACE_VERSION (defined in plugin_audit.h).

  • release_thd: A function that the server calls to inform the plugin that it is being dissociated from its thread context. This should be NULL if there is no such function.

  • event_notify: A function that the server calls to notify the plugin that an auditable event has occurred. This function should not be NULL; that would not make sense because no auditing would occur.

  • class_mask: An array of MYSQL_AUDIT_CLASS_MASK_SIZE elements. Each element specifies a bitmask for a given event class to indicate the subclasses for which the plugin wants notification. (This is how the plugin subscribes to events of interest.) An element should be 0 to ignore all events for the corresponding event class.

The server uses the event_notify and release_thd functions together. They are called within the context of a specific thread, and a thread might perform an activity that produces several event notifications. The first time the server calls event_notify for a thread, it creates a binding of the plugin to the thread. The plugin cannot be uninstalled while this binding exists. When no more events for the thread will occur, the server informs the plugin of this by calling the release_thd function, and then destroys the binding. For example, when a client issues a statement, the thread processing the statement might notify audit plugins about the result set produced by the statement and about the statement being logged. After these notifications occur, the server releases the plugin before putting the thread to sleep until the client issues another statement.

This design enables the plugin to allocate resources needed for a given thread in the first call to the event_notify function and release them in the release_thd function:

event_notify function:
  if memory is needed to service the thread
    allocate memory
  ... rest of notification processing ...

release_thd function:
  if memory was allocated
    release memory
  ... rest of release processing ...

That is more efficient than allocating and releasing memory repeatedly in the notification function.

For the NULL_AUDIT audit plugin, the type-specific plugin descriptor looks like this:

static struct st_mysql_audit audit_null_descriptor=
{
  MYSQL_AUDIT_INTERFACE_VERSION,                    /* interface version    */
  NULL,                                             /* release_thd function */
  audit_null_notify,                                /* notify function      */
  { (unsigned long) MYSQL_AUDIT_GENERAL_ALL,
    (unsigned long) MYSQL_AUDIT_CONNECTION_ALL,
    (unsigned long) MYSQL_AUDIT_PARSE_ALL,
    (unsigned long) MYSQL_AUDIT_AUTHORIZATION_ALL,
    (unsigned long) MYSQL_AUDIT_TABLE_ACCESS_ALL,
    (unsigned long) MYSQL_AUDIT_GLOBAL_VARIABLE_ALL,
    (unsigned long) MYSQL_AUDIT_SERVER_STARTUP_ALL,
    (unsigned long) MYSQL_AUDIT_SERVER_SHUTDOWN_ALL,
    (unsigned long) MYSQL_AUDIT_COMMAND_ALL,
    (unsigned long) MYSQL_AUDIT_QUERY_ALL,
    (unsigned long) MYSQL_AUDIT_STORED_PROGRAM_ALL }
};

The server calls audit_null_notify() to pass audit event information to the plugin. The plugin has no release_thd function.

The class_mask member is an array that indicates which event classes the plugin subscribes to. As shown, the array contents subscribe to all subclasses of all event classes that are available. To ignore all notifications for a given event class, specify the corresponding class_mask element as 0.

The number of class_mask elements corresponds to the number of event classes, each of which is listed in the mysql_event_class_t enumeration defined in plugin_audit.h:

typedef enum
{
  MYSQL_AUDIT_GENERAL_CLASS          = 0,
  MYSQL_AUDIT_CONNECTION_CLASS       = 1,
  MYSQL_AUDIT_PARSE_CLASS            = 2,
  MYSQL_AUDIT_AUTHORIZATION_CLASS    = 3,
  MYSQL_AUDIT_TABLE_ACCESS_CLASS     = 4,
  MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS  = 5,
  MYSQL_AUDIT_SERVER_STARTUP_CLASS   = 6,
  MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS  = 7,
  MYSQL_AUDIT_COMMAND_CLASS          = 8,
  MYSQL_AUDIT_QUERY_CLASS            = 9,
  MYSQL_AUDIT_STORED_PROGRAM_CLASS   = 10,
  /* This item must be last in the list. */
  MYSQL_AUDIT_CLASS_MASK_SIZE
} mysql_event_class_t;

For any given event class, plugin_audit.h defines bitmask symbols for individual event subclasses, as well as an xxx_ALL symbol that is the union of the all subclass bitmasks. For example, for MYSQL_AUDIT_CONNECTION_CLASS (the class that covers connect and disconnect events), plugin_audit.h defines these symbols:

typedef enum
{
  /** occurs after authentication phase is completed. */
  MYSQL_AUDIT_CONNECTION_CONNECT          = 1 << 0,
  /** occurs after connection is terminated. */
  MYSQL_AUDIT_CONNECTION_DISCONNECT       = 1 << 1,
  /** occurs after COM_CHANGE_USER RPC is completed. */
  MYSQL_AUDIT_CONNECTION_CHANGE_USER      = 1 << 2,
  /** occurs before authentication. */
  MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE = 1 << 3
} mysql_event_connection_subclass_t;

#define MYSQL_AUDIT_CONNECTION_ALL (MYSQL_AUDIT_CONNECTION_CONNECT | \
                                    MYSQL_AUDIT_CONNECTION_DISCONNECT | \
                                    MYSQL_AUDIT_CONNECTION_CHANGE_USER | \
                                    MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE)

To subscribe to all subclasses of the connection event class (as the NULL_AUDIT plugin does), a plugin specifies MYSQL_AUDIT_CONNECTION_ALL in the corresponding class_mask element (class_mask[1] in this case). To subscribe to only some subclasses, the plugin sets the class_mask element to the union of the subclasses of interest. For example, to subscribe only to the connect and change-user subclasses, the plugin sets class_mask[1] to this value:

MYSQL_AUDIT_CONNECTION_CONNECT | MYSQL_AUDIT_CONNECTION_CHANGE_USER

Audit Plugin Notification Function

Most of the work for an audit plugin occurs in the notification function (the event_notify member of the type-specific plugin descriptor). The server calls this function for each auditable event. Audit plugin notification functions have this prototype:

int (*event_notify)(MYSQL_THD, mysql_event_class_t, const void *);

The second and third parameters of the event_notify function prototype represent the event class and a generic pointer to an event structure. (Events in different classes have different structures. The notification function can use the event class value to determine which event structure applies.) The function processes the event and returns a status indicating whether the server should continue processing the event or terminate it.

For NULL_AUDIT, the notification function is audit_null_notify(). This function increments a global event counter (which the plugin exposes as the value of the Audit_null_called status value), and then examines the event class to determine how to process the event structure:

static int audit_null_notify(MYSQL_THD thd __attribute__((unused)),
                             mysql_event_class_t event_class,
                             const void *event)
{
  ...

  number_of_calls++;

  if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
  {
    const struct mysql_event_general *event_general=
                                    (const struct mysql_event_general *)event;
    ...
  }
  else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
  {
    const struct mysql_event_connection *event_connection=
                                (const struct mysql_event_connection *) event;
    ...

  }
  else if (event_class == MYSQL_AUDIT_PARSE_CLASS)
  {
    const struct mysql_event_parse *event_parse =
                                      (const struct mysql_event_parse *)event;
    ...
  }
  ...
}

The notification function interprets the event argument according to the value of event_class. The event argument is a generic pointer to the event record, the structure of which differs per event class. (The plugin_audit.h file contains the structures that define the contents of each event class.) For each class, audit_null_notify() casts the event to the appropriate class-specific structure and then checks its subclass to determine which subclass counter to increment. For example, the code to handle events in the connection-event class looks like this:

else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
{
  const struct mysql_event_connection *event_connection=
                              (const struct mysql_event_connection *) event;

  switch (event_connection->event_subclass)
  {
  case MYSQL_AUDIT_CONNECTION_CONNECT:
    number_of_calls_connection_connect++;
    break;
  case MYSQL_AUDIT_CONNECTION_DISCONNECT:
    number_of_calls_connection_disconnect++;
    break;
  case MYSQL_AUDIT_CONNECTION_CHANGE_USER:
    number_of_calls_connection_change_user++;
    break;
  case MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE:
    number_of_calls_connection_pre_authenticate++;
      break;
  default:
    break;
  }
}
Note

The general event class (MYSQL_AUDIT_GENERAL_CLASS) is deprecated and will be removed in a future MySQL release. To reduce plugin overhead, it is preferable to subscribe only to the more specific event classes of interest.

For some event classes, the NULL_AUDIT plugin performs other processing in addition to incrementing a counter. In any case, when the notification function finishes processing the event, it should return a status indicating whether the server should continue processing the event or terminate it.

Audit Plugin Error Handling

Audit plugin notification functions can report a status value for the current event two ways:

  • Use the notification function return value. In this case, the function returns zero if the server should continue processing the event, or nonzero if the server should terminate the event.

  • Call the my_message() function to set the error state before returning from the notification function. In this case, the notification function return value is ignored and the server aborts the event and terminates event processing with an error. The my_message() arguments indicate which error to report, and its message. For example:

    my_message(ER_AUDIT_API_ABORT, "This is my error message.", MYF(0));

    Some events cannot be aborted. A nonzero return value is not taken into consideration and the my_message() error call must follow an is_error() check. For example:

    if (!thd->get_stmt_da()->is_error())
    {
      my_message(ER_AUDIT_API_ABORT, "This is my error message.", MYF(0));
    }

These events cannot be aborted:

  • MYSQL_AUDIT_CONNECTION_DISCONNECT: The server cannot prevent a client from disconnecting.

  • MYSQL_AUDIT_COMMAND_END: This event provides the status of a command that has finished executing, so there is no purpose to terminating it.

If an audit plugin returns nonzero status for a nonterminable event, the server ignores the status and continues processing the event. This is also true if an audit plugin uses the my_message() function to terminate a nonterminable event.

Audit Plugin Usage

To compile and install a plugin library file, use the instructions in Section 4.4.3, “Compiling and Installing Plugin Libraries”. To make the library file available for use, install it in the plugin directory (the directory named by the plugin_dir system variable). For the NULL_AUDIT plugin, it is compiled and installed when you build MySQL from source. It is also included in binary distributions. The build process produces a shared object library with a name of adt_null.so (the .so suffix might differ depending on your platform).

To register the plugin at runtime, use this statement, adjusting the .so suffix for your platform as necessary:

INSTALL PLUGIN NULL_AUDIT SONAME 'adt_null.so';

For additional information about plugin loading, see Installing and Uninstalling Plugins.

To verify plugin installation, examine the INFORMATION_SCHEMA.PLUGINS table or use the SHOW PLUGINS statement. See Obtaining Server Plugin Information.

While the NULL_AUDIT audit plugin is installed, it exposes status variables that indicate the events for which the plugin has been called:

mysql> SHOW STATUS LIKE 'Audit_null%';
+----------------------------------------+--------+
| Variable_name                          | Value  |
+----------------------------------------+--------+
| Audit_null_authorization_column        | 0      |
| Audit_null_authorization_db            | 0      |
| Audit_null_authorization_procedure     | 0      |
| Audit_null_authorization_proxy         | 0      |
| Audit_null_authorization_table         | 0      |
| Audit_null_authorization_user          | 0      |
| Audit_null_called                      | 185547 |
| Audit_null_command_end                 | 20999  |
| Audit_null_command_start               | 21001  |
| Audit_null_connection_change_user      | 0      |
| Audit_null_connection_connect          | 5823   |
| Audit_null_connection_disconnect       | 5818   |
| Audit_null_connection_pre_authenticate | 5823   |
| Audit_null_general_error               | 1      |
| Audit_null_general_log                 | 26559  |
| Audit_null_general_result              | 19922  |
| Audit_null_general_status              | 21000  |
| Audit_null_global_variable_get         | 0      |
| Audit_null_global_variable_set         | 0      |
| Audit_null_message_internal            | 0      |
| Audit_null_message_user                | 0      |
| Audit_null_parse_postparse             | 14648  |
| Audit_null_parse_preparse              | 14648  |
| Audit_null_query_nested_start          | 6      |
| Audit_null_query_nested_status_end     | 6      |
| Audit_null_query_start                 | 14648  |
| Audit_null_query_status_end            | 14647  |
| Audit_null_server_shutdown             | 0      |
| Audit_null_server_startup              | 1      |
| Audit_null_table_access_delete         | 104    |
| Audit_null_table_access_insert         | 2839   |
| Audit_null_table_access_read           | 97842  |
| Audit_null_table_access_update         | 278    |
+----------------------------------------+--------+

Audit_null_called counts all events, and the other variables count instances of specific event subclasses. For example, the preceding SHOW STATUS statement causes the server to send a result to the client and to write a message to the general query log if that log is enabled. Thus, a client that issues the statement repeatedly causes Audit_null_called, Audit_null_general_result, and Audit_null_general_log to be incremented each time. Notifications occur whether or not that log is enabled.

The status variables values are global and aggregated across all sessions. There are no counters for individual sessions.

NULL_AUDIT exposes several system variables that enable communication with the plugin at runtime:

mysql> SHOW VARIABLES LIKE 'null_audit%';
+---------------------------------------------------+-------+
| Variable_name                                     | Value |
+---------------------------------------------------+-------+
| null_audit_abort_message                          |       |
| null_audit_abort_value                            | 1     |
| null_audit_event_order_check                      |       |
| null_audit_event_order_check_consume_ignore_count | 0     |
| null_audit_event_order_check_exact                | 1     |
| null_audit_event_order_started                    | 0     |
| null_audit_event_record                           |       |
| null_audit_event_record_def                       |       |
+---------------------------------------------------+-------+

The NULL_AUDIT system variables have these meanings:

  • null_audit_abort_message: The custom error message to use when an event is aborted.

  • null_audit_abort_value: The custom error code to use when an event is aborted.

  • null_audit_event_order_check: Prior to event matching, the expected event order. After event matching, the matching outcome.

  • null_audit_event_order_check_consume_ignore_count: Number of times event matching should not consume matched events.

  • null_audit_event_order_check_exact: Whether event matching must be exact. Disabling this variable enables skipping events not listed in null_audit_event_order_check during event-order matching. Of the events specified, they must still match in the order given.

  • null_audit_event_order_started: For internal use.

  • null_audit_event_record: The recorded events after event recording takes place.

  • null_audit_event_record_def: The names of the start and end events to match when recording events, separated by a semicolon. The value must be set before each statement for which events are recorded.

To demonstrate use of those system variables, suppose that a table db1.t1 exists, created as follows:

CREATE DATABASE db1;
CREATE TABLE db1.t1 (a VARCHAR(255));

For test-creation purposes, it is possible to record events that pass through the plugin. To start recording, specify the start and end events in the null_audit_event_record_def variable. For example:

SET @@null_audit_event_record_def =
  'MYSQL_AUDIT_COMMAND_START;MYSQL_AUDIT_COMMAND_END';

After a statement occurs that matches those start and end events, the null_audit_event_record system variable contains the resulting event sequence. For example, after recording the events for a SELECT 1 statement, null_audit_event_record is a string that has a value consisting of a set of event strings:

MYSQL_AUDIT_COMMAND_START;command_id="3";
MYSQL_AUDIT_PARSE_PREPARSE;;
MYSQL_AUDIT_PARSE_POSTPARSE;;
MYSQL_AUDIT_GENERAL_LOG;;
MYSQL_AUDIT_QUERY_START;sql_command_id="0";
MYSQL_AUDIT_QUERY_STATUS_END;sql_command_id="0";
MYSQL_AUDIT_GENERAL_RESULT;;
MYSQL_AUDIT_GENERAL_STATUS;;
MYSQL_AUDIT_COMMAND_END;command_id="3";

After recording the events for an INSERT INTO db1.t1 VALUES ('some data') statement, null_audit_event_record has this value:

MYSQL_AUDIT_COMMAND_START;command_id="3";
MYSQL_AUDIT_PARSE_PREPARSE;;
MYSQL_AUDIT_PARSE_POSTPARSE;;
MYSQL_AUDIT_GENERAL_LOG;;
MYSQL_AUDIT_QUERY_START;sql_command_id="5";
MYSQL_AUDIT_TABLE_ACCESS_INSERT;db="db1" table="t1";
MYSQL_AUDIT_QUERY_STATUS_END;sql_command_id="5";
MYSQL_AUDIT_GENERAL_RESULT;;
MYSQL_AUDIT_GENERAL_STATUS;;
MYSQL_AUDIT_COMMAND_END;command_id="3";

Each event string has this format, with semicolons separating the string parts:

event_name;event_data;command

Event strings have these parts:

  • event_name: The event name (a symbol that begins with MYSQL_AUDIT_).

  • event_data: Empty, or, as described later, data associated with the event.

  • command: Empty, or, as described later, a command to execute when the event is matched.

Note

A limitation of the NULL_AUDIT plugin is that event recording works for a single session only. Once you record events in a given session, event recording in subsequent sessions yields a null_audit_event_record value of NULL. To record events again, it is necessary to restart the plugin.

To check the order of audit API calls, set the null_audit_event_order_check variable to the expected event order for a particular operation, listing one or more event strings, each containing two semicolons internally, with additional semicolons separating adjacent event strings:

event_name;event_data;command [;event_name;event_data;command] ...

For example:

SET @@null_audit_event_order_check =
  'MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE;;;'
  'MYSQL_AUDIT_GENERAL_LOG;;;'
  'MYSQL_AUDIT_CONNECTION_CONNECT;;';

For better readability, the statement takes advantage of the SQL syntax that concatenates adjacent strings into a single string.

After you set the null_audit_event_order_check variable to a list of event strings, the next matching operation replaces the variable value with a value that indicates the operation outcome:

  • If the expected event order was matched successfully, the resulting null_audit_event_order_check value is EVENT-ORDER-OK.

  • If the null_audit_event_order_check value specified aborting a matched event (as described later), the resulting null_audit_event_order_check value is EVENT-ORDER-ABORT.

  • If the expected event order failed with unexpected data, the resulting null_audit_event_order_check value is EVENT-ORDER-INVALID-DATA. This occurs, for example, if an event was specified as expected to affect table t1 but actually affected t2.

When you assign to null_audit_event_order_check the list of events to be matched, some events should be specified with a nonempty event_data part of the event string. The following table shows the event_data format for these events. If an event takes multiple data values, they must be specified in the order shown. Alternatively, it is possible to specify an event_data value as <IGNORE> to ignore event data content; in this case, it does not matter whether or not an event haas data.

Applicable Events Event Data Format

MYSQL_AUDIT_COMMAND_START

MYSQL_AUDIT_COMMAND_END

command_id="id_value"

MYSQL_AUDIT_GLOBAL_VARIABLE_GET

MYSQL_AUDIT_GLOBAL_VARIABLE_SET

name="var_value" value="var_value"

MYSQL_AUDIT_QUERY_NESTED_START

MYSQL_AUDIT_QUERY_NESTED_STATUS_END

MYSQL_AUDIT_QUERY_START

MYSQL_AUDIT_QUERY_STATUS_END

sql_command_id="id_value"

MYSQL_AUDIT_TABLE_ACCESS_DELETE

MYSQL_AUDIT_TABLE_ACCESS_INSERT

MYSQL_AUDIT_TABLE_ACCESS_READ

MYSQL_AUDIT_TABLE_ACCESS_UPDATE

db="db_name" table="table_name"

In the null_audit_event_order_check value, specifying ABORT_RET in the command part of an event string makes it possible to abort the audit API call on the specified event. (Assuming that the event is one that can be aborted. Those that cannot were described previously.) For example, as shown previously, this is the expected order of events for an insert into t1:

MYSQL_AUDIT_COMMAND_START;command_id="3";
MYSQL_AUDIT_PARSE_PREPARSE;;
MYSQL_AUDIT_PARSE_POSTPARSE;;
MYSQL_AUDIT_GENERAL_LOG;;
MYSQL_AUDIT_QUERY_START;sql_command_id="5";
MYSQL_AUDIT_TABLE_ACCESS_INSERT;db="db1" table="t1";
MYSQL_AUDIT_QUERY_STATUS_END;sql_command_id="5";
MYSQL_AUDIT_GENERAL_RESULT;;
MYSQL_AUDIT_GENERAL_STATUS;;
MYSQL_AUDIT_COMMAND_END;command_id="3";

To abort INSERT statement execution when the MYSQL_AUDIT_QUERY_STATUS_END event occurs, set null_audit_event_order_check like this (remember to add semicolon separators between adjacent event strings):

SET @@null_audit_event_order_check =
  'MYSQL_AUDIT_COMMAND_START;command_id="3";;'
  'MYSQL_AUDIT_PARSE_PREPARSE;;;'
  'MYSQL_AUDIT_PARSE_POSTPARSE;;;'
  'MYSQL_AUDIT_GENERAL_LOG;;;'
  'MYSQL_AUDIT_QUERY_START;sql_command_id="5";;'
  'MYSQL_AUDIT_TABLE_ACCESS_INSERT;db="db1" table="t1";;'
  'MYSQL_AUDIT_QUERY_STATUS_END;sql_command_id="5";ABORT_RET';

It is not necesary to list events that are expected to occur after the event string that contains a command value of ABORT_RET.

After the audit plugin matches the preceding sequence, it aborts event processing and sends an error message to the client. It also sets null_audit_event_order_check to EVENT-ORDER-ABORT:

mysql> INSERT INTO db1.t1 VALUES ('some data');
ERROR 3164 (HY000): Aborted by Audit API ('MYSQL_AUDIT_QUERY_STATUS_END';1).
mysql> SELECT @@null_audit_event_order_check;
+--------------------------------+
| @@null_audit_event_order_check |
+--------------------------------+
| EVENT-ORDER-ABORT              |
+--------------------------------+

Returning a nonzero value from the audit API notification routine is the standard way to abort event execution. It is also possible to specify a custom error code by setting the null_audit_abort_value variable to the value that the notification routine should return:

SET @@null_audit_abort_value = 123;

Aborting a sequence results in a standard message with the custom error code. Suppose that you set audit log system variables like this, to abort on a match for the events that occur for a SELECT 1 statement:

SET @@null_audit_abort_value = 123;
SET @@null_audit_event_order_check =
  'MYSQL_AUDIT_COMMAND_START;command_id="3";;'
  'MYSQL_AUDIT_PARSE_PREPARSE;;;'
  'MYSQL_AUDIT_PARSE_POSTPARSE;;;'
  'MYSQL_AUDIT_GENERAL_LOG;;;'
  'MYSQL_AUDIT_QUERY_START;sql_command_id="0";ABORT_RET';

Then execution of SELECT 1 results in this error message that includes the custom error code:

mysql> SELECT 1;
ERROR 3164 (HY000): Aborted by Audit API ('MYSQL_AUDIT_QUERY_START';123).

mysql> SELECT @@null_audit_event_order_check;
+--------------------------------+
| @@null_audit_event_order_check |
+--------------------------------+
| EVENT-ORDER-ABORT              |
+--------------------------------+

An event can be also aborted with a custom message, specified by setting the null_audit_abort_message variable. Suppose that you set audit log system variables like this:

SET @@null_audit_abort_message = 'Custom error text.';
SET @@null_audit_event_order_check =
  'MYSQL_AUDIT_COMMAND_START;command_id="3";;'
  'MYSQL_AUDIT_PARSE_PREPARSE;;;'
  'MYSQL_AUDIT_PARSE_POSTPARSE;;;'
  'MYSQL_AUDIT_GENERAL_LOG;;;'
  'MYSQL_AUDIT_QUERY_START;sql_command_id="0";ABORT_RET';

Then aborting a sequence results in the following error message:

mysql> SELECT 1;
ERROR 3164 (HY000): Custom error text.
mysql> SELECT @@null_audit_event_order_check;
+--------------------------------+
| @@null_audit_event_order_check |
+--------------------------------+
| EVENT-ORDER-ABORT              |
+--------------------------------+

To disable the NULL_AUDIT plugin after testing it, use this statement to unload it:

UNINSTALL PLUGIN NULL_AUDIT;