Date Tags mysql / udf

UDF_LUA

You are tired of writing your UDFs for MySQL in C or never wanted to write them in C at all ? How about writing them in lua ?

LUA is - easy to learn - easy to embed - easy to use

and not to forget lua has extensions for everything: - LuaSocket - how about some HTTP requests ? - LuaSQL - connecting back to the MySQL Server ? - ...

UDF_Lua is registered in the MySQL Forge.

functions

As example we want to read the real startup time of the errorlog.

-- we are run with the permissions of the mysqld
--
-- let's try the read the "ready for connections" from the errorlog and look
-- for the last [Note]:
--
-- 061124 17:28:39 [Note] /usr/sbin/mysqld-max: ready for connections.

local f = assert(io.open(params[1], "r"))
local readysince = nil

while true do
        local line = f:read()

        if not line then break end

        local match = string.match(line, "^([0-9]+ [0-9:]+) %[Note%]")

        if match then
                readysince = match
        end
end
f:close()

return readysince

Let's execute it:

> select lua_file(".../ready-since.lua", "/var/lib/mysql/jan-dev.err") as ready_since;
+-----------------+
| ready_since     |
+-----------------+
| 061125 18:10:48 |
+-----------------+

Yeah, that matches the string from the errorlog.

Without a script-file

If shorter script you can also just pass the lua-code around. As usual a string is returned and the parameters are available via the params table.

> select lua("return params[1]", 1) as echo;
+------+
| echo |
+------+
| 1    |
+------+
1 row in set (0.02 sec)

Aggregation

For Aggregation functions like SUM() you have to provide a function which is called for each row (mysql_udf_aggr) and one which returns the final result (mysql_udf_result).

_G["mysql_udf_aggr"] = function (params)
        if nil == rows then
                rows = 0
        end

        rows = rows + params[1]
end
_G["mysql_udf_result"] = function (params)
        return rows
end

Running it against the world-database:

root@localhost [world]> select lua_aggr_file("/home/jan/sum.lua", population) from City;
+------------------------------------------------+
| lua_aggr_file("/home/jan/sum.lua", population) |
+------------------------------------------------+
| 1429559884                                     |
+------------------------------------------------+
1 row in set (0.02 sec)

root@localhost [world]> select sum(population) from City;
+-----------------+
| sum(population) |
+-----------------+
|      1429559884 |
+-----------------+
1 row in set (0.01 sec)

Installation

Requirements: - lua 5.1

... and the usual UDF requirements

$ wget <a href="/downloads/mysql/UDF-mysql_udf_lua-0.9.2.tar.gz">http://jan.kneschke.de/downloads/mysql/UDF-mysql_udf_lua-0.9.2.tar.gz</a>
$ gzip -cd UDF-mysql_udf_lua-0.9.2.tar.gz | tar xf -
$ cd UDF-mysql_udf_lua-0.9.2
$ ./configure
$ make
## copy to a directory which is checked by dlopen()
# cp ./.libs/mysql_udf_lua.so /usr/lib/
$ mysql
mysql> create function lua returns string soname 'mysql_udf_lua.so';
mysql> create function lua_file returns string soname 'mysql_udf_lua.so';
mysql> create function lua_aggr_file returns string soname 'mysql_udf_lua.so';

Thanks to Hartmuts CodeGen_MySQL_UDF the UDF is packaged with automake/autoconf.

parameter handling

The parameters of the SELECT call are passed as a lua-table to the functions. The indexing for this table is starting a 1 as it is expected by lua-devs. The type between MySQL and LUA are mapped as expected

return value

Whatever you return is either nil or converted into a string. In case of a execution error NULL will be return and the error will be logged to the mysqld-error-log.

My MySQL Server sends Mail !

For the UDF talk in a few hours I wanted to have a nice and hopefully useless example. It should show how the internals work and what has to be setup even to those who never have used a C-Compiler.

I chose to implement a function to send mail directly from the database which I hope will never be used. Really. It is not a good idea to move application code into the database.

#include <mysql.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

my_bool mail_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
        initid->maybe_null = 0;

        return 0;
}

long long mail(UDF_INIT *initid, UDF_ARGS *args,
               char *is_null, char *error) {
        char *prg;
        FILE *p;
        int i;

        if (args->arg_count != 2) {
                strcpy(error, "MAIL() needs receipient and body");
                return 1;
        }

        if (args->arg_type[0] != STRING_RESULT ||
            args->arg_type[1] != STRING_RESULT) {
                strcpy(error, "MAIL() needs receipient and body as string");
                return 1;
        }

        for (i = 0; i < args->lengths[0]; i++) {
                char c = args->args[0][i];

                if (!((c >= 'a' && c <= 'z') ||
                      (c >= 'A' && c <= 'Z') ||
                      (c == '@') ||
                      (c == '.') ||
                      (c >= '0' && c <= '9') ||
                      (c == '-' ) ||
                      (c == '_' ))) {
                        strcpy(error, "the receipient contains a illegal char");

                        return -1;
                }
        }

        prg = malloc(strlen("/usr/lib/sendmail ") + args->lengths[0] + 1);
        strcpy(prg, "/usr/lib/sendmail ");
        strcat(prg, args->args[0]);


        p = popen(prg, "w");

        free(prg);

        if (NULL == p) {
                strcpy(error, "opening pipe failed");
                return -1;
        }


        fwrite(args->args[1], args->lengths[1], 1, p);
        fclose(p);


        *is_null = 0;
        *error = 0;

        return 0;
}

That's the code which has to be compiled with:

$ gcc -I /usr/local/mysql/include/ -shared -o udf_mail.so mail.c -Wall
$ cp udf_mail.so /usr/lib/

at mysql side:

mysql> create function mail returns integer soname 'udf_mail.so';
mysql> select mail('jan@kneschke.de',
           'Subject: UDF Mail\r\n\r\nYou got mail');

MySQL and memcached

Some time ago I wrote a Proof of Concept for a Memcache UDF based on Sean's libmemcache:

It is not meant for production use as libmemcache uses exit(), assert() and other "shutdown now" calls which zap the whole server if they are hit.


Comments

Enable javascript to load comments.