Extending MySQL Capabilies with UDFs, Plugins and Components

Extending MySQL Capabilies with UDFs, Plugins and Components

MySQL offers three different approaches to extending the SQL capabilities with the default product you download and install. These are:

For the purposes of this post I will be using the current LTS version MySQL 8.4 . You can find all code at https://github.com/ronaldbradford/mysql-tutorial/tree/main/uuidv

The UUIDv() Function

Lets say you want to create a UUID function, but support the different versions available, specifically:

Argument Result
1 time-based (Gregorian ticks + random node)
4 random
6 reordered time-based (sortable)
7 Unix-ms time-ordered

You have three options with MySQL, and it’s possible to use all three for comparison.

It’s not possible to use the same function name and run on three concurrently, so for this post I including the type as a suffix. IRL you would choose one type of implementation and the name could just be UUIDv(n).

  • uuidv_udf()
  • uuidv_plugin()
  • uuidv_component()

Background

Loadable Functions (UDFs) — introduced in the MySQL 4.x era early 2000s, the oldest of the three. You write xxx_init/xxx/xxx_deinit C functions, compile to a shared library, and load with CREATE FUNCTION ... SONAME. Pro: simple, minimal boilerplate, and ubiquitous — works on essentially every MySQL version in existence, so it’s the most portable. Con: a flat C ABI with no access to internal server services, weak type safety, and the API is effectively frozen — Oracle considers it legacy and steers new work elsewhere.

Server Plugin API — introduced in MySQL 5.1 (2008). This is the framework behind storage engines, authentication plugins, audit plugins, full-text parsers, etc., loaded via INSTALL PLUGIN. Pro: gives deep hooks into well-defined server subsystems, which is the only way to implement things like a new auth method or audit log. Con: each plugin type has its own rigid descriptor structure, plugins are tightly coupled to server internals (so they break across versions), and it’s overkill if all you want is a SQL-callable function.

Components — introduced in MySQL 8.0 (2018) as the modern, forward-looking architecture meant to eventually supersede the plugin API. Loaded with INSTALL COMPONENT, they interact through versioned services (like udf_registration, which is how your component_uuidv registers a function without CREATE FUNCTION). Pro: clean service-based design with proper versioning and loose coupling, components can depend on and provide services to each other, and it’s where Oracle is investing. Con: more upfront boilerplate, requires MySQL 8.0+, and the service catalog (while growing) doesn’t yet expose every internal capability the old plugin API reaches.

Code

The actual C code for the real code in the three capabilities is the same. See /uuidv/uuid_gen.h . What changes is the scaffolding and registration, with /uuidv/udf/uuidv_udf.cc , /uuidv/plugin/uuidv_plugin.cc and uuidv/component/component_uuidv.cc . On the surface for a simple functions, it would appear straighforward to migrate from UDF to Plugin or Component.

Another small difference is the declaration of parameters.

is_null and error parameter types: char * vs unsigned char *

When implementing a MySQL loadable function, the type of the is_null and error output parameters depends on which extension mechanism you use — and getting it wrong will produce a compiler warning or error.

The difference

Implementation Parameter type Reason
Legacy UDF (CREATE FUNCTION) char * matches Udf_func_string in mysql/udf_registration_types.h
Daemon server plugin (hybrid UDF entry points) char * same legacy UDF contract
Component (via udf_registration service) unsigned char * matches the component service’s own function pointer type

Why they differ

The legacy UDF API and the component service API were designed independently. mysql/udf_registration_types.h defines Udf_func_string with char *:

typedef char *(*Udf_func_string)(UDF_INIT *, UDF_ARGS *, char *,
                                  unsigned long *, char *, char *);

The component udf_registration service defines its equivalent with
unsigned char *:

typedef char *(*Udf_func_string)(UDF_INIT *, UDF_ARGS *, char *,
                                  unsigned long *, unsigned char *,
                                  unsigned char *);

Each implementation must match its own API’s function pointer signature exactly. A mismatch will produce a type error at the point where the function pointer is passed to the registration call.

Can they be unified?

Not without a cast. Functionally there is no difference — both types are single-byte values used as boolean flags (0 = not null / no error, 1 = null / error). But the compiler enforces the type at the registration call site, so each file must use the type its API expects.

The practical rule: if you copy the body of a UDF function between a legacy UDF and a component implementation, remember to change the is_null and error parameter types accordingly.

User Defined Function (UDF) Example

mysql> CREATE FUNCTION uuidv_udf RETURNS STRING SONAME 'uuidv_udf.so';

mysql> SELECT CAST(uuidv_udf(4) AS CHAR);
+--------------------------------------+
| CAST(uuidv_udf(4) AS CHAR)           |
+--------------------------------------+
| 414215e9-97f4-498a-a023-98addb85567d |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CAST(uuidv_udf(7) AS CHAR);
+--------------------------------------+
| CAST(uuidv_udf(7) AS CHAR)           |
+--------------------------------------+
| 019ee11d-5179-7776-a87e-366ab17bba99 |
+--------------------------------------+
1 row in set (0.00 sec)

MySQL Plugin Example

mysql> INSTALL PLUGIN uuidv_plugin SONAME 'uuidv_plugin.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION uuidv_plugin RETURNS STRING SONAME 'uuidv_plugin.so';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT CAST(uuidv_plugin(4) AS CHAR);
+--------------------------------------+
| CAST(uuidv_plugin(4) AS CHAR)        |
+--------------------------------------+
| 7b117e24-e011-4b21-a97c-b4c709ce4907 |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CAST(uuidv_plugin(7) AS CHAR);
+--------------------------------------+
| CAST(uuidv_plugin(7) AS CHAR)        |
+--------------------------------------+
| 019ee11d-e061-72ff-9f15-ffb856c4705c |
+--------------------------------------+
1 row in set (0.00 sec)

MySQL Component Example

Installing a component does not require you to register the function.

mysql> INSTALL COMPONENT 'file://component_uuidv';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT CAST(uuidv_component(4) AS CHAR);
+--------------------------------------+
| CAST(uuidv_component(4) AS CHAR)     |
+--------------------------------------+
| 05255d2e-4903-454b-9909-64e40cb5c75b |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CAST(uuidv_component(7) AS CHAR);
+--------------------------------------+
| CAST(uuidv_component(7) AS CHAR)     |
+--------------------------------------+
| 019ee128-e170-7bb4-a0da-7e05b9261f1a |
+--------------------------------------+
1 row in set (0.00 sec)

Leveraging plugin and component improvements

In my next post I will highlight some of the additional benefits that plugins and components offer including variables, status variables, and tables.

Tagged with: MySQL

Producing One-Sample Z-Test statistics with SQL

The one-sample Z-test determines whether a sample mean differs significantly from a known population mean when the population standard deviation is also known. It is the appropriate test when the population parameters are established — quality control benchmarks, national averages, long-run process measurements — and you want to evaluate whether a new sample is consistent with them.

Switching to JSON Error Logging in MySQL

You no longer need to manually parse the MySQL Error log via scripting and RegEx pattern matching. Using the component_log_sink_json component you can obtain JSON error logging for easier parsing.

Installing MySQL 9.7 LTS Community Edition on CentOS

Historically installing MySQL on a RedHat Compatible Linux server was as simple as yum install mysql-server. Today’s MySQL Oracle Linux, Red Hat Enterprise Linux, CentOS, and Fedora 9.7 instructions are not accurate mixing in 8.