More Related Content Similar to PostgreSQL - C言語によるユーザ定義関数の作り方 Similar to PostgreSQL - C言語によるユーザ定義関数の作り方 (20) More from Satoshi Nagayasu More from Satoshi Nagayasu (20) PostgreSQL - C言語によるユーザ定義関数の作り方6. 開発のおおまかな流れ
C言語で関数本体を作成する
Makefileを作成する
関数を登録するSQLを作成する
C言語関数のコンパイルとインストール
SQL文で関数を登録
共有 バックエンド
C言語ソース
オブジェクト 登録
Makefile 登録用SQL
8. サンプル(1)
もっとも簡単な関数
引数:無し
戻り値:常にtrueを返す
snaga=# SELECT testfunc1();
testfunc1
-----------
t
(1 row)
snaga=#
9. サンプル(1) ~ C関数の作成
まず、“testfunc.c” を作る
1:#include "postgres.h"
2:#include "fmgr.h"
3:#include "funcapi.h"
4:
5:PG_FUNCTION_INFO_V1(testfunc1); /* お約束 */
6:
7:extern Datum testfunc1(PG_FUNCTION_ARGS); /* お約束 */
8:
9:Datum /* 戻り値は ‘Datum’ になる */
10:testfunc1(PG_FUNCTION_ARGS) /* 引数定義 */
11:{
12: PG_RETURN_BOOL(true); /* 値を返す */
13:}
10. サンプル(1) ~ Makefileの作成
次に Makefile を作成
1:SRCS = testfunc.c
2:
3:MODULE_big = testfunc
4:OBJS = $(SRCS:.c=.o)
5:DOCS =
6:DATA_built = testfunc.sql
7:
8:ifdef USE_PGXS
9:PGXS = $(shell pg_config --pgxs)
10:include $(PGXS)
11:else
12:subdir = contrib/testfunc
13:top_builddir = ../..
14:include $(top_builddir)/src/Makefile.global
15:include $(top_srcdir)/contrib/contrib-global.mk
16:endif
11. サンプル(1) ~ 登録用SQLの作成
登録用のSQL文を作成する
“testfunc.sql.in” として作成
SQLの関数名
戻り値の型
C言語の関数名
CREATE OR REPLACE FUNCTION testfunc1()
RETURNS boolean
AS 'MODULE_PATHNAME', 'testfunc1'
LANGUAGE 'C' STRICT;
12. サンプル(1) ~ 関数のコンパイル
作成したファイルを contrib/testfunc に置く
contrib はソースを展開したディレクトリにある
{657}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc% pwd
/home/snaga/postgresql-8.1beta2/contrib/testfunc
{658}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc% ls -l
合計 12
-rw-r--r-- 1 snaga users 585 9月 24 20:34 Makefile
-rw-r--r-- 1 snaga users 200 9月 24 20:34 testfunc.c
-rw-r--r-- 1 snaga users 110 9月 24 20:36 testfunc.sql.in
{659}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc%
13. サンプル(1) ~ 関数のコンパイル
make する(makeコマンドを実行)
{659}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc% make
sed 's,MODULE_PATHNAME,$libdir/testfunc,g' testfunc.sql.in >testfunc.sql
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -fno-strict-aliasing
-fpic -I. -I../../src/include -D_GNU_SOURCE -c -o testfunc.o testfunc.c
ar crs libtestfunc.a testfunc.o
ranlib libtestfunc.a
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -fno-strict-aliasing
-fpic -shared -Wl,-soname,libtestfunc.so.0 testfunc.o -L../../src/port
-Wl,-rpath,/home/snaga/pgsql81b2/lib -o libtestfunc.so.0.0
rm -f libtestfunc.so.0
ln -s libtestfunc.so.0.0 libtestfunc.so.0
rm -f libtestfunc.so
ln -s libtestfunc.so.0.0 libtestfunc.so
{660}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc% ls
Makefile libtestfunc.so.0@ testfunc.o
libtestfunc.a libtestfunc.so.0.0* testfunc.sql
libtestfunc.so@ testfunc.c testfunc.sql.in
{661}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc%
14. サンプル(1) ~ 関数のインストール
testfunc.sql が
make install を実行 share/contrib に
インストールされる
{662}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc% make install
/bin/sh ../../config/install-sh -c -m 644 testfunc.sql
/home/snaga/pgsql81b2/share/contrib
/bin/sh ../../config/install-sh -c -m 755 libtestfunc.so.0.0
/home/snaga/pgsql81b2/lib/testfunc.so
{663}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc%
Libtestfunc.so.0.0 が
lib にインストールされる
15. サンプル(1) ~ 関数の登録
データベースに対して関数を登録する
{663}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc% psql -f ¥
/home/snaga/pgsql81b2/share/contrib/testfunc.sql
CREATE FUNCTION
{664}snaga@athena:~/postgresql-8.1beta2/contrib/testfunc%
データベースに対して
testfunc.sql を実行する
(実際には一行)
16. サンプル(1) ~ 動作確認
psql を使って動作を確認する
{87}snaga@athena:~/pgsql81b2% ./bin/psql
Welcome to psql 8.1beta2, the PostgreSQL interactive terminal.
Type: ¥copyright for distribution terms
¥h for help with SQL commands
¥? for help with psql commands
¥g or terminate with semicolon to execute query
¥q to quit
snaga=# SELECT testfunc1();
testfunc1
-----------
t
(1 row)
snaga=#
19. 引数の受け取り方
マクロ “PG_GETARG_xxx” を使う
型に対応したマクロは include/fmgr.h を参照
1:#include "postgres.h"
2:#include "fmgr.h"
3:#include "funcapi.h"
4:
5:PG_FUNCTION_INFO_V1(testfunc2);
6:
7:extern Datum testfunc2(PG_FUNCTION_ARGS);
8:
ここの定義は変わらない
9:Datum
10:testfunc2(PG_FUNCTION_ARGS)
11:{
12: int i = PG_GETARG_INT32(0); “0” は1番目の引数の意。
13: PG_GETARG_INT32は
14: PG_RETURN_BOOL(true); int4型を受け取る場合。
15:}
20. 関数定義のしかた
関数登録用SQLで引数の型を宣言する
複数引数の宣言も可能
CREATE OR REPLACE FUNCTION testfunc2(int4)
RETURNS boolean
AS 'MODULE_PATHNAME', 'testfunc2' 渡す引数の型を宣言する
LANGUAGE 'C' STRICT; (ここではint4)
22. レコードの返し方(1/2)
レコード(=タプル)として返却するために
ここでは2つの int8 の組みをレコードとして返す
Datum
testfunc3(PG_FUNCTION_ARGS)
{
TupleDesc tupd; /* タプルの型情報の構造体 */
HeapTupleData tupleData; /* タプルのデータ用構造体 */
HeapTuple tuple = &tupleData; /* タプルのデータ用ポインタ */
char *values[2];
タプルの型情報(の入れ物)を作る
Datum result;
型情報を作成する
tupd = CreateTemplateTupleDesc(2, false);
TupleDescInitEntry(tupd, 1, "c1", INT8OID, -1, 0);
TupleDescInitEntry(tupd, 2, "c2", INT8OID, -1, 0);
これらのカラムを組みに
してレコードとする。 カラム番号 カラム名 カラムの型
23. レコードの返し方(2/2)
データをレコードとして組み立てて返却する
バッファを作成して
文字列として作成する
データを文字列の配列と
して用意する
values[0] = palloc(32);
snprintf(values[0], 32, "%d", 128);
values[1] = palloc(32); 先に作成した型情報と
snprintf(values[1], 32, "%d", 256); データからタプルを作る
tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupd),
values);
result = TupleGetDatum(TupleDescGetSlot(tupd), tuple);
PG_RETURN_DATUM(result);
タプルをDatum型に変換
}
Datumを返却
24. 関数定義のしかた
返却される型として “RECORD” を指定する
CREATE OR REPLACE FUNCTION testfunc3()
RETURNS RECORD
AS 'MODULE_PATHNAME', 'testfunc3'
LANGUAGE 'C' STRICT;
25. 動作確認
関数の呼び出し方
レコードを返却する場合には、レコードの型情報
を指定する必要がある。
snaga=# SELECT * FROM testfunc3() f(c1 int8, c2 int8);
c1 | c2
-----+-----
128 | 256
(1 row) testfunc3()が返却するのは
int8型のカラム“c1”と
int8型のカラム“c2”である。
snaga=#
レコードとして返却される
28. コード概要
関数コンテキスト
Datum testfunc4(PG_FUNCTION_ARGS)
{ 一回目の呼び出し
FuncCallContext *funcctx;
if (SRF_IS_FIRSTCALL()) コンテキスト初期化
{
funcctx = SRF_FIRSTCALL_INIT(); 呼び出し最大回数を
設定
funcctx->max_calls = 3;
} 関数コンテキストを
取り出す
funcctx = SRF_PERCALL_SETUP();
回数がMAXに達して
if (call_cntr < max_calls) いなければ行を返す
SRF_RETURN_NEXT(funcctx, Int32GetDatum(call_cntr));
else
SRF_RETURN_DONE(funcctx);
} Datum型を渡す
呼び出し回数が
最大に達したら
30. レコードの返し方(2/3)
最初の呼び出し時には、関数コンテキストを初期
化し、ユーザデータ用のメモリ領域も初期化する
if (SRF_IS_FIRSTCALL()) 関数コンテキスト
初期化
{ ユーザデータ用コンテ
funcctx = SRF_FIRSTCALL_INIT(); キストに切り替え
呼び出し
一回目 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* MyData *fctx = (MyData *) palloc(sizeof(MyData)); */
ユーザ用領域を
funcctx->max_calls = 3; 呼び出し 確保
/* funcctx->user_fctx = fctx; */ 最大回数を設定
MemoryContextSwitchTo(oldcontext); ユーザデータ用コンテ
} キストを設定
前のコンテキストに
切り替え
31. レコードの返し方(3/3)
関数コンテキストを取り出す
呼び出し最大回数を越えてなければ値を
Datum型で返却する
SRF_RETURN_NEXT()
最大回数を越えていたら終了する
SRF_RETURN_DONE()
funcctx = SRF_PERCALL_SETUP();
if (funcctx->call_cntr < funcctx->max_calls)
SRF_RETURN_NEXT(funcctx, Int32GetDatum(call_cntr));
else
SRF_RETURN_DONE(funcctx);
}
32. コード全体
Datum testfunc4(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL())
{
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* MyData *fctx = (MyData *) palloc(sizeof(MyData)); */
funcctx->max_calls = 3;
/* funcctx->user_fctx = fctx; */
MemoryContextSwitchTo(oldcontext);
}
funcctx = SRF_PERCALL_SETUP();
if (funcctx->call_cntr < funcctx->max_calls)
SRF_RETURN_NEXT(funcctx, Int32GetDatum(funcctx->call_cntr));
else
SRF_RETURN_DONE(funcctx);
}
33. 関数定義のしかた
返却される型に追加して “SETOF” を指定する
CREATE OR REPLACE FUNCTION testfunc4()
RETURNS SETOF INT4
AS 'MODULE_PATHNAME', 'testfunc4'
LANGUAGE 'C' STRICT;
34. 動作確認
関数の呼び出し方
snaga=# SELECT testfunc4();
testfunc4
-----------
1
2
3
(3 rows)
snaga=# int4型の値が複数行に
渡って返却される
37. コード全体(1/2)
Datum
testfunc5(PG_FUNCTION_ARGS)
{
最初の呼び出しの場合
FuncCallContext *funcctx;
MemoryContext oldcontext;
関数コンテキスト初期化
if (SRF_IS_FIRSTCALL())
{
TupleDesc tupd; ユーザ領域用コンテキス
トに切り替え
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
タプルデスクリプタ作成
tupd = CreateTemplateTupleDesc(2, false);
TupleDescInitEntry(tupd, 1, "c1", INT8OID, -1, 0);
TupleDescInitEntry(tupd, 2, "c2", INT8OID, -1, 0);
funcctx->max_calls = 3; タプルデスクリプタを
funcctx->user_fctx = tupd; ユーザ領域に保存
前のコンテキストに
MemoryContextSwitchTo(oldcontext); 戻す
}
38. コード全体(2/2)
funcctx = SRF_PERCALL_SETUP();
関数コンテキストを取り出す
if (funcctx->call_cntr < funcctx->max_calls) (毎回呼ばれる)
{
TupleDesc tupd;
HeapTupleData tupleData;
HeapTuple tuple = &tupleData; ユーザ用コンテキストから
Datum result; タプルデスクリプタを取り出す
char *values[2];
タプルデータを組み立てる
tupd = (TupleDesc)funcctx->user_fctx;
(適当な数値データを作
成)
values[0] = palloc(32);
snprintf(values[0], 32, "%d", 128 * funcctx->call_cntr); タプルデスクリプタとデータ
values[1] = palloc(32); からレコードを作製
snprintf(values[1], 32, "%d", 256 * funcctx->call_cntr);
tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupd), values);
result = TupleGetDatum(TupleDescGetSlot(tupd), tuple);
SRF_RETURN_NEXT(funcctx, result);
} レコードを返却
else
SRF_RETURN_DONE(funcctx);
}
39. 関数定義のしかた
“SETOF” な “RECORD” を 返却する
CREATE OR REPLACE FUNCTION testfunc5()
RETURNS SETOF RECORD
AS 'MODULE_PATHNAME', 'testfunc5'
LANGUAGE 'C' STRICT;
40. 動作確認
関数の呼び出し方
snaga=# SELECT * FROM testfunc5() f(c1 int8, c2 int8);
c1 | c2
-----+-----
0 | 0
128 | 256
256 | 512 testfunc5()が返却するのは
(3 rows) int8型のカラム“c1”と
int8型のカラム“c2”である。
snaga=#
“c1”, “c2” から成るレコードが
複数返却される