#author("2017-07-11T22:58:56+09:00","default:haikikyou","haikikyou")
[[PostgreSQL/開発]]
#contents
* 概要 [#x22e14be]
ここでは,拡張機能の開発で必要となる知識について整理する。~
具体的には,拡張機能のディレクトリ構造,ビルドスクリプト,制御ファイルについて記述する。
* 拡張機能のディレクトリ構造 [#y89187af]
CREATE EXTENSIONとしてインストールされる拡張機能のディレクトリ構造は,以下のようになっている(ここでは,拡張機能を「myext」として説明を進める)。
#geshi{{{
myext
|- myext.control
|- myext--xx.sql
|- Makefile
|- myext.c
}}}
- myext.control
-- CREATE EXTENSIONやALTER EXTENSIONが実行されたときに読み込まれる。
- myext--xx.sql
-- CREATE EXTENSIONやALTER EXTENSIONが実行されたときに実行されるターゲットバージョンのスクリプト。xxからyyへのアップデートに段階的な更新が必要な場合は,myext--xx--yy.sqlのようにする。
-- インストールするオブジェクトがない場合,空ファイルを作成しておけばCREATE EXTENSIONコマンドは成功する。
- Makefile
-- ビルド定義
- myex.c
-- ソースコード。もちろん複数のソースコードのコンパイル&リンクも問題ない。
&size(12){&color(white,#00afcc){ サンプル1};}; trueを返すだけのmyext関数の作成
#ref(./myext.tar.gz,100%)
各ファイルの内容はざっと最小限で書いて以下のような感じになる。各ファイルの定義内容については後述する。
''Makefile''
#geshi{{{
MODULES = myext
EXTENSION = myext
DATA = myext--1.0.sql
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
}}}
''myext--1.0.sql''
#geshi(sql){{{
\echo Use "CREATE EXTENSION myext" to load this file. \quit
CREATE OR REPLACE FUNCTION myext()
RETURNS BOOL
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
}}}
''myext.c''
#geshi(c){{{
#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(myext);
Datum
myext(PG_FUNCTION_ARGS)
{
PG_RETURN_BOOL(true); // ただtrueを返すだけの関数
}
}}}
この内容で作成される拡張関数だが,以下のような結果を返すものである。
#geshi{{{
postgres=# select myext();
myext
-------
t
(1 row)
}}}
* Makefile [#f07ae49f]
- MODULES
-- 同じ系のソースから生成される共有ライブラリのリスト。
-- myext.cとmyext2.cがあれば,myext.soとmyext2.soが作成される。
#geshi(Make){{{
# myext.soとmyext2.soがターゲットリストに加わる
MODULES = myext myext2
}}}
- MODULE_big
-- 作成する共有ライブラリ。
-- OBJSにオブジェクトファイルを列挙する。
#geshi(Make){{{
# myext.soターゲットが作成される
# イメージ)
# myext.so: $(OBJS)
MODULE_big = myext
OBJS = myext.o hoge.o
}}}
- PROGRAM
-- $(pg_config --bindir)にプログラムがインストールされる実行ファイル。
-- OBJSにオブジェクトファイルを列挙する。
#geshi(Make){{{
PROGRAM = myext
OBJS = myext.o hoge.o
}}}
- EXTENSION
-- 拡張の名前を指定する。$(EXTENSION).controlファイルもパッケージに必要。
#geshi(Make){{{
MODULES = myext
# 拡張の名前
# $(pg_config --sharedir)/extension/にmyext.controlがインストールされる
EXTENSION = myext
}}}
- MODULEDIR
-- DATAやDOCSのインストール先ディレクトリ。
-- $(pg_config --sharedir)/$(MODULEDIR)となる。~
指定がない場合は,EXTENSIONが使われる。EXTENSIONがなければcontribとなる。
#geshi(Make){{{
MODULES = myext
EXTENSION = myext
MODULEDIR = myext
DATA = myext--1.0.sql
}}}
- DATA
-- $(pg_config --sharedir)にインストールされるデータ。
#geshi(Make){{{
MODULES = myext
EXTENSION = myext
DATA = myext--1.0.sql
}}}
- DATA_built
-- $(pg_config --sharedir)にインストールされるデータ。
-- メモ:DATAとの違いがよく分からない,DATAとの違いは$(srcdir)のプリフィックスはつかないこと?
#geshi(Make){{{
MODULES = myext
EXTENSION = myext
DATA_built = myext--1.0.sql
}}}
- DOCS
-- $(pg_config --docdir)にインストールされるドキュメント。
#geshi(Make){{{
MODULES = myext
DOCS = $(addsuffix .example, $(MODULES))
}}}
- SCRIPTS
-- $(pg_config --bindir)にプログラムがインストールされるスクリプト。
#geshi(Make){{{
SCRIPTS = myext.sh
}}}
- SCRIPTS_built
-- $(pg_config --bindir)にプログラムがインストールされるスクリプト。
-- メモ:SCRIPTSとの違いがよく分からない,SCRIPTSとの違いは$(srcdir)のプリフィックスはつかないこと?
#geshi(Make){{{
SCRIPTS_built = myext.sh
}}}
- REGRESS
--リグレッションテストのリスト(.sqlは不要)。
#geshi(Make){{{
# sql/myext.sqlが実行される
REGRESS = myext
}}}
- REGRESS_OPTS
-- リグレッションテストの実行引数。pg_regressに渡すオプション。
#geshi(Make){{{
# portオプションを渡す。
# (デフォルトと異なるportで動作している場合)
REGRESS_OPTS += --port $(PG_PORT)
}}}
- EXTRA_CLEAN
--
#geshi(Make){{{
}}}
- PG_CPPFLAGS
--
#geshi(Make){{{
}}}
- PG_LIBS
--
#geshi(Make){{{
}}}
- SHLIB_LINK
--
#geshi(Make){{{
}}}
- PG_CONFIG
--
#geshi(Make){{{
}}}
- PGFILEDESC
--
#geshi(Make){{{
}}}
* 制御ファイル(.control) [#lb562e94]
* CREATE EXTENSION [#cbd4361b]
** CreateExtension実行の流れ [#ga2effea]
CREATE EXTENSION呼び出しの流れをデバッガのスタックトレースを実行し俯瞰する。~
まずは,以下のコマンドを実行してみる。myextは,ページ内の&size(12){&color(white,#00afcc){ サンプル1 };};を参照。
#geshi(sql){{{
CREATE EXTENSION myext;
}}}
最終的にはpg_dlsymが呼ばれるだろうと想定しブレークポイントを貼っておく。以下,コマンドを実行しブレークポイントで中断させた時のスタックトレースである。
#geshi(){{{
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 24.1
* frame #0: 0x000000010edba000 postgres`pg_dlsym(...) at dynloader.c:38 <-- 最終的にシステムコールで共有オブジェクトの読み込み
frame #1: 0x000000010f0083bd postgres`internal_load_library(...) at dfmgr.c:240 <-- ライブラリのロード
frame #2: 0x000000010f007d7f postgres`load_external_function(...) at dfmgr.c:105
frame #3: 0x000000010eb8f532 postgres`fmgr_c_validator(...) at pg_proc.c:823
frame #4: 0x000000010f00c143 postgres`OidFunctionCall1Coll(...) at fmgr.c:1596
frame #5: 0x000000010eb8f0bb postgres`ProcedureCreate(...) at pg_proc.c:726
frame #6: 0x000000010ec45204 postgres`CreateFunction(...) at functioncmds.c:1048
frame #7: 0x000000010ee7520e postgres`ProcessUtilitySlow(...) at utility.c:1361
frame #8: 0x000000010ee73878 postgres`standard_ProcessUtility(...) at utility.c:892
frame #9: 0x000000010ee7299d postgres`ProcessUtility(...) at utility.c:334
frame #10: 0x000000010ec3f486 postgres`execute_sql_string(...) at extension.c:744 <-- インストールスクリプト実行
frame #11: 0x000000010ec39764 postgres`execute_extension_script(...) at extension.c:904
frame #12: 0x000000010ec386af postgres`CreateExtension(...) at extension.c:1461 <-- ここでCREATE EXTENSIONコマンド実行
frame #13: 0x000000010ee74cb9 postgres`ProcessUtilitySlow(...) at utility.c:1281
frame #14: 0x000000010ee73878 postgres`standard_ProcessUtility(...) at utility.c:892
frame #15: 0x000000010ee7299d postgres`ProcessUtility(...) at utility.c:334
frame #16: 0x000000010ee72318 postgres`PortalRunUtility(...) at pquery.c:1183
frame #17: 0x000000010ee7155c postgres`PortalRunMulti(...) at pquery.c:1314
frame #18: 0x000000010ee70b6d postgres`PortalRun(...) at pquery.c:812
frame #19: 0x000000010ee6c527 postgres`exec_simple_query(...) at postgres.c:1104
frame #20: 0x000000010ee6b730 postgres`PostgresMain(...) at postgres.c:4045
frame #21: 0x000000010edd45e2 postgres`BackendRun(...) at postmaster.c:4253
frame #22: 0x000000010edd389d postgres`BackendStartup(...) at postmaster.c:3927
frame #23: 0x000000010edd2965 postgres`ServerLoop at postmaster.c:1698
frame #24: 0x000000010edd02d6 postgres`PostmasterMain(...) at postmaster.c:1306
frame #25: 0x000000010ed1097f postgres`main(...) at main.c:228
frame #26: 0x00007fffdb4e0235 libdyld.dylib`start + 1
frame #27: 0x00007fffdb4e0235 libdyld.dylib`start + 1
}}}
以下プロセスの概要を記述する。
- 制御ファイル(share/extension/myext.control)を読み込みパースする。
- バージョン指定があるか確認する。
-- CREATE EXTENSIONコマンドで指定されている又は.controlファイルのdefault_version指定が必要。
- 更新パスを計算する。
- pg_extensionテーブルに拡張機能の情報を登録及び依存エントリーを更新。
- .controlファイルに定義commentがあれば更新。
- インストールスクリプトを実行。
** CreateExtensionの実行 [#z6b8cc72]
&ref(./load_extension.png,70%);
CreateExtensionの実行は初回だけでよい。CREATE EXTENSIONコマンドでデータベースに登録されたデータベースオブジェクトはカタログテーブルから確認することができる。
#geshi(sql){{{
postgres=# select pe.*, PC.probin from pg_extension pe inner join pg_proc pc on (pe.extnamespace = pc.pronamespace) where extname = 'myext';
-[ RECORD 1 ]--+--------------
extname | myext
extowner | 10
extnamespace | 2200
extrelocatable | t
extversion | 1.0
extconfig |
extcondition |
probin | $libdir/myext
}}}
CREATE EXTENSIONコマンドは,バックエンドプロセス終了後,再度新規にプロセスを開始しても再実行する必要はない。関連する拡張機能の情報はデータベースに登録されており,カタログから参照できるからだ。psqlクライアントで接続後にCREATE EXTENSIONで登録した関数を含むクエリを実行してみると,FuncExprノードの評価時に関数オブジェクトに関連する共有オブジェクトがロードされていることが確認できる。関数オブジェクト(共有オブジェクトも)は初回ロード時にバックエンドプロセスのキャッシュテーブルに登録され,次回以降はキャッシュテーブルの情報が参照されるようになっている。
#geshi{{{
(...) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 22.1
* frame #0: 0x0000000106ee3d69 postgres`load_external_function(...) at dfmgr.c:102 <-- 共有オブジェクトをロードし_PG_initを呼ぶ。
frame #1: 0x0000000106eeab29 postgres`fmgr_info_C_lang(...) at fmgr.c:351
frame #2: 0x0000000106ee5647 postgres`fmgr_info_cxt_security(...) at fmgr.c:282
frame #3: 0x0000000106ee56c5 postgres`fmgr_info_cxt(...) at fmgr.c:172
frame #4: 0x0000000106b94991 postgres`init_fcache(...) at execQual.c:1328
frame #5: 0x0000000106b993d9 postgres`ExecEvalFunc(...) at execQual.c:2393
frame #6: 0x0000000106b9e603 postgres`ExecTargetList(...) at execQual.c:5401
frame #7: 0x0000000106b9e4fe postgres`ExecProject(...) at execQual.c:5625
frame #8: 0x0000000106bbf57c postgres`ExecResult(...) at nodeResult.c:155
frame #9: 0x0000000106b92aaa postgres`ExecProcNode(...) at execProcnode.c:385
frame #10: 0x0000000106b8e1e3 postgres`ExecutePlan(...) at execMain.c:1549
frame #11: 0x0000000106b8e111 postgres`standard_ExecutorRun(...) at execMain.c:337
frame #12: 0x0000000106b8df5a postgres`ExecutorRun(...) at execMain.c:285
frame #13: 0x0000000106d4d081 postgres`PortalRunSelect(...) at pquery.c:942
frame #14: 0x0000000106d4ca74 postgres`PortalRun(...) at pquery.c:786
frame #15: 0x0000000106d48527 postgres`exec_simple_query(...) at postgres.c:1104
frame #16: 0x0000000106d47730 postgres`PostgresMain(...) at postgres.c:4045
frame #17: 0x0000000106cb05e2 postgres`BackendRun(...) at postmaster.c:4253
frame #18: 0x0000000106caf89d postgres`BackendStartup(...) at postmaster.c:3927
frame #19: 0x0000000106cae965 postgres`ServerLoop at postmaster.c:1698
frame #20: 0x0000000106cac2d6 postgres`PostmasterMain(...) at postmaster.c:1306
frame #21: 0x0000000106bec97f postgres`main(...) at main.c:228
frame #22: 0x00007fffa402e235 libdyld.dylib`start + 1
frame #23: 0x00007fffa402e235 libdyld.dylib`start + 1
}}}
&size(12){&color(white,orange){ 参考 };}; [[load_external_function()>https://doxygen.postgresql.org/dfmgr_8c.html#a2f33d84731fc5a01efcede6bd27f03bd]] - &size(11){&color(gray){on doxygen.postgresql.org};};
ちなみに関数に関する情報は,スタックトレースの上流でカタログテーブルから読み込みされている。具体的には,parser/parse_func.cのParseFuncOrColumn関数である。以下関連するリンクを掲載する。
&size(12){&color(white,orange){ 参考 };};
- [[ParseFuncOrColumn()>https://doxygen.postgresql.org/parse__func_8c.html#a7fea264d34da42755e685ee2743296a9]] - &size(11){&color(gray){on doxygen.postgresql.org};};
- [[func_get_detail()>https://doxygen.postgresql.org/parse__func_8c.html#a0a4ba3cefe6144064f7cfdd0525e0499]] - &size(11){&color(gray){on doxygen.postgresql.org};};
** shared_preload_librariesとの違い [#z090e855]
shared_preload_librariesという設定があるが,これはpostgres起動時にライブラリをプレロードするための設定である。こちらは,EXTENSIONとは異なり制御ファイルやインストールスクリプトは必要ない。サーバー起動時,postmasterプロセスがServerLoopに到達するより前の時点で共有オブジェクトがロードされる。サーバー起動時にのみ操作可能な処理を行なうような拡張機能はこの設定を利用する。
&size(12){&color(white,orange){ 参考 };}; [[PostmasterMain()>https://doxygen.postgresql.org/postmaster_8c.html#a6978ea29d43da35d7325984bf3c6c239]] - &size(11){&color(gray){on doxygen.postgresql.org};};
また,類似の設定があるので合わせてメモしておく。
|~設定|~説明|設定場所例|h
|shared_preload_libraries|サーバー起動時にのみ有効な設定。ライブラリをプレロードする。|postgresql.conf,pg_ctlオプション,PGOPTIONS|
|session_preload_libraries|セッション起動時に有効な設定。ライブラリをプレロードする。スーパーユーザのみが指定可能。|postgresql.conf,pg_ctlオプション,PGOPTIONS,ALTER ROLE|
|local_preload_libraries|セッション起動時に有効な設定。ライブラリをプレロードする。任意のユーザーが指定可能。&br;ただし,ライブラリのパスは,$libidr/pluginsに限定されている。&br;データベース管理者がこのディレクトリに安全と判断されたライブラリを設置し,ユーザーが任意にロードできるような仕組みらしい。|postgresql.conf,pg_ctlオプション,PGOPTIONS,ALTER ROLE|
&size(12){&color(white,#00afcc){ サンプル };};
#geshi(c){{{
# pg_ctlの例
pg_ctl start -w -o "-c session_preload_libraries=myext"
# PGOPTIONSの例
export PGOPTIONS="-c local_preload_libraries=myext"
psql -U guest postgres
# ALTER ROLEの例
ALTER ROLE guest SET session_preload_libraries = myext;
}}}
** shared_preload_libraries指定有無による共有オブジェクトロードの確認 [#e5f54467]
myext共有オブジェクトがロードされているかデバッガーで確認してみる。~
postgresql.confに拡張機能を指定し,psqlで接続後にバックグラウンドプロセスにアタッチする。
#geshi{{{
# postgresql.conf
shared_preload_libraries = 'myext'
}}}
''shared_preload_librariesの指定がない場合''
myextシンボルは確認できない。
#geshi{{{
(lldb) image lookup -r -n myext
}}}
''shared_preload_librariesの指定をした場合''
myextシンボルが確認できる。
#geshi{{{
(lldb) image lookup -r -n myext
2 matches found in /Users/guest/workspace/PostgresAnalysis/pg/lib/postgresql/myext.so:
Address: myext.so[0x0000000000000f10] (myext.so.__TEXT.__text + 144)
Summary: myext.so`myext at myext.c:28 Address: myext.so[0x0000000000000f00] (myext.so.__TEXT.__text + 128)
Summary: myext.so`pg_finfo_myext at myext.c:24
(lldb)
}}}
#ref(./load_extension_preload.png,70%);
#br
&size(12){&color(white,#00afcc){ サンプル };}; &ref(./shared.tar.gz,100%);
** CREATE EXTENSIONを使わずにオブジェクトを登録する [#d6d07e42]
インストールスクリプトに記述されている内容をそのまま実行すれば類似のことが可能ではあるが,段階的な更新スクリプトの実行やEXTENSIONに関連するオブジェクト一式の紐付けが行われない。
''例:'' 個別にインストールスクリプトの内容を実行した場合
#geshi(sql){{{
CREATE OR REPLACE FUNCTION myext()
RETURNS BOOL
AS '$libdir/myext.so'
LANGUAGE C STRICT;
}}}
pg_extensionに情報は登録されないため,DROP EXTENSION myext;コマンドで関連オブジェクト一式を削除するといった操作ができない。
&size(12){&color(white,orange){ 参考 };}; [[RemoveObjects()>https://doxygen.postgresql.org/defrem_8h.html#a4118ef5c82dc7acd581edadee49b988c]] - &size(11){&color(gray){on doxygen.postgresql.org};};
* 参考リンク [#w9a5a10f]
- [[extension.c>https://doxygen.postgresql.org/extension_8c.html]] - &size(11){&color(gray){ on doxygen.postgresql.org };};
- [[CreateExtension()>https://doxygen.postgresql.org/extension_8c.html#a2993f062a957c566d558e3b7eb4f3ef7]] - &size(11){&color(gray){ on doxygen.postgresql.org };};
- [[AlterExtension()>https://doxygen.postgresql.org/extension_8c.html#a97cfb4bd687671e9ff78ce6bfed81381]] - &size(11){&color(gray){ on doxygen.postgresql.org };};
- [[parse_extension_control_file()>https://doxygen.postgresql.org/extension_8c.html#a58f04078439229aa40e334f9bea7ae50]] - &size(11){&color(gray){ on doxygen.postgresql.org };};
- [[35.15. 関連するオブジェクトを拡張としてパッケージ化>https://www.postgresql.jp/document/9.5/html/extend-extensions.html]] - &size(11){&color(gray){ [[PostgreSQL 9.5.4文書>https://www.postgresql.jp/document/9.5/]] };};
- [[35.16. 拡張構築基盤>https://www.postgresql.jp/document/9.5/html/extend-pgxs.html]] - &size(11){&color(gray){ [[PostgreSQL 9.5.4文書>https://www.postgresql.jp/document/9.5/]] };};
- [[18.11. クライアント接続デフォルト>https://www.postgresql.jp/document/9.5/html/runtime-config-client.html]] - &size(11){&color(gray){ [[PostgreSQL 9.5.4文書>https://www.postgresql.jp/document/9.5/]] };};
* コメント [#q2e0e72b]
#comment