第13章 MySQL の拡張

目次

13.1. MySQL の内部情報
13.1.1. MySQL のスレッド
13.1.2. MySQL テストスイート
13.2. MySQL への新しい関数の追加
13.2.1. CREATE FUNCTION/DROP FUNCTION の構文
13.2.2. 新しいユーザ定義関数の追加
13.2.3. 新しいネイティブ関数の追加
13.3. MySQL への新しいプロシージャの追加
13.3.1. プロシージャの分析
13.3.2. プロシージャの作成

13.1. MySQL の内部情報

この章では、MySQL コードを扱うときに知る必要がある事項について説明します。MySQL の開発に貢献したい、開発中の非公式バージョンのコードにアクセスしたい、あるいは開発過程に興味がある、という場合は、項2.3.4. 「開発ソースツリーからのインストール」 の指示を参照してください。MySQL の内部情報に興味がある場合は、当社の internals メーリングリストに加入してください。このメーリングリストはそれほど投稿数は多くありません。加入方法の詳細については、項1.7.1.1. 「MySQL メーリングリスト」 を参照してください。MySQL AB の開発者は全員 internals リストに加入しており、MySQL コードに取り組んでる人々をサポートします。コードについて質問したり、MySQL プロジェクトに提供したいパッチを送信する場合には、このリストを遠慮なくご使用ください。

13.1.1. MySQL のスレッド

MySQL サーバが作成するスレッドを以下に示します。

  • TCP/IP 接続スレッドは、すべての接続要求を受け取り、新しく専用のスレッドを作成して、接続ごとの認証および SQL クエリの処理を行う。

  • Windows NT では、名前付きパイプハンドラスレッドが、名前付きパイプ接続要求に対して TCP/IP 接続スレッドと同じ処理を実行する。

  • シグナルスレッドは、すべてのシグナルを処理する。また、通常はアラームを処理して process_alarm() を呼び出し、長時間アイドル状態が続いている接続をタイムアウトさせる。

  • mysqld-DUSE_ALARM_THREAD を指定してコンパイルされている場合、アラーム処理専用スレッドが作成される。このスレッドは、sigwait() で問題が発生しているシステムか、または開発者が専用のシグナル処理スレッドを使用せずに thr_alarm() コードをアプリケーションで使用したい場合にのみ使用する。

  • --flush_time=# オプションを使用する場合、専用スレッドが作成され、指定された間隔ですべてのテーブルをフラッシュする。

  • すべての接続はそれ自身のスレッドを持つ。

  • INSERT DELAYED を使用されるすべてのテーブルはそれ自身のスレッドを持つ。

  • --master-host オプションを使用した場合、スレーブレプリケーションスレッドが起動し、マスタの更新を読み込み、適用する。

mysqladmin processlist は、接続スレッド、INSERT DELAYED スレッド、およびレプリケーションスレッドのみを表示します。

13.1.2. MySQL テストスイート

最近までは、全範囲を対象とする当社のテストスイートは独自に開発した顧客データを使用しており、そのために公開されていませんでした。テストプロセスの中で唯一公開されていたのが、sql-bench ディレクトリにある Perl DBI/DBD ベンチマークの crash-me テスト、および tests ディレクトリにあるその他の諸々のテストでした。標準化された公開テストスイートがなかったため、当社製品のユーザや開発者は、MySQL コードに対して回帰試験を実施することが困難でした。この問題を解決するために、バージョン 3.23.29 から、Unix のソースディストリビューションおよびバイナリディストリビューションに、新しく作成したテストシステムを追加しました。このテストは Unix で実行できます。また、サーバを Cygwin でコンパイルした場合は、Cygwin 環境の Windows 上で実行できます。ネイティブな Windows 環境では現在は動作しません。

現在のテストケースは MySQL 全体をテストするわけではありませんが、SQL を処理するコードにおける明白なバグや OS/ライブラリの問題は検出できるはずです。またレプリケーションのテストは徹底的に行っています。最終的には、コードを 100% カバーするようなテストを作成することを目標としています。そのためにテストスイートを拡充するためのみなさんからのご協力をお待ちしています。特に、お使いのシステムにとってクリティカルな機能を検査するテストをご提供していただければ、今後リリースされるすべての MySQL バージョンでお使いのアプリケーションの動作が保証されることになります。

13.1.2.1. MySQL テストスイートの実行

テストシステムは、テスト言語インタープリタ(mysqltest)、すべてのテストを実行するシェルスクリプト(mysql-test-run)、テスト専用言語で記述された実際のテストケース、および予想されるテスト結果から構成されます。ビルドしてからシステムでテストスイートを実行するには、ソースルートから make test または mysql-test/mysql-test-run と入力します。バイナリディストリビューションをインストールした場合は、cd でインストールルートディレクトリ(/usr/local/mysql など)に移動し、scripts/mysql-test-run を実行します。すべてのテストが正常終了する必要があります。正常終了しないケースがある場合、その原因を調べ、それが MySQL のバグであれば問題点を報告してください。 See 項13.1.2.3. 「MySQL テストスイートのバグの報告」

テストスイートを実行しようとするマシン上で mysqld が動作している場合、それが 9306 および 9307 のポートをどちらも使用していないのであれば、それを停止する必要はありません。どちらかのポートが使用されている場合は、mysql-test-run を編集してマスタポートまたはスレーブポート、あるいはその両方を、使用できるポートに変更する必要があります。

mysql-test/mysql-test-run test_name を使用して、個々のテストケースを 1 件ずつ実行できます。

あるテストが失敗した場合、--force オプションを指定して mysql-test-run を実行し、ほかにも失敗するテストがあるかどうかを調べる必要があります。

13.1.2.2. MySQL テストスイートの拡張

mysqltest 言語を使用して独自にテストケースを作成できます。残念ながら、その完全なマニュアルはまだ作成されていません。ただ、現在のテストケースを参考にすることができます。独自にテストケースを作成する際の注意点を以下に示します。

  • 作成したテストは mysql-test/t/*.test に置く。

  • テストケースは、; で終端されたステートメントで構成され、mysql コマンドラインクライアントへの入力と同様である。ステートメントは、それが内部コマンド(sleep など)として認識される場合を除いて、デフォルトでクエリとして MySQL サーバに送信される。

  • SELECTSHOWEXPLAIN など、結果セットを生成するすべてのクエリは、先に @/path/to/result/file を指定する必要がある。ファイルには予想される結果を記述する必要がある。結果ファイルを簡単に生成する方法としては、mysql-test ディレクトリから mysqltest -r < t/test-case-name.test を実行し、必要に応じて生成された結果ファイルを編集して予想される出力に変更する。この場合、表示されない文字を追加または削除しないように注意する必要がある。必ずテキストの変更または行の削除、あるいはその両方だけを実行するようにする。行を挿入する必要がある場合、必ずフィールドをハードタブで区切り、行の最後にハードタブを置くようにする。od -c を使用すると、編集中にテキストエディタで何も壊していないことを確認できる。mysqltest -r の出力を編集する必要があるのはバグが見つかった場合だけなので、そのような必要性が生じないことを祈っている。

  • テストスイートとの整合性を取るには、結果ファイルを mysql-test/r ディレクトリに置いて、名前を test_name.result にする必要がある。テストで複数の結果ファイルを生成する場合は、test_name.a.resulttest_name.b.result のように名前を付ける必要がある。

  • ステートメントがエラーを返す場合、そのステートメントの前に --error error-number を指定する。ここで、エラー番号には、',' で区切られたエラー番号のリストを指定できる。

  • レプリケーションのテストケースを作成している場合、テストファイルの先頭行に source include/master-slave.inc; と記述する必要がある。マスタとスレーブを切り替えるには、connection master; および connection slave; を使用する。もう一方の接続で何らかの処理を実行する必要がある場合、マスタには connection master1; を実行し、スレーブには connection slave1; を実行する。

  • ループの中で何らかの処理を実行する必要がある場合、以下のようなコードを作成する。

    let $1=1000;
    while ($1)
    {
     # do your queries here
     dec $1;
    }
    

  • クエリ間でスリープするには、sleep コマンドを使用する。秒数は小数点以下まで指定できるので、たとえば 1.3 秒スリープする場合は sleep 1.3; を実行する。

  • テストケースのためにオプションを追加してスレーブを実行するには、mysql-test/t/test_name-slave.opt にコマンドライン形式で記述する。マスタの場合は、mysql-test/t/test_name-master.opt に記述する。

  • テストスイートに質問がある場合、またはテストスイートに提供するテストケースがある場合、MySQL 内部情報のメーリングリストにメールを送信する。See 項1.7.1.1. 「MySQL メーリングリスト」。 このメーリングリストは添付ファイルを受け付けないので、関連するファイルは ftp://support.mysql.com/pub/mysql/Incoming/ にアップロードする必要がある。

13.1.2.3. MySQL テストスイートのバグの報告

お使いの MySQL バージョンがテストスイートを正常終了できない場合、以下の処理を実行する必要があります。

  • どこで異常が発生しているかをできるかぎり調査するまで、バグレポートを送信しないこと。バグレポートを送信する場合は、使用しているシステムおよび MySQL のバージョンに関する情報を収集するための mysqlbug スクリプトを使用する。 See 項1.7.1.3. 「バグまたは問題を報告する方法」

  • mysql-test-run の出力および mysql-test/r ディレクトリのすべての .reject ファイルの内容を必ず送信する。

  • テストスイートのテストが異常終了する場合、そのテストを単独で実行したときも異常終了するかどうかを調べる。

    cd mysql-test
    mysql-test-run --local test-name
    

    単独で実行しても異常終了する場合、--with-debug を指定して MySQL を実行し、--debug オプション付きで mysql-test-run を実行する。これでも異常終了する場合は、当社でトレースファイル var/tmp/master.trace を調べたいので、ftp://support.mysql.com/pub/mysql/secret にそのファイルをアップロードする。このとき、必ず使用しているシステムの詳細な説明、mysqld バイナリのバージョン、および mysqld バイナリのコンパイル方法を連絡する。

  • --force オプションを指定して mysql-test-run を実行し、ほかのテストが異常終了するかどうかも調べる。

  • 自分で MySQL をコンパイルした場合、マニュアルを参照して使用しているプラットフォーム上で MySQL をコンパイルする方法を調べる。できれば、当社がコンパイルしたバイナリを http://www.mysql.com/downloads/ からダウンロードして使用する。当社が提供する標準のバイナリはすべてテストスイートを正常終了するはずである。

  • Result length mismatch または Result content mismatch などのエラーが発生する場合、これはテストの出力と予想される出力が正確に一致しなかったことを意味する。この場合、MySQL のバグの可能性がある。あるいは、使用している mysqld のバージョンが特定の環境下で多少異なる結果を出力した可能性がある。

    異常終了したテスト結果は、結果ファイルと同じベース名に拡張子 .reject を付けた名前のファイルに出力される。テストケースを実行して異常終了した場合、これらの 2 つのファイルをパラメータとして diff を実行する必要がある。両者の違いがわからない場合、それぞれをパラメータとして od -c を実行し、そのサイズを調べる。

  • テストが全体的に異常終了する場合、mysql-test/var/log ディレクトリのログファイルを調べて、異常の原因に関するヒントを探す。

  • デバッグ機能を組み込んで MySQL をコンパイルしていた場合、--gdb または --debug、あるいはその両方のオプションを指定して mysql-test-run を実行することによってデバッグできる。 See 項E.1.2. 「トレースファイルの作成」

    デバッグ機能を組み込まずに MySQL をコンパイルしていた場合、デバッグ機能を組み込むようにコンパイルしてから再実行する必要がある。それには、--with-debug オプションを指定して configure を実行する。 See 項2.3. 「MySQL ソースディストリビューションのインストール」

13.2. MySQL への新しい関数の追加

MySQL に新しい関数を追加する方法は 2 つあります。

  • ユーザ定義関数(UDF)インタフェースを使用して関数を追加できる。ユーザ定義関数は、CREATE FUNCTION ステートメントおよび DROP FUNCTION ステートメントを使用して、動的に追加および削除できる。 See 項13.2.1. 「CREATE FUNCTION/DROP FUNCTION の構文」

  • ネイティブ(組み込み)MySQL 関数として関数を追加できる。ネイティブ関数は、コンパイルして mysqld サーバに組み込むことによって、永続的に使用できるようになる。

それぞれの方法に長所および短所があります。

  • ユーザ定義関数を作成する場合、サーバのほかにオブジェクトファイルをインストールする必要がある。関数をコンパイルしてサーバに組み込んだ場合はその必要はない。

  • UDF は、MySQL バイナリディストリビューションに追加できる。ネイティブ関数の場合、ソースディストリビューションを修正する必要がある。

  • MySQL ディストリビューションをアップグレードする場合、既存の UDF はそのまま使用できる。ネイティブ関数の場合、アップグレードするたびに修正作業を繰り返す必要がある。

どちらの方法を使用して新しい関数を追加する場合も、ABS()SOUNDEX() などのネイティブ関数と同じように使用できます。

13.2.1. CREATE FUNCTION/DROP FUNCTION の構文

CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|REAL|INTEGER}
       SONAME shared_library_name

DROP FUNCTION function_name

ユーザ定義関数(UDF)は、ABS()CONCAT() などの MySQL ネイティブ(組み込み)関数と同様に動作する新しい関数を追加して MySQL を拡張するための手段です。

AGGREGATE は、MySQL バージョン 3.23 で新しく追加されました。AGGREGATE 関数は SUMCOUNT() などの MySQL のネイティブグループ関数とまったく同じように動作します。

CREATE FUNCTION は、関数の名前、型、および共有ライブラリ名を mysql.func システムテーブルに保存します。関数を作成および削除するには、mysql データベースに対する INSERT 特権および DELETE 特権を持っている必要があります。

mysqld を起動する際に --skip-grant-tables オプションが指定されていなかった場合、すべてのアクティブな関数はサーバが起動するたびに再ロードされます。オプションが指定されていた場合、UDF の初期化は行われず、それを使用することはできません(アクティブな関数とは CREATE FUNCTION でロードされたが DROP FUNCTION による削除は行われていない関数)。

ユーザ定義関数の作成手順については、項13.2. 「MySQL への新しい関数の追加」 を参照してください。UDF のメカニズムが機能するには、関数を C または C++ で記述する必要があります。また、オペレーティングシステムでダイナミックローディングがサポートされていること、および mysqld を(静的ではなく)動的にコンパイルしている必要があります。

注意: AGGREGATE を実行するには、type カラムを含む mysql.func テーブルが必要です。このテーブルがない場合、スクリプト mysql_fix_privilege_tables を実行して、このテーブルを作成する必要があります。

13.2.2. 新しいユーザ定義関数の追加

UDF メカニズムが機能するには、関数を C または C++ で記述し、オペレーティングシステムでダイナミックローディングがサポートされている必要があります。MySQL ソースディストリビューションには、5 つの関数を定義する sql/udf_example.cc ファイルが含まれています。UDF の呼び出し例として、このファイルを参考にしてください。mysqld が UDF 関数を使用できるようにするには、--with-mysqld-ldflags=-rdynamic オプションを指定して MySQL を設定する必要があります。その理由は、多くのプラットフォーム(Linux を含む)では、静的リンクされたプログラムから(dlopen() を使用して)ダイナミックライブラリをロードできます。これは --with-mysqld-ldflags=-all-static を使用している場合でも実行できます。しかし、mysqld からシンボルにアクセスする必要がある UDF を使用する場合(default_charset_info を使用する sql/udf_example.ccmetaphone の例を参照)、プログラムを -rdynamic を指定してリンクする必要があるからです(man dlopen 参照)。

プリコンパイルされたバージョンのサーバを使用している場合は、MySQL-Max を使用します。MySQL-Max はダイナミックローディングをサポートしています。

SQL ステートメントで使用する関数には、それぞれ対応する C(または C++)関数を定義する必要があります。以下の説明では、``xxx'' をサンプルの関数名として使用します。SQL と C/C++ の区別をするために、XXX()(大文字)は SQL 関数呼び出しを、xxx()(小文字)は C/C++ 関数呼び出しを、それぞれ表すものとします。

XXX() のインタフェースを実装するために作成する C/C++ 関数を以下に示します。

  • xxx()(必須)

    関数のメイン。ここで関数の戻り値を計算する。SQL の型と C/C++ 関数の戻り値の型の対応を以下に示す。

    SQL の型C/C++ の型
    STRINGchar *
    INTEGERlong long
    REALdouble
  • xxx_init()(省略可能)

    xxx() の初期化関数。以下の処理を実行できる。

    • XXX() に渡す引数の数を確認する。

    • 引数が要求している型であることを確認する。または、関数が呼び出されたときに、引数を強制的に要求している型に変換するように MySQL に指示する。

    • 関数で必要とするメモリを割り当てる。

    • 結果の最大長を指定する。

    • 最大の小数点以下桁数(REAL 関数で使用)を指定する。

    • 結果として NULL を返すことができるかどうかを指定する。

  • xxx_deinit()(省略可能)

    xxx() の後処理関数。初期化関数が割り当てたメモリを解放する必要がある。

SQL ステートメントが XXX() を呼び出すと、MySQL は初期化関数 xxx_init() を呼び出して、引数の確認やメモリ割り当てなどの必要なセットアップを実行させます。xxx_init() がエラーを返した場合、SQL ステートメントの処理はエラーメッセージを出力して中断され、関数のメインと後処理関数の呼び出しは行われません。正常終了した場合、関数 xxx() がレコードごとに 1 回呼び出されます。すべてのレコードの処理が終了したら、後処理関数 xxx_deinit() が呼び出され、必要なクリーンアップ処理を実行します。

集計関数(SUM() など)の場合は、以下の関数も用意する必要があります。

  • xxx_reset()(必須)

    合計値をリセットし、引数を新しいグループの初期値として使用する。

  • xxx_add()(必須)

    引数を既存の合計値に加算する。

集計 UDF を使用する場合、MySQL は以下のように動作します。

  1. xxx_init() を呼び出して集計関数に結果を保存するためのメモリを割り当てる。

  2. GROUP BY 式に従ってテーブルを並べ替える。

  3. 新しいグループの先頭のレコードに対して、xxx_reset() 関数を呼び出す。

  4. 同じグループに属する新しいレコードごとに、xxx_add() 関数を呼び出す。

  5. グループが変わった場合、または最後のレコードの処理が終わった場合、xxx() を呼び出して集計値を取得する。

  6. すべてのレコードの処理が終了するまで 3 から 5 の手順を繰り返す。

  7. xxx_deinit() を呼び出して、UDF に割り当てられていたメモリを解放する。

上記の関数はすべて(関数だけでなく、初期化関数および後処理関数も)スレッドセーフで作成する必要があります。これは、変化する可能性があるグローバル変数やスタティック変数にメモリを割り当てることはできないことを意味します。メモリが必要な場合は、xxx_init() で割り当て、xxx_deinit() で解放する必要があります。

13.2.2.1. 単純関数の場合の UDF の呼び出し手順

関数は以下に示すように宣言する必要があります。注意: 戻り値の型およびパラメータは、CREATE FUNCTION ステートメントで SQL 関数 XXX() の戻り値を STRINGINTEGER、または REAL のどの型で返すと宣言するかによって異なります。

STRING 関数の場合は以下のように宣言します。

char *xxx(UDF_INIT *initid, UDF_ARGS *args,
          char *result, unsigned long *length,
          char *is_null, char *error);

INTEGER 関数の場合は以下のように宣言します。

long long xxx(UDF_INIT *initid, UDF_ARGS *args,
              char *is_null, char *error);

REAL 関数の場合は以下のように宣言します。

double xxx(UDF_INIT *initid, UDF_ARGS *args,
              char *is_null, char *error);

初期化関数および終了関数は以下のように宣言します。

my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);

void xxx_deinit(UDF_INIT *initid);

initid パラメータは、上記の 3 つの関数すべてに渡されます。UDF_INIT 構造体を参照しており、関数間のデータの受け渡しに使用されます。UDF_INIT 構造体のメンバを以下に示します。初期化関数では、必要に応じてメンバの値を変更します(メンバのデフォルト値を使用する場合はそのままにしておきます)。

  • my_bool maybe_null

    xxx_init() は、xxx()NULL を返す可能性がある場合は maybe_null1 に設定する必要がある。maybe_null と宣言されている引数が存在する場合は、デフォルト値は 1 である。

  • unsigned int decimals

    小数点以下桁数。デフォルト値は、関数に渡される引数の最大の小数点以下桁数(たとえば、1.341.345、および 1.3 を渡された場合、1.345 の小数点以下桁数が 3 で一番大きいので、デフォルト値は 3 になる)。

  • unsigned int max_length

    戻り値の文字列表現の最大長。関数の戻り値の型によってデフォルト値は異なる。文字列関数の場合、デフォルトは最長の引数の長さ。整数関数の場合、デフォルトは 21 桁。実数関数の場合、デフォルトは 13 に initid->decimals で示される小数点以下桁数を加算した値(数値関数の場合、この長さには符号や小数点が含まれる)。

    BLOB 型の値を返す場合、このメンバの値を 65K または 16M に設定できる。このメモリは割り当てられないが、一時的にデータを格納する必要が生じた場合、どのカラム型を使用するかを判断するために使用できる。

  • char *ptr

    関数が独自の用途に使用できるポインタ。たとえば、関数は initid->ptr を使用して、割り当てられたメモリを関数間で受け渡すことができる。xxx_init() では、以下のようにメモリを割り当て、その値をこのポインタに代入する。

    initid->ptr = allocated_memory;
    

    xxx() および xxx_deinit() は、initid->ptr を参照してメモリを使用したり、解放します。

13.2.2.2. 集計関数の場合の UDF の呼び出し手順

ここでは、集計 UDF 関数を作成する際に定義する必要があるさまざまな関数について説明します。

注意: 以下に示す関数は MySQL 4.1.1 では必要ありません。また使用されることもありません。MySQL 4.0 および MySQL 4.1.1 の両方で動作するようなコードを作成する場合にこれらの関数を定義します。

char *xxx_reset(UDF_INIT *initid, UDF_ARGS *args,
                char *is_null, char *error);

この関数は、MySQL が新しいグループの最初の行を見つけたときに呼び出されます。この関数では、内部の集計変数をリセットし、渡された引数をグループの最初の引数として設定する必要があります。

多くの場合、この処理はすべての変数をリセットすることで内部的に実装します(たとえば、xxx_clear() を呼び出してから xxx_add() を呼び出します)。

次の関数は、MySQL 4.1.1 以降でのみ必要です。

char *xxx_clear(UDF_INIT *initid, char *is_null, char *error);

この関数は、MySQL が集計結果をリセットする必要があるときに呼び出されます。この関数は新しいグループが始まるたびに呼び出されますが、一致するレコードがなかったクエリの値をリセットするために呼び出される場合もあります。is_null は、xxx_clear() を呼び出す前に CHAR(0) を参照するように設定されます。

何らかの異常が発生した場合、error の参照先に 1 バイトを保存できます。

char *xxx_add(UDF_INIT *initid, UDF_ARGS *args,
              char *is_null, char *error);

この関数は、同じグループに属する 2 番目以降のすべてのレコードについて呼び出されます。この関数では、UDF_ARGS の値を内部集計変数に加算する必要があります。

xxx() 関数は、単純 UDF 関数を定義するときと同じように宣言する必要があります。 See 項13.2.2.1. 「単純関数の場合の UDF の呼び出し手順」

この関数は、グループのすべてのレコードが処理されたときに呼び出されます。通常はこの関数で args 変数にアクセスする必要はまったくなく、内部集計変数に基づいて値を返します。

xxx_reset() および xxx_add() では、引数の処理はすべて、通常の UDF と同じように実行する必要があります。 See 項13.2.2.3. 「引数の処理」

xxx() では、戻り値の処理を、通常の UDF と同じように実行する必要があります。 See 項13.2.2.4. 「戻り値およびエラー処理」

is_null および error へのポインタ引数は、xxx_reset()xxx_clear()xxx_add()、および xxx() への呼び出しですべて同じです。この引数を使用して、エラーが発生したこと、または xxx() 関数が NULL を返す必要があるかどうかを記憶できます。注意: *error には文字列は格納できません。これは 1 バイトフラグです。

is_null は、グループごとにリセットされます(xxx_clear() を呼び出す前でも)。error はリセットされることはありません。

isnull または errorxxx() の後に設定された場合、MySQL はグループ関数の結果として NULL を返します。

13.2.2.3. 引数の処理

args パラメータは、UDF_ARGS 構造体を参照します。そのメンバを以下に示します。

  • unsigned int arg_count

    引数の数。作成した関数を呼び出すときに引数の数が決まっている場合は、初期化関数でこの値をチェックする。たとえば、以下のように指定する。

    if (args->arg_count != 2)
    {
        strcpy(message,"XXX() requires two arguments");
        return 1;
    }
    

  • enum Item_result *arg_type

    各引数の型。型を表す値として設定できるのは、STRING_RESULTINT_RESULT、および REAL_RESULT である。

    引数が指定した型であることを確認し、一致しない場合にエラーを返すには、初期化関数で arg_type 配列をチェックする。たとえば、以下のように指定する。

    if (args->arg_type[0] != STRING_RESULT ||
        args->arg_type[1] != INT_RESULT)
    {
        strcpy(message,"XXX() requires a string and an integer");
        return 1;
    }
    

    関数の引数を特定の型で渡すように要求する代わりに、初期化関数を使用して arg_type の各要素に希望する型を設定する方法もある。これにより、MySQL は、xxx()を呼び出すたびに強制的に引数をその型に変換する。たとえば、最初の 2 つの引数を文字列と整数で渡すように指定するには、xxx_init() で以下の処理を行う。

    args->arg_type[0] = STRING_RESULT;
    args->arg_type[1] = INT_RESULT;
    

  • char **args

    args->args は、関数が呼び出されたときに渡された引数の一般的な性質について初期化関数に情報を伝達する。i 番目の引数が定数の場合、args->args[i] は引数の値への参照である(値にアクセスする正しい方法については以下の手順を参照)。非定数の場合、args->args[i]0 である。定数引数は、34*7-2、または SIN(3.14) など、定数だけを使用する式である。非定数引数は、カラム名、または非定数引数を使用して呼び出される関数など、レコードごとに変化する可能性がある値を参照する式である。

    関数を呼び出すたびに、args->args に、現在処理中のレコードに関して渡される実際の引数が設定される。

    関数は i 番目の引数を以下のように参照する。

    • STRING_RESULT 型の引数は、文字列へのポインタとその長さとして渡され、バイナリデータまたは可変長のデータを処理できる。実際の文字列は args->args[i] で参照し、文字列の長さは args->lengths[i] と表す。文字列がヌルで終端されていると仮定することは不適切である。

    • INT_RESULT 型の引数の場合、args->args[i] をキャストして long long 型の値を取得する必要がある。

      long long int_val;
      int_val = *((long long*) args->args[i]);
      
    • REAL_RESULT 型の引数の場合、args->args[i] をキャストして double 型の値を取得する必要がある。

      double    real_val;
      real_val = *((double*) args->args[i]);
      

  • unsigned long *lengths

    初期化関数の呼び出し時は、lengths 配列の要素は対応する引数の最大文字列長を示す。この値は変更せず、そのままにしておく必要がある。関数を呼び出すたびに、lengths に、現在処理中のレコードに関して渡される文字列引数の実際の長さが設定される。(初期化関数の呼び出し時は)引数が INT_RESULT 型または REAL_RESULT 型である場合も、lengths には引数の最大長が格納される。

13.2.2.4. 戻り値およびエラー処理

初期化関数は、エラーがない場合は 0、エラーが発生した場合は 1 を返す必要があります。エラーが発生した場合、xxx_init()message パラメータにヌルで終端されたエラーメッセージを格納する必要があります。このメッセージがクライアントに返されます。メッセージバッファの長さは MYSQL_ERRMSG_SIZE 文字ですが、標準の端末画面の幅に合わせて、80 文字未満でメッセージを作成するように努力する必要があります。

long long 関数および double 関数の場合、関数 xxx() の戻り値が関数値になります。文字列関数の場合、結果を参照するポインタを返し、その文字列長を length 引数に格納する必要があります。

戻り値の内容と長さは、以下のように設定します。

memcpy(result, "result string", 13);
*length = 13;

calc 関数には、255 バイトの result バッファが渡されます。返す値がこの大きさに収まる場合は、そのメモリ割り当てについて心配する必要はありません。

文字列関数が 255 バイトより長い文字列を返す必要がある場合、xxx_init() 関数または xxx() 関数で malloc() を使用してそのための領域を割り当て、xxx_deinit() 関数でそれを解放する必要があります。割り当てたメモリを UDF_INIT 構造体の ptr スロットに保存しておけば、将来 xxx() が呼び出されたときにそのメモリを再利用できます。 See 項13.2.2.1. 「単純関数の場合の UDF の呼び出し手順」

関数で戻り値が NULL であることを示すには、is_null1 を設定します。

*is_null = 1;

関数がエラーを返すことを示すには、error1 を設定します。

*error = 1;

xxx() が、いずれかのレコードの処理で *error1 を設定した場合、カレントレコードおよび XXX() を呼び出したステートメントが処理するそれ以降のレコードについて、関数値が NULL になります(それ以降のレコードでは xxx() の呼び出しも行われません)。注意:MySQL 3.22.10 より古いバージョンでは、*error および *is_null の両方に値を設定する必要があります。

*error = 1;
*is_null = 1;

13.2.2.5. ユーザ定義関数のコンパイルおよびインストール

UDF を実装するファイルは、サーバが動作するホスト上でコンパイルし、インストールする必要があります。ここでは、MySQL ソースディストリビューションに含まれるサンプル UDF ファイル udf_example.cc を使用して、その手順について説明します。このファイルには以下の関数が定義されています。

  • metaphon() は、文字列引数の metaphon 文字列を返す。これは soudex 文字列と同種の文字列で、英語向けに精度が高められている。

  • myfunc_double() は、その引数に含まれる文字の ASCII 値の合計を、その引数の長さの合計で割った値を返す。

  • myfunc_int() は、その引数の長さの合計を返す。

  • sequence([const int]) は、番号が指定された場合はその番号から始まるシーケンス、指定されなかった場合は 1 から始まるシーケンスを返す。

  • lookup() は、ホスト名に対応する IP 番号を返す。

  • reverse_lookup() は、IP 番号に対応するホスト名を返す。文字列 "xxx.xxx.xxx.xxx" または 4 つの数値をパラメータとして呼び出すことができる。

ダイナミックにロード可能なファイルは、以下のようなコマンドを使用して、共有オブジェクトファイルとしてコンパイルする必要があります。

shell> gcc -shared -o udf_example.so myfunc.cc

MySQL ソースツリーの sql ディレクトリで以下のコマンドを実行するだけで、システムに合った適切なコンパイラオプションを知ることができます。

shell> make udf_example.o

make が表示するコンパイルコマンドをほとんどそのまま使用できます。ただし、行末付近にある -c オプションは指定しないでください。また、行末に -o udf_example.so を追加してください(一部のシステムでは、-c オプションをコマンドに残す必要があります)。

UDF を含む共有オブジェクトをコンパイルしたら、それをインストールして、MySQL に通知する必要があります。udf_example.cc をコンパイルして共有オブジェクトを作成すると、udf_example.so のような名前のファイルが生成されます(正確な名前はプラットフォームによって異なる可能性があります)。このファイルを /usr/lib などのダイナミックリンカ ld の検索対象パスにコピーするか、または共有オブジェクトを置いたディレクトリをリンカ設定ファイル(たとえば /etc/ld.so.conf)に追加します。

多くのシステムでは、UDF 関数ファイルを置いたディレクトリを参照するように環境変数 LD_LIBRARY または LD_LIBRARY_PATH を設定することもできます。システムで使用する必要がある変数については、dlopen のマニュアルページを参照してください。それらの変数を mysql.server または mysqld_safe の起動スクリプトで設定し、mysqld を再起動する必要があります。

ライブラリをインストールしたら、以下のコマンドを使用して、新しく追加した関数について mysqld に通知します。

mysql> CREATE FUNCTION metaphon RETURNS STRING SONAME "udf_example.so";
mysql> CREATE FUNCTION myfunc_double RETURNS REAL SONAME "udf_example.so";
mysql> CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "udf_example.so";
mysql> CREATE FUNCTION lookup RETURNS STRING SONAME "udf_example.so";
mysql> CREATE FUNCTION reverse_lookup
    ->        RETURNS STRING SONAME "udf_example.so";
mysql> CREATE AGGREGATE FUNCTION avgcost
    ->        RETURNS REAL SONAME "udf_example.so";

関数を削除するには、DROP FUNCTION を使用します。

mysql> DROP FUNCTION metaphon;
mysql> DROP FUNCTION myfunc_double;
mysql> DROP FUNCTION myfunc_int;
mysql> DROP FUNCTION lookup;
mysql> DROP FUNCTION reverse_lookup;
mysql> DROP FUNCTION avgcost;

CREATE FUNCTION および DROP FUNCTION の各ステートメントは、mysql データベースのシステムテーブル func を更新します。関数の名前、型、および共有ライブラリ名はこのテーブルに保存されます。関数を作成および削除するには、mysql データベースに対する INSERT 特権および DELETE 特権を持っている必要があります。

すでに作成されている関数を CREATE FUNCTION を使用して追加してはいけません。関数を再インストールする必要がある場合は、まず DROP FUNCTION で関数を削除してから CREATE FUNCTION で再インストールします。たとえば新しいバージョンの関数を再コンパイルした場合に、この手順で再インストールして mysqld に新しいバージョンの関数を追加します。再インストールしなければ、サーバは古いバージョンの関数を使い続けます。

mysqld を起動する際に --skip-grant-tables オプションが指定されていなかった場合、アクティブな関数はサーバが起動するたびに再ロードされます。オプションが指定されていた場合、UDF の初期化は行われず、それを使用することはできません(アクティブな関数とは CREATE FUNCTION でロードされたが DROP FUNCTION による削除は行われていない関数)。

13.2.3. 新しいネイティブ関数の追加

ここでは、新しいネイティブ関数を追加する手順について説明します。注意: この手順には MySQL ソースコードの修正が含まれるので、バイナリディストリビューションにネイティブ関数を追加することはできません。MySQL をソースディストリビューションからコンパイルする必要があります。注意: (新しいバージョンがリリースされたときなど)別のバージョンの MySQL に移行する場合、新しいバージョンでも同じ手順を繰り返してネイティブ関数を追加する必要があります。

MySQL の新しいネイティブ関数を追加するには、以下の手順に従います。

  1. sql_functions[] 配列に関数名を定義する行を lex.h に追加する。

  2. 関数のプロトタイプが単純な場合(引数の数が 0、1、2、または 3 個しかない)、lex.h で sql_functions[] 配列の 2 番目の引数として SYM(FUNC_ARG#) (# は引数の数)を指定し、item_create.cc に関数オブジェクトを作成する関数を追加する必要がある。この例として、"ABS" および create_funcs_abs() を参照すること。

    関数のプロトタイプが複雑な場合(引数の数が可変の場合など)、sql_yacc.yy に 2 行追加する必要がある。まず、yacc が定義する必要があるプリプロセッサシンボルを表す行を追加する(ファイルの先頭部分)。次に、関数のパラメータを定義し、パラメータの指定方法を定義する行を simple_expr 解析規則に追加する。この参考例として、sql_yacc.yyATAN が出現する部分を参照すること。

  3. item_func.h で、関数の戻り値の型(数値または文字列)に応じて、Item_num_func または Item_str_func を継承するクラスを宣言する。

  4. item_func.cc で、定義している関数の型(数値型または文字列型)に応じて、以下のいずれかの宣言を追加する。

    double   Item_func_newname::val()
    longlong Item_func_newname::val_int()
    String  *Item_func_newname::Str(String *str)
    

    標準のアイテム(Item_num_func など)からオブジェクトを継承する場合、定義する必要があるのはおそらく上記のいずれか 1 つだけであり、残りの関数の定義は親オブジェクトにまかせることができる。たとえば、Item_str_func クラスは ::str() が返す値に対して atof() を実行する val() 関数を定義している。

  5. おそらく、以下のオブジェクト関数も定義する必要がある。

    void Item_func_newname::fix_length_and_dec()
    

    この関数は、少なくとも渡された引数に基づいて max_length を計算する必要がある。max_length は、関数が返す可能性がある最大文字数である。関数が NULL 値を返すことができない場合、この関数で maybe_null = 0 も設定する必要がある。関数は、関数の引数が NULL を返すことができるかどうかを、その引数の maybe_null 変数を調べることによって、確認できる。この実装例については、Item_func_mod::fix_length_and_dec を参照すること。

上記の関数はすべてスレッドセーフで作成する必要があります(言い換えると、mutex ロックを使用せずに関数でグローバルまたはスタティックな変数を使用してはいけません)。

NULL を返す場合、::val()::val_int()、または ::str() で、null_value を 1 に設定して、0 を返す必要があります。

::str() オブジェクト関数の場合、さらにいくつかの注意事項があります。

  • String *str 引数は、結果の格納に使用できる文字列バッファを提供する(String 型の詳細については、sql_string.h ファイルを参照すること)。

  • ::str() 関数は、結果が NULL の場合は (char*) 0 を、それ以外の値の場合は結果が格納された文字列を返す必要がある。

  • 現在のすべての文字列関数は、どうしても必要な場合を除いて、メモリ割り当ては一切行わないように定義されている。

13.3. MySQL への新しいプロシージャの追加

MySQL では、クライアントに送信する前にクエリのデータにアクセスして、変更できるプロシージャを、C++ を使用して定義できます。データの変更は、1 行ごと、または GROUP BY レベルで実行できます。

MySQL バージョン 3.23 には、プロシージャで実行できる処理を示すサンプルプロシージャが用意されています。

また、mylua を参照することをおすすめします。これは、LUA 言語を使用して実行時に mysqld にプロシージャをロードするツールです。

13.3.1. プロシージャの分析

analyse([max elements,[max memory]])

このプロシージャは、sql/sql_analyse.cc で定義されています。クエリから返された結果を調べて、その分析結果を返します。

  • max elements(デフォルトは 256)は、analyse がカラムごとに検出した、重複を除いた値の最大数。analyse はこの値を使用して、ENUM 型が最適なカラム型かどうかを調べる。

  • max memory(デフォルトは 8192)は、analyse が重複を除いた値をすべて検出する際にカラムごとに割り当てる必要がある最大メモリ容量。

SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max elements,[max memory]])

13.3.2. プロシージャの作成

現在、プロシージャの作成に関する唯一のドキュメントはソースです。

以下のファイルを参照することで、プロシージャに関するすべての情報を入手できます。

  • sql/sql_analyse.cc

  • sql/procedure.h

  • sql/procedure.cc

  • sql/sql_select.cc


This is a translation of the MySQL Reference Manual that can be found at dev.mysql.com. The original Reference Manual is in English, and this translation is not necessarily as up to date as the English version.

アダルトレンタルサーバー