MySQLのユーザー定義関数(UDF)

MySQLのユーザー定義関数(UDF)は大昔に作った記憶があるけど、最近作ってなかったので試しに作ってみたメモ。

関数の中身はなんでも良かったんだけど、フィボナッチ数を求める fib()を作ってみた。

ちゃんと知りたい場合は、マニュアルとかサンプルプログラムを見ましょう。

% gcc -shared -I /usr/local/mysql/include fib.c -o fib.so
% sudo cp fib.so /usr/local/mysql/lib/plugin/
% /usr/local/mysql/bin/mysql -uroot
mysql> create function fib returns integer soname 'fib.so';
Query OK, 0 rows affected (0.02 sec)
mysql> select n, fib(n) from (values row(1),row(2),row(3),row(4),row(5),row(6),row(7),row(8),row(9),row(10)) as n(n);
+----+--------+
| n  | fib(n) |
+----+--------+
|  1 |      1 |
|  2 |      1 |
|  3 |      2 |
|  4 |      3 |
|  5 |      5 |
|  6 |      8 |
|  7 |     13 |
|  8 |     21 |
|  9 |     34 |
| 10 |     55 |
+----+--------+
10 rows in set (0.01 sec)
// UDFの詳細はマニュアルとサンプルプログラムを参照
// https://dev.mysql.com/doc/refman/8.0/en/adding-functions.html
// https://github.com/mysql/mysql-server/blob/8.0/sql/udf_example.cc
//
// コンパイル方法
// gcc -shared -I /usr/local/mysql/include fib.c -o fib.so
// 作成された so ファイルを plugin-dir ディレクトリに置く
// 使用時:
// create function fib returns integer soname 'fib.so';
// select fib(10);
// 関数が不要になった場合は破棄できる:
// drop function fib;

#include <string.h>
#include "mysql.h"
#include "mysql/udf_registration_types.h"

// 初期化
// 関数を使用するクエリの実行前に呼ばれる
// initid  : 戻り値
// args    : 引数
// message : エラーメッセージ用バッファ。MYSQL_ERRMSG_SIZE バイト(8.0 では 512)
// 戻り値は、成功時には false、エラー時には true (変なの)
// 関数の戻り値は CREATE FUNCTION で指定される
bool fib_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
  initid->maybe_null = true;   // 戻り値がNULLになり得る場合はtrue
  initid->decimals = 0;        // 戻り値が浮動小数点数の場合に小数点以下の桁数を指定
  initid->max_length = 21;     // 戻り値の最大文字数/最大桁数
  initid->ptr = NULL;          // この関数用に確保したメモリのポインタ等
  if (args->arg_count != 1) {  // 引数が1個じゃなければエラー
    strcpy(message, "fib() requires one argument");
    return true;
  }
  if (args->maybe_null[0]) { // 引数にNULLは許容しない
    strcpy(message, "fib() requires not NULL");
    return true;
  }
  if (args->arg_type[0] != INT_RESULT) { // 引数が整数でなければエラー
    strcpy(message, "fib() requires an integer argument");
    return true;
  }
  // 上のようにチェックする他に MySQL に自動変換させることもできる
  //args->arg_type[0] = INT_RESULT;

  // おまけ
  // args->attribues[0] と args->attribute_lengths[0] で 0番目の引数に与えた名前がわかる
  // fib(123) の場合は "123", fib(hoge) の場合は "hoge"

  return false;
}

// 終了
// initid->ptr にメモリを確保していた場合はここで解放する
void fib_deinit(UDF_INIT *initid)
{
}

// 実行
// initid  : ptr を使う場合以外は使用しない
// args    : 引数
// is_null : 戻り値がNULLになる場合 true を設定
// error   : エラー時に true を設定。ひとつのクエリ内ではそれ以降は評価されずにNULLが返る
long long fib(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
  long long n;
  n = *((long long *)args->args[0]);

  long long i, a, b, c;
  for (i = 0, b = 1, c = 0; i < n; i++) {
    a = b;
    b = c;
    c = a + b;
    if (c <= 0) {  // オーバーフロー時は NULL を返す
      *is_null = true;
      return 0;
    }
  }
  return c;
}

続く