PostgreSQL/開発

概要

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

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

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

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
    • ソースコード。もちろん複数のソースコードのコンパイル&リンクも問題ない。

 サンプル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

  • MODULES
    • 同じ系のソースから生成される共有ライブラリのリスト。
    • myext.cとmyext2.cがあれば,myext.soとmyext2.soが作成される。
      # myext.soとmyext2.soがターゲットリストに加わる
      MODULES = myext myext2
  • MODULE_big
    • 作成する共有ライブラリ。
    • OBJSにオブジェクトファイルを列挙する。
      # myext.soターゲットが作成される
      # イメージ)
      #   myext.so: $(OBJS)
      MODULE_big = myext
      OBJS = myext.o hoge.o
  • PROGRAM
    • $(pg_config --bindir)にプログラムがインストールされる実行ファイル。
    • OBJSにオブジェクトファイルを列挙する。
      PROGRAM = myext
      OBJS = myext.o hoge.o
  • EXTENSION
    • 拡張の名前を指定する。$(EXTENSION).controlファイルもパッケージに必要。
      MODULES = myext
      # 拡張の名前
      #   $(pg_config --sharedir)/extension/にmyext.controlがインストールされる
      EXTENSION  = myext
  • MODULEDIR
    • DATAやDOCSのインストール先ディレクトリ。
    • $(pg_config --sharedir)/$(MODULEDIR)となる。
      指定がない場合は,EXTENSIONが使われる。EXTENSIONがなければcontribとなる。
      MODULES = myext
      EXTENSION = myext
      MODULEDIR = myext
      DATA = myext--1.0.sql
  • DATA
    • $(pg_config --sharedir)にインストールされるデータ。
      MODULES = myext
      EXTENSION = myext
      DATA = myext--1.0.sql
  • DATA_built
    • $(pg_config --sharedir)にインストールされるデータ。
    • メモ:DATAとの違いがよく分からない,DATAとの違いは$(srcdir)のプリフィックスはつかないこと?
      MODULES = myext
      EXTENSION = myext
      DATA_built = myext--1.0.sql
  • DOCS
    • $(pg_config --docdir)にインストールされるドキュメント。
      MODULES = myext
      DOCS = $(addsuffix .example, $(MODULES))
  • SCRIPTS
    • $(pg_config --bindir)にプログラムがインストールされるスクリプト。
      SCRIPTS = myext.sh
  • SCRIPTS_built
    • $(pg_config --bindir)にプログラムがインストールされるスクリプト。
    • メモ:SCRIPTSとの違いがよく分からない,SCRIPTSとの違いは$(srcdir)のプリフィックスはつかないこと?
      SCRIPTS_built = myext.sh
  • REGRESS
    • リグレッションテストのリスト(.sqlは不要)。
      # sql/myext.sqlが実行される
      REGRESS = myext
  • REGRESS_OPTS
    • リグレッションテストの実行引数。pg_regressに渡すオプション。
      # portオプションを渡す。
      # (デフォルトと異なるportで動作している場合)
      REGRESS_OPTS += --port $(PG_PORT)
  • EXTRA_CLEAN
    • make cleanで削除されるデータ
      # rm -rf $(EXTRA_CLEAN)
      EXTRA_CLEAN = my.log
  • PG_CPPFLAGS
    • CPPFLAGSに追加されるビルドオプション
      CPPFLAGS = -std=c++11
  • PG_LIBS
    • PROGRAMのリンクに追加されるライブラリ
      PG_LIBS = /usr/local/postgres/libs/hello.so
  • SHLIB_LINK
    • MODULE_bigリンクに追加されるライブラリ
      MODULE_big = /usr/local/postgres/libs/hello.so
  • PG_CONFIG
    • pg_configコマンド(pg_configは,bindirやsharedirなどの情報を出力する)
      PG_CONFIG = pg_config
  • PGFILEDESC
    • ライブラリの概要
    • dllのファイル説明に登録される(win)
      PGFILEDESC = "myext - always returns true"

制御ファイル(.control)

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

  • directory (string)
    • 拡張のスクリプトファイルがあるディレクトリ。
    • 指定がなければ,"`pg_config --sharedir`/extension"となる。絶対パス指定の場合は指定された値が,相対パスの場合はSHAREDIRからの総体パスとなる。
      # デフォルト
      #directory = 'extension'
      directory = 'myext'
       参考  get_extension_script_directory() - on doxygen.postgresql.org
  • requires (string)
    • 拡張が依存する拡張機能のリストで,カンマ区切りで指定する。
      requires = 'myext1, myext2'
  • relocatable (boolean)
    • 拡張機能をインストールしたスキーマから移動可能か否か。trueの場合は移動可能。
    • relocatable = falseの場合は,schema変数を指定してスクリプト内の@extschema@置換変数を使ってスキーマ指定することができる。
      relocatable = true
  • schema (string)
    • 再配置可能ではない(relocatable = false)の拡張に対して指定する,オブジェクトをインストールするスキーマ。relocatable = trueでこの値を指定するとエラーになる。
    • 拡張機能が指定のスキーマにインストールされるよう強制する。通常指定がない場合は,publicとなるだろう。
      schema = 'sales'

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

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

  • 制御ファイル(share/extension/myext.control)を読み込みパースする。
  • バージョン指定があるか確認する。
    • CREATE EXTENSIONコマンドで指定されている又は.controlファイルのdefault_version指定が必要。
  • 更新パスを計算する。
  • pg_extensionテーブルに拡張機能の情報を登録及び依存エントリーを更新。
  • .controlファイルに定義commentがあれば更新。
  • インストールスクリプトを実行。

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 35件 [詳細] filemyext.tar.gz 39件 [詳細] fileload_extension_preload.png 42件 [詳細] fileshared.tar.gz 36件 [詳細]
[PR]

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-04-27 (金) 03:28:23 (205d)
GO TO TOP