Ulf Wendel

MySQL Plugin development: public API?

| 1 Comment

The MySQL Plugin API is a neat way to add functionality to the MySQL server. Any user of MySQL is using it: see the storage engine plugins listed by SHOW PLUGINS. There are many types of plugins. For example, the do-whatever-you-want daemon plugins. Among the hottest things that appeared lately are plugins that add new interfaces to MySQL, such as the Memcache plugin of MySQL 5.6.

MySQL Client Server Protocol Memcache Protocol
Port 3306 Port 11211
| Daemon Plugin
MySQL

However, to me a PHP guy writing an extension, assimilating arbitrary libraries and glueing them together is reason enough to into plugin hacking It is tempting to a plugin developer to reuse code of the system being extended. MySQL 5.6.7-rc has more than 2 million lines of code. There must be hot stuff among it! But, is it "public", is there a public API?

A look at the daemon plugin example

Writing a daemon plugin is straight forward: grab the source, cd into the plugin directory and copy the daemon plugin example. Edit the structure that describes the plugin. The manual has more details.

mysql_declare_plugin(daemon_example)
{
  MYSQL_DAEMON_PLUGIN,
  &daemon_example_plugin,
  "daemon_example",
  "Brian Aker",
  "Daemon example, creates a heartbeat beat file in mysql-heartbeat.log",
  PLUGIN_LICENSE_GPL,
  daemon_example_plugin_init, /* Plugin Init */
  daemon_example_plugin_deinit, /* Plugin Deinit */
  0x0100 /* 1.0 */,
  NULL,                       /* status variables                */
  NULL,                       /* system variables                */
  NULL,                       /* config options                  */
  0,                          /* flags                           */
}
mysql_declare_plugin_end;

Among others, the structure lists an init (daemon_example_plugin_init) and a deinit (daemon_example_plugin_deinit) function of the plugin. The init function is called when the plugin is loaded into the server during startup or using INSTALL PLUGIN.

King Brian’s init function is below. It opens a log file and starts a heartbeat thread to fill the log.


static int daemon_example_plugin_init(void *p) {
  DBUG_ENTER("daemon_example_plugin_init");
  
  struct mysql_heartbeat_context *con;
  pthread_attr_t attr;          /* Thread attributes */
  char heartbeat_filename[FN_REFLEN];
  char buffer[HEART_STRING_BUFFER];
  time_t result= time(NULL);
  struct tm tm_tmp;

  struct st_plugin_int *plugin= (struct st_plugin_int *)p;
 
  con= (struct mysql_heartbeat_context *)
    my_malloc(sizeof(struct mysql_heartbeat_context), MYF(0)); 

  fn_format(heartbeat_filename, "mysql-heartbeat", "", ".log",
            MY_REPLACE_EXT | MY_UNPACK_FILENAME);
  unlink(heartbeat_filename);
  con->heartbeat_file= my_open(heartbeat_filename, O_CREAT|O_RDWR, MYF(0));

  /*
    No threads exist at this point in time, so this is thread safe.
  */
  localtime_r(&result, &tm_tmp);
  my_snprintf(buffer, sizeof(buffer),
              "Starting up at %02d%02d%02d %2d:%02d:%02d\n",
              tm_tmp.tm_year % 100, tm_tmp.tm_mon+1,
              tm_tmp.tm_mday, tm_tmp.tm_hour,
              tm_tmp.tm_min, tm_tmp.tm_sec);
  my_write(con->heartbeat_file, (uchar*) buffer, strlen(buffer), MYF(0));

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr,
                              PTHREAD_CREATE_JOINABLE);

  /* now create the thread */
  if (pthread_create(&con->heartbeat_thread, &attr, mysql_heartbeat,
                     (void *)con) != 0) {
    fprintf(stderr,"Could not create heartbeat thread!\n");
    exit(0);
  }
  plugin->data= (void *)con;

  DBUG_RETURN(0);
}

I have tried to highlight some functions and macros used: DEBUG_ENTER, my_malloc, my_open, my_snprintf, my_write, DBUG_RETURN. Obviously, the daemon plugin has to include some header files to get access to them. And, in daemon_example.cc you find the corresponding include statements.

DBUG_ENTER, DBUG_RETURN include/my_dbug.h
my_malloc include/my_sys.h (mysys/my_malloc.c)
my_snprintf sql/sql_plugin_services.h
my_write include/my_sys.h (mysys/my_write.c)


#include <my_global.h>
#include <sql_priv.h>
#include <stdlib.h>
#include <ctype.h>
#include <mysql_version.h>
#include <mysql/plugin.h>
#include <my_dir.h>
#include "my_pthread.h"                         // pthread_handler_t
#include "my_sys.h"                             // my_write, my_malloc
#include "m_string.h"                           // strlen
#include "sql_plugin.h"                         // st_plugin_in

Cool, plugin developers can use all of the servers’ code?!

Looking at the above list of included files one may come to the conclusion a plugin developer is allowed use any server code. But, then there is sql/sql_plugin_services.h, which is included by sql_plugin.h. Is this the formal public API for accessing server code. Walking in the dark with no manual or book guidance…

In sql_plugin_services.h a couple of services are defined. The daemon example plugin is using the my_snprintf_service.

static struct st_service_ref list_of_services[]=
{
  { "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler },
  { "thd_alloc_service",   VERSION_thd_alloc,   &thd_alloc_handler },
  { "thd_wait_service",    VERSION_thd_wait,    &thd_wait_handler },
  { "my_thread_scheduler_service",
    VERSION_my_thread_scheduler, &my_thread_scheduler_handler },
  { "my_plugin_log_service", VERSION_my_plugin_log, &my_plugin_log_handler },
  { "mysql_string_service",
    VERSION_mysql_string, &mysql_string_handler },
}

Not recalling a note about any services in the manual, the happy grepping through the code continues. An enlightening code comment can be found in sql/plugin.cc. Plugins are provided with "built-in functions".

  /* link the services in */
  for (i= 0; i < array_elements(list_of_services); i++)
  {
    if ((sym= dlsym(plugin_dl.handle, list_of_services[i].name)))
    {
      uint ver= (uint)(intptr)*(void**)sym;
      if (ver > list_of_services[i].version ||
        (ver >> 8) < (list_of_services[i].version >> 8))
      {
        char buf[MYSQL_ERRMSG_SIZE];
        my_snprintf(buf, sizeof(buf),
                    "service '%s' interface version mismatch",
                    list_of_services[i].name);
        report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, 0, buf);
        DBUG_RETURN(0);
      }
      *(void**)sym= list_of_services[i].service;
    }
  }

Internal APIs vs. public stable APIs for plugin developers

The official daemon example plugin is accessing server functionality in two ways: using services and direclty including whatever header is needed.

The key idea behind the service is probably the versioning. Plugin developers should be allowed to assume that version 1 of service A acts in a defined way, no matter what the MySQL server version is. On the contrary, my_write could change its interface in the next server version and break a plugin without prior notice. However, there seems to be no registry for servies, no directory and not much of an API for a plugin developer to request the use of selected services of a certain version. Thus, this kind of service is nice but at the end of the day…?

The do-whatever-you-like style

The daemon example plugin is using a mixture of internal server APIs and plugin services. The few, internal my_sys.h functions used are peanuts compared to the use of internal MySQL APIs by storage engine plugins. Note, that an API includes functions and data types. Given that, plugin developers don’t have much of a choice but to use internal APIs that may change at any time without prior notice.

If I was to write a plugin, I would bite the bullet and use any internal API, possibly, through a lightweight wrapper to minimize dependencies.

Happy hacking!

@Ulf_Wendel Follow me on Twitter

One Comment

Leave a Reply

Required fields are marked *.