Cによるユーザ定義関数
PostgreSQLではCでユーザ定義関数を定義することができるので,Cでユーザ定義関数を作成してみた.
Cで定義したユーザ定義関数は,PostgreSQLから呼び出される.したがって,PostgreSQLと連係するために必要なライブラリやヘッダファイルをまずインストールする必要がある.Debian GNU/Linuxなら以下を実行する.
# apt-get install postgresql-devこれで事前準備は完了である.
Cでユーザ定義関数を作成する際の記述内容の概要を以下に示す.
- include
- 基本定義が記述されたpostgres.h,ファンクションマネージャ(※)を使用するためのfmgr.hをincludeする.これらはapt-getしたシステムなら先述の事前準備により以下にインストールされるようだ.
/usr/include/postgresql/server/fmgr.h /usr/include/postgresql/server/postgres.h
- プロトコルバージョンの指定
- プロトコルバージョンの指定はファンクションマネージャが変更されても互換性が保てるようにするためのものらしい.プロトコルバージョン1の指定には以下のマクロを使用する.
PG_FUNCTION_INFO_V1(funcname)
functnameは定義する関数である.プロトコルバージョンを指定しない場合,プロトコルバージョン0と認識される.これは7.0あたりの古いPostgreSQLと互換性をとるためのバージョンである. - 関数宣言
- 関数funcnameの関数宣言は以下の形式である.
Datum funcname(PG_FUNCTION_ARGS)
- 引数の取得
- PostgreSQLから渡される引数はすべてマクロを使用して取得する.詳細は後述.
- 返り
- 関数から返るにもマクロを使用する.詳細は後述.
- elog()関数
- elog()関数は関数内の処理に応じてログを吐いたりトランザクションをアボートさせたりするのに使用する.
※ ファンクションマネージャは,関数やマクロを提供し,それによってPostgreSQLと連係するCプログラミングが簡単になる.
サンプルとして二つのINTEGER型の引数をとり,その和を返す関数を作成した.以下に示す.
この関数を使用するユーザ定義関数を作成する.まずコンパイルして共有オブジェクトを作成する.
$ gcc -I/usr/include/postgresql/server -shared my_func1.c -o my_func1.so次に作成したオブジェクトの関数を呼び出す関数を定義する.Cで記述されたユーザ定義関数を定義するには,DBの管理者権限が必要である.
advanced=# CREATE FUNCTION call_func1(INTEGER, INTEGER) advanced-# RETURNS INTEGER advanced-# AS '/tmp/my_func1.so', 'my_func1' advanced-# LANGUAGE 'c' advanced-# WITH (iscachable); CREATEASに続くのは.soファイルへの絶対パスと,共有オブジェクトファイルでの呼び出す関数のシンボルである.呼んでみる.
advanced=> SELECT call_func1(1,2);
call_func1
------------
3
(1 row)
advanced=> SELECT call_func1(100, 200);
call_func1
------------
300
(1 row)
サンプルプログラムでは
field1 = PG_GETARG_INT32(0); field2 = PG_GETARG_INT32(1);などとマクロを使用して引数を取得した.引数取得マクロは全て引数を一つとる.この値は何番目の引数かを指定するものである.一番目の引数は0である.マクロは沢山あるが,それらの一覧はfmgr.hに記述されている."_P"で終るマクロは引数へのポインタを返し,"_P_COPY"で終るマクロは,引数のコピーへのポインタを返す関数である.
サンプルプログラムでは,
PG_RETURN_INT32(field1 + field2);として関数から値を返した.多くの返りマクロは引数を一つとる.これは返り値として使用される.ただし,以下のマクロは引数がない.
- PG_RETURN_NULL()
- PG_RETURN_VOID()
SQLによるユーザ定義関数ではテーブルを引数にとるユーザ定義関数を作成することが出来た.Cによるユーザ定義関数でもこれが可能である.そこでテーブルを引数にとり,その二つのTEXT型のフィールドf1,f2を連結して返す関数を作成した.まずテーブルを作成してデータを投入しておく.
フィールドf1,f2はいずれもNULLであってはならないという仕様にしてみた.コンパイルしてmy_func2.soを作成,そして関数定義する.
advanced=> CREATE TABLE hoge(
advanced(> f1 TEXT,
advanced(> f2 TEXT);
CREATE
advanced=> INSERT INTO hoge
advanced-> VALUES ('aaa', 'bbb');
INSERT 26211 1
advanced=> INSERT INTO hoge
advanced-> VALUES ('ccc', NULL);
INSERT 26212 1
advanced=> INSERT INTO hoge
advanced-> VALUES (NULL, 'ddd');
INSERT 26213 1
これを引数にとる関数を作成する.
advanced=# CREATE FUNCTION call_func2(hoge) advanced-# RETURNS TEXT advanced-# AS '/tmp/my_func2.so', advanced-# 'my_func2' advanced-# LANGUAGE 'c' advanced-# WITH (iscachable); CREATE呼んでみる.
advanced=> SELECT call_func2(hoge) FROM hoge advanced-> WHERE f1='aaa'; call_func2 ------------ aaabbb (1 row) advanced=> SELECT call_func2(hoge) FROM hoge advanced-> WHERE f1='ccc'; ERROR: f2 is NULL advanced=> SELECT call_func2(hoge) FROM hoge advanced-> WHERE f2='ddd'; ERROR: f1 is NULL
my_func2はテーブル名と同名の型をもつ,その構造体のタプル(レコード)を引数に持つ.C関数にはタプルへのポインタが渡される.引数のポインタを取得するには以下のマクロを使用する.
PG_GETARG_POINTER(n)nは何番目のポインタかを指定する.このマクロは内部で関数を呼び出し,それによってDatum型(※1)のデータへのポインタが返るので,今回は(TupleTableSlot *)にキャストした.TupleTableSlotは/usr/include/postgresql/server/executor/tuptable.hでdefineされている.取得したタプルからフィールドを取得するには,以下の関数を使用する.
Datum GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull);第一引数で指定したタプルから第二引数の文字列で指定した名前のフィールドの値をDatum型で返す.第三引数はフィールドの値がNULLだった場合に0以外が設定される.この関数はDatum型のデータを返すので,これを適切な型に変換する必要がある.これには以下のマクロを使用する.
DatumGetTextP(datum)このマクロは/usr/include/postgresql/server/executor/executor.hでdefineされている.datumはTOAST(※2)によって圧縮がかかっている可能性があるので,単にキャストするだけでは問題がある.このマクロの類似関数が沢山あってDatum型から多くのデータ型に変換することができる.その一覧は/usr/include/postgresql/server/fmgr.hにある.
- ※1
- PostgresSQLでの汎用データ型.
- ※2
- PostgreSQLではTOAST(The Oversized-Attribute Storage Technique)により,一行辺りのサイズを1GBまで扱えるようになっている.これはTOASTにより必要に応じてデータを圧縮することによるらしい.
TEXTはPostgreSQL独自のデータ型で,可変長文字列である.これに対応するcでの型としてtextがある.ヘッダを調査すると,textは実際にはvarlena構造体であることがわかる.以下,/usr/include/postgresql/server/c.hより.
struct varlena
{
int32 vl_len;
char vl_dat[1];
};
vl_lenは文字列長で,vl_datが文字列である.コメントではNULLストップはないと書いてあるように見えるが,サンプル作成の過程でvl_datは'\0'で終了しているように見えたので,サンプルではNULLストップを前提としてコーディングした.varlena構造体は直接操作せず,以下のマクロを使用する.
- VARHDRSZ
- varlena構造体のvl_datを除くサイズ.
- VARSIZE(*varlena)
- vl_lenの値.
- VARDATA(*varlena)
- vl_dataへのポインタ.
サンプルでは出力用のtext構造体のために,動的に領域確保を行なっている.これにはPostgreSQL用のmacclocであるpallocを使用した.macclocするとfreeしたいが,トランザクションがアボートしたりしたときの処理が難しくなるのを避けるためにpallocが用意されている.freeに対応するものとしてpfreeもある.pacllocで確保した領域は呼び出したcontext内でのみ有効で,contextが無効になると領域は開放されるようだ.今回のサンプルの様に関数内で領域を確保してそこに値を書き込んで返す必要があるけれども,領域を開放する術がないように見える場合にはありがたい.とにかくCでユーザ定義関数を書くときはmallocとfreeではなくpallocとpfreeを使用すると考えればよいと思う.
サンプルの様にelogという関数を使用すると,フロントエンドへメッセージを出力したり,トランザクションをアボートしたりできる.以下,/usr/include/postgresql/server/utils/elog.hより.
extern void elog(int lev, const char *fmt,...) __attribute__((format(printf, 2, 3)));elogの引数について以下に示す.
- 第一引数
- レベル
- 第二引数
- フォーマット文字列
- 第三引数以降
- フォーマットに埋め込む値.
- NOTICE
- 任意の情報をフロントエンドへ送信する.
- ERROR
- エラー.トランザクションはアボートする.なんらかの状態を返すらしい.
- FATAL
- 致命的なエラー.バックエンドプロセスがアボートする.
- REALLYFATAL
- さらに致命的なエラー.全バックエンドプロセスがアボートする.
- DEBUG
- デバッグメッセージ.postmasterを-dオプションつきで起動すると表示されるらしい.デバッグレベルは/etc/postgresql/postgresql.confで指定できる.
- STOP
- 無効.REALLYFATALと同じ結果になる.
- LOG
- 無効.DEBUGと同じ結果になる.

