35.15. 関連するオブジェクトを拡張としてパッケージ化

PostgreSQLへの有用な拡張は通常、複数のSQLオブジェクトを含んでいます。 例えば、新しいデータ型は新しい関数、新しい演算子、おそらく新しいインデックス演算子クラスを必要とします。 これらのオブジェクトをすべて単一のパッケージとしてまとめることは、データベース管理を単純化するために役に立ちます。 PostgreSQLではこうしたパッケージを拡張とよびます。 拡張を定義するためには、少なくとも、拡張のオブジェクトを作成するためのSQLコマンドを含むスクリプトファイル、拡張自身の数個の基本属性を指定する制御ファイルが必要です。 また拡張がCコードを含む場合、通常Cコードで構築された共有ライブラリが存在します。 これらのファイルがあれば、単純なCREATE EXTENSIONコマンドがそのオブジェクトをデータベース内に読み込みます。

拡張を使用する主な利点は、SQLスクリプトを実行するだけでデータベースに"粗な"なオブジェクトの群をロードできることではなく、PostgreSQLが拡張のオブジェクトをまとまったものと理解できることです。 単一のDROP EXTENSIONコマンドでオブジェクトすべてを削除することができます(個々の"アンインストール"スクリプトを保守する必要はありません)。 もっと有用なことは、pg_dumpが拡張の個々のメンバオブジェクトを削除してはならないことを把握していることです。 代わりにダンプ内にはCREATE EXTENSIONコマンドだけが含まれます。 これは、古いバージョンよりも多くのまたは異なるオブジェクトを含む可能性がある、拡張の新しいバージョンへの移行を大きく単純化します。 しかし、こうしたダンプを新しいデータベースにロードする際には、拡張の制御ファイル、スクリプトファイル、その他のファイルが利用できるようにしておく必要があります。

PostgreSQLはユーザに、拡張全体を削除させる以外に、拡張内に含まれる個々のオブジェクトを削除させません。 また、拡張のメンバオブジェクトの定義を変更する(例えば関数ではCREATE OR REPLACE FUNCTIONを介して変更する)ことはできますが、変更した定義はpg_dumpによりダンプされないことに留意してください。 こうした変更は通常、同時に拡張のスクリプトファイルにも同じ変更を行った場合のみ認識することができます。 (しかし後述するように設定データを持つテーブルに対しては特殊な準備があります。)

また拡張機構は、拡張に含まれるSQLオブジェクトの定義を調整するパッケージ調整スクリプトを準備しています。 例えば、拡張のバージョン1.1でバージョン1.0と比べて1つの関数を追加し、他の関数本体を変更する場合、拡張の作成者はこれらの2つの変更のみを行う更新スクリプトを提供することができます。 そしてALTER EXTENSION UPDATEコマンドを使用して、これらの変更を適用し、指定されたデータベース内に実際にインストールされた拡張のバージョンが何かを記録します。

拡張のメンバとなり得るSQLオブジェクトの種類をALTER EXTENSIONで説明します。 拡張は1つのデータベースの中でのみ認識されますので、データベース、ロール、テーブル空間などデータベースクラスタ全体のオブジェクトは拡張のメンバにすることができないことに注意してください。 (拡張のスクリプトでこうしたオブジェクトを生成することは禁止されていませんが、作成したとしても、拡張の一部として記録されません。) また、テーブルは拡張のメンバになることができますが、インデックスなどそれに付随するオブジェクトは拡張の直接的なメンバとはみなされません。

35.15.1. 拡張のファイル

CREATE EXTENSIONコマンドは各拡張に関して、拡張と同じ名前に.controlという拡張子を持つファイル名である必要がある、制御ファイルに依存します。 また、このファイルはインストレーションのSHAREDIR/extensionディレクトリ内に存在しなければなりません。 また少なくとも1つの、extension--version.sqlという命名規約(例えばfoo拡張のバージョン1.0ではfoo--1.0.sql)に従ったSQLスクリプトファイルが存在しなければなりません。 デフォルトでは、このスクリプトファイルもSHAREDIR/extensionディレクトリに格納されますが、制御ファイルでスクリプトファイルを別のディレクトリに指定することができます。

拡張の制御ファイルのファイル書式はpostgresql.confファイルと同じです。 すなわち、parameter_name = valueという代入を1行当たり1つ記述します。 空行および#から始まるコメントが許されます。 単一の単語または数字ではない値にはすべて引用符で確実にくくってください。

制御ファイルは以下のパラメータを設定することができます。

directory (string)

拡張のSQLスクリプトファイルを含むディレクトリです。 絶対パスで指定されていない限り、この名前はインストレーションのSHAREDIRディレクトリからの相対バスになります。 デフォルトの動作はdirectory = 'extension'と指定した場合と同じです。

default_version (string)

拡張のデフォルトのバージョン(CREATE EXTENSIONでバージョン指定がない場合にインストールされるバージョン)です。 これは省略することができますが、その場合VERSIONオプションがないCREATE EXTENSIONは失敗します。 ですので通常省略しようとは思わないでしょう。

comment (string)

拡張に関するコメント(任意の文字列)です。 この他の方法として、スクリプトファイル内でCOMMENTコマンドを使用してコメントを設定することができます。

encoding (string)

スクリプトファイルで使用される文字セット符号化方式です。 スクリプトファイルに何らかの非ASCII文字が含まれる場合に指定しなければなりません。 指定がなければ、ファイルはデータベース符号化方式であると仮定されます。

module_pathname (string)

このパラメータの値でスクリプトファイル内のMODULE_PATHNAMEの出現箇所が置換されます。 設定されていない場合は置換は行われません。 通常これは、スクリプトファイル内で共有ライブラリの名前を直接書き込む必要がなくなるように$libdir/shared_library_nameに設定され、C言語関数ではCREATE FUNCTIONコマンド中でMODULE_PATHNAMEを使用します。

requires (string)

拡張が依存する拡張の名前のリストです。 例えばrequires = 'foo, bar'です。 対象の拡張がインストールできるようになる前に、これらの拡張がインストールされていなければなりません。

superuser (boolean)

このパラメータがtrue(デフォルト)の場合、スーパーユーザのみが拡張を作成または新しいバージョンに更新することができます。 falseに設定されている場合は、インストレーション内でコマンドを実行するまたはスクリプトを更新するために必要な権限のみが必要とされます。

relocatable (boolean)

拡張を最初に作成した後に拡張により含まれるオブジェクトを別のスキーマに移動することができる場合、拡張は再配置可能です。 デフォルトはfalse、つまり、拡張は再配置可能ではありません。 詳しくは後で説明します。

schema (string)

このパラメータは再配置可能ではない拡張に対してのみ設定することができます。 拡張が指名したスキーマのみにロードされ、他にはロードされないことを強制します。 詳しくは後で説明します。

主制御ファイルextension.controlに加え、拡張はextension--version.controlという形の名前の副制御ファイルを持つことができます。 これらを提供する場合は、スクリプトファイルディレクトリに格納しなければなりません。 副制御ファイルは主制御ファイルと同じ書式に従います。 拡張の対応するバージョンをインストールまたは更新する時、副制御ファイル内で設定されるパラメータはいずれも、主制御ファイルを上書きします。 しかしdirectoryおよびdefault_versionパラメータは副制御ファイルで設定することはできません。

拡張のSQLスクリプトファイルにはトランザクション制御コマンド(BEGINCOMMITなど)およびトランザクションブロックの内側で実行することができないコマンド(VACUUMなど)を除く任意のSQLコマンドを含めることができます。 スクリプトファイルが暗黙的にトランザクションブロック内で実行されるためです。

拡張のSQLスクリプトファイルには、\echoから始まる行を含めることができます。 この行は拡張の機構では無視されます(コメントとして扱われます)。 これは、このスクリプトがCREATE EXTENSION(後述のスクリプト例を参照)ではなくpsqlに渡された場合にエラーを発生するために一般的に使用するために用意されたものです。 これがないと、ユーザは間違って拡張としてではなく、"まとまっていまい"オブジェクトとして拡張の内容をロードしてしまい、復旧が多少困難な状態になる可能性があります。

スクリプトファイルは指定した符号化方式で認められる任意の文字を含めることができますが、PostgreSQLが制御ファイルの符号化方式が何かを把握する方法がありませんので、制御ファイルにはASCII文字のみを含めなければなりません。 実際には、拡張のコメントに非ASCII文字を含めたい場合にのみ、これが問題になります。 このような場合には、制御ファイルのcommentを使用せず、代わりにコメントを設定するためにスクリプトファイル内でCOMMENT ON EXTENSIONを使用することを勧めます。

35.15.2. 拡張の再配置性

ユーザは拡張に含まれるオブジェクトを拡張の作成者が考えていたスキーマとは別のスキーマにロードしたいとよく考えます。 再配置性に関して3つのレベルがサポートされます。

すべての場合において、スクリプトファイルは対象のスキーマを指し示すようにあらかじめ設定したsearch_pathを用いて実行されます。 つまりCREATE EXTENSIONは以下と同じことを行います。

SET LOCAL search_path TO @extschema@;

これによりスクリプトファイルで作成されるオブジェクトを対象のスキーマ内に格納することができます。 スクリプトファイルは要望に応じてsearch_pathを変更することができますが、一般的には望まれません。 CREATE EXTENSIONの実行後、search_pathは以前の設定に戻されます。

対象のスキーマは制御ファイル内のschemaパラメータがあればこのパラメータにより決定されます。 このパラメータがなければ、CREATE EXTENSIONSCHEMAがあればこの値で決まり、これ以外の場合は現在のデフォルトのオブジェクト生成用スキーマ(呼び出し元のsearch_pathの最初のもの)になります。 制御ファイルのschemaパラメータが使用される時、対象のスキーマが存在しない場合は作成されますが、これ以外の2つの場合ではすでに存在しなければなりません。

何らかの事前に必要な拡張が制御ファイル内のrequiresに列挙されていた場合、それらの対象スキーマがsearch_pathの初期設定に追加されます。 これにより新しい拡張のスクリプトファイルからそれらのオブジェクトが可視になります。

再配置不可能な拡張は複数スキーマにまたがるオブジェクトを含めることができますが、通常、外部使用を意図したオブジェクトはすべて単一スキーマに格納することが望まれます。 この単一スキーマが拡張の対象のスキーマとみなされます。 こうした調整は依存する拡張を作成する間、デフォルトのsearch_path設定を都合に合わせて扱います。

35.15.3. 拡張設定テーブル

一部の拡張は、拡張をインストールした後でユーザにより追加または変更される可能性があるデータを持つ設定テーブルを含みます。 通常、テーブルが拡張の一部である場合、テーブル定義もその内容もpg_dumpによりダンプされません。 しかしこの振舞いは設定テーブルの場合望まれません。 ユーザによってなされたデータ変更はダンプ内に含まれなければなりません。 さもないとダンプしリストアした後で拡張の動作が変わってしまいます。

この問題を解消するために、拡張のスクリプトファイルでは設定テーブルとして作成されるテーブルに印を付け、pg_dumpにテーブルの内容をダンプに含める(定義は含まれません)ようにさせることができます。 このためには、以下の例のようにテーブルを作成した後にpg_extension_config_dump(regclass, text)関数を呼び出してください。

CREATE TABLE my_config (key text, value text);

SELECT pg_catalog.pg_extension_config_dump('my_config', '');

任意数のテーブルをこの方法で印付けることができます。

pg_extension_config_dumpの第2引数が空文字列である場合、テーブルのすべての内容がpg_dumpによりダンプされます。 これは、拡張のスクリプトによって作成された初期段階においてテーブルが空である場合のみ正しいものです。 テーブルの中で初期データとユーザが提供したデータが混在する場合、pg_extension_config_dumpの第2引数においてダンプすべきデータを選択するWHERE条件を提供します。 以下に例を示します。

CREATE TABLE my_config (key text, value text, standard_entry boolean);

SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entry');

このようにした後、拡張のスクリプトで作成される行のみでstandard_entryが確実に真になるようにします。

初期状態で提供される行がユーザによって変更されるようなもっと複雑な状況では、設定テーブルに対するトリガを作成して、変更された行が正しく印付けられることを確実にするように取り扱うことができます。

35.15.4. 拡張の更新

拡張機構の1つの利点は、拡張のオブジェクトを定義するSQLコマンドの更新を簡便に管理する方法を提供していることです。 これは、拡張のインストール用スクリプトのリリース版それぞれにバージョン名称またはバージョン番号を関連付けることで行われます。 さらに、ユーザにあるバージョンから次のバージョンへ動的にデータベースを更新させることができるようにしたい場合、あるバージョンから次のバージョンまでの間に行われる必要な変更を行う更新スクリプトを提供しなければなりません。 更新スクリプトはextension--oldversion--newversion.sqlというパターンに従った名前(例えば、foo--1.0--1.1.sqlfoo拡張のバージョン1.0からバージョン1.1に変更するコマンドを含みます。)を持たなければなりません。

適切な更新スクリプトが利用可能である場合、ALTER EXTENSION UPDATEコマンドはインストール済みの拡張を指定した新しいバージョンへ更新します。 更新スクリプトは、CREATE EXTENSIONがインストール用スクリプト向けに提供する環境と同じ環境で実行されます。 具体的にはsearch_pathは同じ方法で設定され、スクリプトにより作成される新しいオブジェクトはすべて自動的に拡張に追加されます。

拡張が副制御ファイルを持つ場合、更新スクリプトで使用される制御パラメータは、スクリプトの対象の(新しい)バージョンに関連付けされたものになります。

更新機構を使用して、オブジェクトの"粗"集合から拡張に変換するという、特別かつ重大な状況を解消することができます。 拡張機構がPostgreSQLに(9.1で)追加されるようになる前では、パッケージ化されずに単に詰めあわされたオブジェクトを作成する拡張モジュールを多くのユーザが作成していました。 こうしたオブジェクトを持つデータベースが存在する場合、どのようにすればこれらのオブジェクトを適切にパッケージ化された拡張に変換できるでしょうか? 削除した後で普通にCREATE EXTENSIONを行うことも1つの方法ですが、オブジェクトに依存関係がある(例えば拡張により作成されたデータ型のテーブル列が存在する場合など)場合は好まれません。 こうした状況を解消する方法は、空の拡張を作成し、ALTER EXTENSION ADDを使用して、既存のオブジェクトそれぞれを拡張に関連づけ、最後にパッケージ化されていないリリースに存在しないが現在のバージョンの拡張には存在する新しいオブジェクトを作成するという方法です。 CREATE EXTENSIONFROM old_versionオプションでこの状況をサポートします。 この場合、通常のインストール用スクリプトは実行されず、代わりにextension--old_version--target_version.sqlという名前の更新スクリプトが実行されるようになります。 old_versionとして使用するダミーのバージョン名の選択は拡張の作成者に任せられていますが、unpackagedがよく使われる規約です。 拡張形式に更新できるようにしたい過去のバージョンが複数存在する場合、それらを識別できるように複数のダミーバージョン番号を使用していください。

ALTER EXTENSIONは、要求される更新を実現するために更新スクリプトを連続して実行することができます。 例えばfoo--1.0--1.1.sqlfoo--1.1--2.0.sqlのみが利用可能であるとすると、現在1.0がインストールされている時にバージョン2.0への更新が要求された場合、ALTER EXTENSIONはこれらを順番に適用します。

PostgreSQLはバージョン名称の特性についてまったく仮定を行いません。 例えば1.0の次が1.1であるかどうかを把握しません。 これは利用可能なバージョン名をかみ合わせ、もっとも少ない数の更新スクリプトを適用するために必要な経路を続けるだけです。 (バージョン名には、--を含まず先頭または最後に-が付かなければ、任意の文字を取ることができます。)

"ダウングレード"スクリプトを提供することが便利な場合があります。 例えばfoo--1.1--1.0.sqlは、バージョン1.1に関連した変更を基に戻すことができます。 この場合、ダウングレードスクリプトがより短いパスを生成するために、予期せず適用されてしまう可能性に注意してください。 複数のバージョンをまたがって更新する"近道"更新スクリプトと近道の開始バージョンへのダウングレードスクリプトが存在する場合に危険性があります。 ダウングレードしてから近道となる更新スクリプトを実行する方が、バージョンを1つずつ進めるよりも少ない処理で済んでしまうかもしれません。 ダウングレードスクリプトが取り返しがつかないオブジェクトを何か削除してしまう場合、望まない結果になってしまいます。

想定外の更新経路かどうかを検査するためには、以下のコマンドを使用してください。

SELECT * FROM pg_extension_update_paths('extension_name');

これは指定した拡張の個々の既知のバージョン名の組み合わせをそれぞれ、元のバージョンから対象のバージョンへ進む時に取られる更新経路順、またはもし利用できる更新経路がなければNULLを付けて、表示します。 経路は--を区切り文字として使用したテキスト形式で表示されます。 配列形式の方が良ければregexp_split_to_array(path,'--')を使用することができます。

35.15.5. 拡張の例

ここでは、SQLのみの拡張の完全な例を示します。 "k""v"という名称の2つの要素からなる複合型であり、そのスロットには任意の型の値を格納することができるものです。 格納の際テキスト以外の値は自動的にテキストに変換されます。

pair--1.0.sqlスクリプトファイルは以下のようになります。

-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION pair" to load this file. \quit

CREATE TYPE pair AS ( k text, v text );

CREATE OR REPLACE FUNCTION pair(anyelement, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';

CREATE OR REPLACE FUNCTION pair(text, anyelement)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';

CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';

CREATE OR REPLACE FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';

CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = anyelement, PROCEDURE = pair);
CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = text, PROCEDURE = pair);
CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = anyelement, PROCEDURE = pair);
CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair);

pair.control制御ファイルは以下のようになります。

# pair extension
comment = 'A key/value pair data type'
default_version = '1.0'
relocatable = true

これらの2つのファイルを正しいディレクトリにインストールするためにメークファイルを作成する必要はほとんどありませんが、以下を含むMakefileを使用することができます。

EXTENSION = pair
DATA = pair--1.0.sql

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

このメークファイルは項35.16で説明するPGXSに依存します。 make installコマンドは制御ファイルとスクリプトファイルをpg_configで報告される正しいディレクトリにインストールします。

ファイルがインストールされた後、CREATE EXTENSIONコマンドを使用してオブジェクトを任意の特定のデータベースにロードしてください。