PostgreSQL - C言語によるユーザ定義関数の作り方

9,172 views

Published on

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
9,172
On SlideShare
0
From Embeds
0
Number of Embeds
373
Actions
Shares
0
Downloads
0
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

PostgreSQL - C言語によるユーザ定義関数の作り方

  1. 1. PostgreSQL C言語によるユーザ定義関数の作り方 日本PostgreSQLユーザ会 永安悟史 snaga@snaga.org
  2. 2. Contents PostgreSQL関数の基礎知識 もっとも簡単な関数の作成 関数に引数を渡す レコードを返却する関数の作成 複数行を返却する関数の作成 テーブルを返却する関数の作成
  3. 3. PostgreSQL関数の基礎知識
  4. 4. C言語で関数を作るメリット PostgreSQLの内部構造にアクセス・操作できる 共有メモリ システムカタログ 外部のさまざまなライブラリと連携できる 外部ライブラリの機能をPostgreSQLの関数の 延長で使用できる 高速 Cですから
  5. 5. 関数の種類 引数 引数無し 引数有り 戻り値 値(単一行、単一列) レコード(単一行、複数列) 複数行(複数行、単一列) テーブル(複数行、複数列)
  6. 6. 開発のおおまかな流れ C言語で関数本体を作成する Makefileを作成する 関数を登録するSQLを作成する C言語関数のコンパイルとインストール SQL文で関数を登録 共有 バックエンド C言語ソース オブジェクト 登録 Makefile 登録用SQL
  7. 7. もっとも簡単な関数の作成
  8. 8. サンプル(1) もっとも簡単な関数 引数:無し 戻り値:常にtrueを返す snaga=# SELECT testfunc1(); testfunc1 ----------- t (1 row) snaga=#
  9. 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. 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. 11. サンプル(1) ~ 登録用SQLの作成 登録用のSQL文を作成する “testfunc.sql.in” として作成 SQLの関数名 戻り値の型 C言語の関数名 CREATE OR REPLACE FUNCTION testfunc1() RETURNS boolean AS 'MODULE_PATHNAME', 'testfunc1' LANGUAGE 'C' STRICT;
  12. 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. 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. 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. 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. 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=#
  17. 17. 関数に引数を渡す
  18. 18. 引数を渡すには 必要な処理は二ヶ所にある C言語関数における引数受け取り CREATE FUNCTIONによる定義
  19. 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. 20. 関数定義のしかた 関数登録用SQLで引数の型を宣言する 複数引数の宣言も可能 CREATE OR REPLACE FUNCTION testfunc2(int4) RETURNS boolean AS 'MODULE_PATHNAME', 'testfunc2' 渡す引数の型を宣言する LANGUAGE 'C' STRICT; (ここではint4)
  21. 21. レコードを返却する関数の作成
  22. 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. 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. 24. 関数定義のしかた 返却される型として “RECORD” を指定する CREATE OR REPLACE FUNCTION testfunc3() RETURNS RECORD AS 'MODULE_PATHNAME', 'testfunc3' LANGUAGE 'C' STRICT;
  25. 25. 動作確認 関数の呼び出し方 レコードを返却する場合には、レコードの型情報 を指定する必要がある。 snaga=# SELECT * FROM testfunc3() f(c1 int8, c2 int8); c1 | c2 -----+----- 128 | 256 (1 row) testfunc3()が返却するのは int8型のカラム“c1”と int8型のカラム“c2”である。 snaga=# レコードとして返却される
  26. 26. 複数行を返却する関数の作成
  27. 27. 概要 SET RETURNING FUNCTION(SRF)とも 呼ばれる 返却する行数と同じ回数、関数が呼び出される 一回目の呼び出しで初期化 呼び出し一回につき、一行を返却する 二つのメモリコンテキストを使う 関数用コンテキスト ユーザデータ用コンテキスト 関数用コンテキストの中にある
  28. 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型を渡す 呼び出し回数が 最大に達したら
  29. 29. レコードの返し方(1/3) 関数コンテキスト用のポインタを宣言する Datum testfunc4(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; /* 関数コンテキスト */ MemoryContext oldcontext;
  30. 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. 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. 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. 33. 関数定義のしかた 返却される型に追加して “SETOF” を指定する CREATE OR REPLACE FUNCTION testfunc4() RETURNS SETOF INT4 AS 'MODULE_PATHNAME', 'testfunc4' LANGUAGE 'C' STRICT;
  34. 34. 動作確認 関数の呼び出し方 snaga=# SELECT testfunc4(); testfunc4 ----------- 1 2 3 (3 rows) snaga=# int4型の値が複数行に 渡って返却される
  35. 35. テーブルを返却する関数の作成
  36. 36. 概要 マニュアルではTable Functionと呼ばれている ここまで解説した手法を組み合わせる 「複数行」の「レコード」を返却する関数として 実装する
  37. 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. 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. 39. 関数定義のしかた “SETOF” な “RECORD” を 返却する CREATE OR REPLACE FUNCTION testfunc5() RETURNS SETOF RECORD AS 'MODULE_PATHNAME', 'testfunc5' LANGUAGE 'C' STRICT;
  40. 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” から成るレコードが 複数返却される
  41. 41. Appendix 引数の受け取り方(include/fmgr.h) PG_GETARG_xxxx() 値の返却(include/fmgr.h) PG_RETURN_xxxx() 値とDatumとの変換(include/postgres.h) xxxxGetDatum() DatumGetxxxx()

×