Yet another OSS DB:Firebird(5)

内部関数が足りない? それならUDFだ!

Firebird日本ユーザー会
アナハイムテクノロジー
はやしつとむ
2009/6/9
Firebird日本ユーザー会の林です。今回は自分仕様の関数を作れる“ユーザー定義関数”の仕組みを解説します。

内部関数で物足りないあなたに

 第4回「Firebirdの標準関数を極める」は、バージョン2.1で大きく変更されたFirebirdの内部関数について解説しました。かなりの数の内部関数が取り込まれて充実したFirebirdですが、もちろんこれだけでは不足する場合があります。そのため、Firebirdではユーザー定義関数(UDF)という仕組みが用意されていて、誰でも簡単に関数を作成して追加することができるようになっています。それでは、詳しく見ていきましょう。

UDFの仕組み

 UDFをFirebirdで利用するための仕組みとして、DECLARE EXTERNAL FUNCTION文が用意されています。入手・作成したUDFは、このステートメントで各データベースへ登録することで、内部関数と同じように各種のSQL文で利用することができるようになります。

【DECLARE EXTERNAL FUNCTION文のSyntax】

DECLARE EXTERNAL FUNCTION ローカル関数名
  [<タイプ定義> [, <タイプ定義> ...]]
  RETURNS {<リターンタイプ定義> | PARAMETER パラメータ位置} [FREE_IT]
     ENTRY_POINT '関数名' MODULE_NAME 'DLL/SOファイル名'
     
 <タイプ定義>          ::= sqltype [BY DESCRIPTOR] | CSTRING(文字列長)
 <リターンタイプ定義>  ::= sqltype [BY {DESCRIPTOR|VALUE}] | CSTRING(文字列長) 
  • ローカル関数名は、関数名のエイリアスなので自由に(Firebirdのオブジェクト命名規則の範囲内で)付けてよい
  • CSTRING型の場合は、長さを1〜(32767 ÷ 1文字の最大bytes長)で指定します。1文字の最大bytes長は各キャラクタセットで違いますが、SJIS_0208なら2bytes、UTF8なら4bytesです。
●UDF利用の流れ

標準UDFのコード

 それではまず、標準UDFに用意された簡単な関数のコードを追っていくところから始めたいと思います。いまでは使われなくなったとはいえ、ソースコードはそのまま残っているので、これをサンプルにして学ぶのが近道です。

 以下に示すのは、Firebird-2.5.0.23247-Beta1.tar.bz2を展開して、src\extlibに含まれているib_udf.cppのソースコードの一部です。大変簡潔なので、UDF作成が手軽にできることをお分かりいただけると思います。

ib_udf.cpp(284)
double EXPORT IB_UDF_srand()
     {
     srand((unsigned) time(NULL));
     return ((float) rand() / (float) RAND_MAX);
     }

 一見して分かるとおり、IB_UDF_srand()関数は引数を取らず、戻り値がdouble型となっています。

 1行目で、srand()関数によって乱数系列を初期化しています。RAND_MAXはstdlib.hで定義されている定数値で、rand()関数が0〜RAND_MAXの範囲の乱数を返すため、RAND_MAXで割ることで0-1の範囲の乱数を返すということになります。

 問題は、IB_UDF_srand()関数の呼び出しを行うたびに、srand()関数が呼ばれることにあります。WindowsのようにOSのタイマの精度が低い場合、連続した関数呼び出し中でtime()関数が同じ値を返してしまい、結果としてrand()関数が同じ値を返すことになってしまいます。

 そこで、Firebird2.1で標準UDFのほとんどを内部関数化した際に、Posix版では/dev/urandomデバイスを利用し、Windows版ではCryptGenRandom APIを利用するように書き換えられました。そもそもCの標準関数であるrand()は十分な乱数特性がないので、現在ではより高度なアルゴリズムで乱数を生成するべきだという認識が広がっています。

guid.cpp(41)
void GenerateRandomBytes(void* buffer, size_t size)
{
  HCRYPTPROV hProv;
	 // Acquire crypto-provider context handle.
  if (! CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 
                                                   CRYPT_VERIFYCONTEXT))
  {
      if (GetLastError() == NTE_BAD_KEYSET)
      {
          // A common cause of this error is that 
          // the key container does not exist.
          // To create a key container, call CryptAcquireContext
          // using the CRYPT_NEWKEYSET flag.
          if (! CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL,
              CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET))
          {
              Firebird::system_call_failed::raise("CryptAcquireContext");
          }
      }
      else
      {
          Firebird::system_call_failed::raise("CryptAcquireContext");
      }
  }
    if (! CryptGenRandom(hProv, size, static_cast<UCHAR*>(buffer)))
  {
     Firebird::system_call_failed::raise("CryptGenRandom");
  }
  CryptReleaseContext(hProv, 0);
}

1/4 次のページへ

Index
内部関数が足りない? それならUDFだ!
→ Page 1
内部関数で物足りないあなたに
UDFの仕組み
標準UDFのコード

Page 2
引数を取らないUDF
数値型の引数を取るUDF

Page 3
UDFに文字を渡す
UDFに文字列を渡す

Page 4
最後に
Yet another OSS DB:Firebird


Database Expert フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Database Expert 記事ランキング

本日月間