PostgreSQL/開発

概要

ここでは,拡張機能の開発で必要となる知識について整理する。
具体的には,拡張機能のディレクトリ構造,ビルドスクリプト,制御ファイルについて記述する。

拡張機能のディレクトリ構造

CREATE EXTENSIONとしてインストールされる拡張機能のディレクトリ構造は,以下のようになっている(ここでは,拡張機能を「myext」として説明を進める)。

myext
    |- myext.control
    |- myext--xx.sql
    |- Makefile
    |- myext.c

 サンプル1 trueを返すだけのmyext関数の作成

各ファイルの内容はざっと最小限で書いて以下のような感じになる。各ファイルの定義内容については後述する。

Makefile

MODULES = myext
EXTENSION = myext
DATA = myext--1.0.sql 

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

myext--1.0.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

#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を返すだけの関数                     
}

この内容で作成される拡張関数だが,以下のような結果を返すものである。

postgres=# select myext();
 myext 
-------
 t
(1 row)

Makefile

制御ファイル(.control)

CreateExtensionやAlterExtensionコマンド実行時に参照される制御設定用ファイルである。

CREATE EXTENSION

CreateExtension実行の流れ

CREATE EXTENSION呼び出しの流れをデバッガのスタックトレースを実行し俯瞰する。
まずは,以下のコマンドを実行してみる。myextは,ページ内の サンプル1 を参照。

CREATE EXTENSION myext;

最終的にはpg_dlsymが呼ばれるだろうと想定しブレークポイントを貼っておく。以下,コマンドを実行しブレークポイントで中断させた時のスタックトレースである。

(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

以下プロセスの概要を記述する。

CreateExtensionの実行

load_extension.png

CreateExtensionの実行は初回だけでよい。CREATE EXTENSIONコマンドでデータベースに登録されたデータベースオブジェクトはカタログテーブルから確認することができる。

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ノードの評価時に関数オブジェクトに関連する共有オブジェクトがロードされていることが確認できる。関数オブジェクト(共有オブジェクトも)は初回ロード時にバックエンドプロセスのキャッシュテーブルに登録され,次回以降はキャッシュテーブルの情報が参照されるようになっている。

(...) 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

 参考  load_external_function() - on doxygen.postgresql.org

ちなみに関数に関する情報は,スタックトレースの上流でカタログテーブルから読み込みされている。具体的には,parser/parse_func.cのParseFuncOrColumn関数である。以下関連するリンクを掲載する。

 参考 

shared_preload_librariesとの違い

shared_preload_librariesという設定があるが,これはpostgres起動時にライブラリをプレロードするための設定である。こちらは,EXTENSIONとは異なり制御ファイルやインストールスクリプトは必要ない。サーバー起動時,postmasterプロセスがServerLoopに到達するより前の時点で共有オブジェクトがロードされる。サーバー起動時にのみ操作可能な処理を行なうような拡張機能はこの設定を利用する。

 参考  PostmasterMain() - on doxygen.postgresql.org

また,類似の設定があるので合わせてメモしておく。

設定説明設定場所例
shared_preload_librariesサーバー起動時にのみ有効な設定。ライブラリをプレロードする。postgresql.conf,pg_ctlオプション,PGOPTIONS
session_preload_librariesセッション起動時に有効な設定。ライブラリをプレロードする。スーパーユーザのみが指定可能。postgresql.conf,pg_ctlオプション,PGOPTIONS,ALTER ROLE
local_preload_librariesセッション起動時に有効な設定。ライブラリをプレロードする。任意のユーザーが指定可能。
ただし,ライブラリのパスは,$libidr/pluginsに限定されている。
データベース管理者がこのディレクトリに安全と判断されたライブラリを設置し,ユーザーが任意にロードできるような仕組みらしい。
postgresql.conf,pg_ctlオプション,PGOPTIONS,ALTER ROLE

 サンプル 

# 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指定有無による共有オブジェクトロードの確認

myext共有オブジェクトがロードされているかデバッガーで確認してみる。
postgresql.confに拡張機能を指定し,psqlで接続後にバックグラウンドプロセスにアタッチする。

# postgresql.conf
shared_preload_libraries = 'myext'

shared_preload_librariesの指定がない場合

myextシンボルは確認できない。

(lldb) image lookup -r -n myext

shared_preload_librariesの指定をした場合

myextシンボルが確認できる。

(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) 
load_extension_preload.png
 

 サンプル  fileshared.tar.gz

CREATE EXTENSIONを使わずにオブジェクトを登録する

インストールスクリプトに記述されている内容をそのまま実行すれば類似のことが可能ではあるが,段階的な更新スクリプトの実行やEXTENSIONに関連するオブジェクト一式の紐付けが行われない。

例: 個別にインストールスクリプトの内容を実行した場合

CREATE OR REPLACE FUNCTION myext()
RETURNS BOOL
AS '$libdir/myext.so'
LANGUAGE C STRICT;

pg_extensionに情報は登録されないため,DROP EXTENSION myext;コマンドで関連オブジェクト一式を削除するといった操作ができない。

 参考  RemoveObjects() - on doxygen.postgresql.org

拡張の更新

拡張の再配置

参考リンク


添付ファイル: fileload_extension.png 226件 [詳細] fileload_extension_preload.png 240件 [詳細] filemyext.tar.gz 198件 [詳細] fileshared.tar.gz 176件 [詳細]

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP