インデックスアクセスメソッドがIndexAmRoutine
で提供しなければならない、インデックス構築および保守関数を以下に示します。
IndexBuildResult * ambuild (Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo);
新しいインデックスを構築します。
空のインデックスリレーションが物理的に作成されます。
これは、アクセスメソッドが必要とする何らかの固定データと、テーブル内に既に存在するすべてのタプルに対応する項目が書き込まれなければなりません。
通常、ambuild
関数はtable_index_build_scan()
を呼び出し、既存のタプルをテーブルからスキャンし、インデックスに挿入しなければならないキーを計算します。
この関数は、新しいインデックスに関する統計情報を含むpallocされた構造体を返さなければなりません。
void ambuildempty (Relation indexRelation);
空のインデックスを構築し、それを指定されたリレーションの初期フォーク(INIT_FORKNUM
)に書き出します。
このメソッドはログを取らないインデックスに対してのみ呼び出されます。
初期フォークに書き出された空のインデックスは、サーバの再起動の度に主リレーションフォークにコピーされます。
bool aminsert (Relation indexRelation, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRelation, IndexUniqueCheck checkUnique, bool indexUnchanged, IndexInfo *indexInfo);
既存のインデックスに新しいタプルを挿入します。
values
配列とisnull
配列がインデックスされるキー値を提供するもので、heap_tid
がインデックスされるTIDです。
アクセスメソッドが一意なインデックスをサポートする場合(そのamcanunique
が真の場合)、checkUnique
は実行する一意性検査の種類を示します。
これは一意性制約が遅延可能か否かによって変わります。
62.5を参照してください。
通常アクセスメソッドは、一意性検査を行う時にheapRelation
パラメータのみを必要とします
(タプルの有効性を検証するためにヒープ内を検索しなければなりません)。
indexUnchanged
真偽値はインデックス付されるタプルの性質に関するヒントを与えます。
真なら、そのタプルは既存のインデックス中のタプルと重複しています。
新しいタプルは論理的に変わっていない後継であるMVCCタプルバージョンです。
これはUPDATE
の実行によりインデックス対象のどの列も変更されず、それにもかかわらずインデックスにおいて新しいバージョンを要求する場合に起こります。
インデックスAMは、同じ論理的な行の多くのバージョンが蓄積されるときに、インデックスのある部分にボトムアップインデックス削除を適用するかどうかを決定するためにこのヒントを使うことができます。
非キー列を更新してもindexUnchanged
の値には影響がないことに留意してください。
checkUnique
がUNIQUE_CHECK_PARTIAL
の場合、関数の論理型の結果値で十分です。
この場合、真の結果は新しい項目は一意であることが確認されたことを、一方偽の結果は一意でない可能性があること(遅延一意性検査を予定しなければならないこと)を意味します。
他の場合では、一定の偽という結果が推奨されます。
一部のインデックスではすべてのタプルをインデックス付けしない可能性があります。
タプルがインデックス付けされない場合、aminsert
は何も行わずに戻らなければなりません。
SQL文の中で、インデックスAMがインデックスへの連続的な挿入をまたがってデータをキャッシュすることが望ましい場合は、indexInfo->ii_Context
にメモリを確保し、そのデータへのポインタをindexInfo->ii_AmCache
(初期値はNULLです)に格納することができます。
IndexBulkDeleteResult * ambulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state);
インデックスからタプル(複数可)を削除します。
これは「一括削除」操作を行いますが、インデックス全体をスキャンし、各項目に対して削除すべきかどうか検査を行うように実装されることが想定されています。
渡されるcallback
関数は、callback(
という形で、参照用TIDで識別されるインデックス項目を削除すべきかどうか決定するために呼び出さなければなりません。
NULLまたはpallocした削除操作の影響に関する統計情報を含む構造体を返さなければなりません。
TID
, callback_state) returns boolamvacuumcleanup
に渡さなければならない情報がなければ、NULLを返しても問題ありません。
maintenance_work_mem
の制限により、多くのタプルが削除される時、ambulkdelete
を複数回呼び出す必要があるかもしれません。
stats
引数は、このインデックスに対する前回の呼び出し結果です。
(VACUUM
操作における最初の呼び出しではこれはNULLです。)
これにより、アクセスメソッドは操作全体に跨った統計情報を計算することができます。
典型的に、渡されたstats
がNULLでない場合、ambulkdelete
は同じ構造体を変更し、返します。
IndexBulkDeleteResult * amvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats);
VACUUM
操作(0回以上のambulkdelete
呼び出し)後の整理を行います。
これは、インデックス統計情報を返す以上の処理を行う必要はありません。
しかし、空のインデックスページの回収などの一括整理を行う可能性があります。
stats
は最後のambulkdelete
呼び出しが返したものです。
削除する必要があるタプルが存在しなかったためにambulkdelete
が呼び出されなかった場合はNULLとなります。
結果はNULLでなければ、pallocされた構造体でなければなりません。
含まれる統計情報はpg_class
を更新するために使用され、また、VERBOSE
が指定されたVACUUM
によって報告されます。
VACUUM
操作の間にインデックスがまったく変わらなかった場合はNULLを返しても問題ありません。
しかし、そうでなければ正しい統計情報を返さなければなりません。
amvacuumcleanup
もANALYZE
操作の完了時点にも呼び出されます。
この場合、stats
は常にNULLで、戻り値はまったく無視されます。
この事象はinfo->analyze_only
を検査することで識別されます。
アクセスメソッドがそのような呼び出しで挿入後の整理以外何もしないように、そしてそれは自動バキュームワーカプロセスのみであるようにすることを推奨します。
bool amcanreturn (Relation indexRelation, int attno);
列のインデックスされた元の値を返すことにより、そのインデックスが指定された列でインデックスオンリースキャンをサポート可能かどうかを判断します。
属性番号は1始まり、すなわち最初の列のattnoは1です。
インデックスオンリースキャンがサポートされている場合は真が返され、サポートされていない場合は偽が返ります。
取得できない列がinclude列である意味はないので、この関数は(サポートされていれば)include列に対しては常に真を返すでしょう。
アクセスメソッドがインデックスオンリースキャンをサポートしていない場合、IndexAmRoutine
構造体のamcanreturn
フィールドをNULLにセットすることができます。
void amcostestimate (PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation, double *indexPages);
インデックススキャンのコストを推定します。 この関数については後述の62.6で説明します。
bytea * amoptions (ArrayType *reloptions, bool validate);
インデックス用のreloptionsの解析と検証を行います。
インデックスに非NULLのreloptions配列が存在する場合にのみ呼び出されます。
reloptions
は、name
=
value
形式の項目からなる、text
型の配列です。
この関数はbytea
型の値を生成しなければならず、この値はインデックスのrelcache項目のrd_options
フィールドにコピーされます。
bytea
型の値の内容はアクセスメソッドが独自に定義できるように開放されています。
標準のアクセスメソッドのほとんどはすべてStdRdOptions
構造体を使用します。
validate
が真の場合、何らかのオプションが認識できなかった場合や無効な値が存在した場合、この関数は適切なエラーメッセージを報告しなければなりません。
validate
が偽の場合、無効な項目は単に無視されます。
(読み込みオプションが既にpg_catalog
に格納されている場合validate
は偽です。
アクセスメソッドがそのオプション用の規則を変更した場合にのみ、無効な項目が検出されます。
そして、その場合、古い項目を無視することが適切です。)
デフォルトの動作を行わせたい場合はNULLを返しても問題ありません。
bool amproperty (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull);
amproperty
メソッドにより、インデックスメソッドはpg_index_column_has_property
および関連する関数のデフォルトの動作を上書きすることができます。
インデックスアクセスメソッドがインデックスの属性の問い合わせについて特別な動作をしないのなら、IndexAmRoutine
構造体のamproperty
フィールドはNULLにすることができます。
そうでなければ、amproperty
はpg_indexam_has_property
の呼び出しに対し、index_oid
とattno
をいずれもゼロにして、pg_index_has_property
の呼び出しに対してindex_oid
が有効、attno
がゼロで、あるいはpg_index_column_has_property
の呼び出しに対してindex_oid
が有効、attno
が1以上で呼び出されます。
prop
は検査対象の属性を指定する列挙型の値、propname
は元の属性の名称の文字列です。
コアのコードが属性名を認識しない場合、prop
はAMPROP_UNKNOWN
になります。
アクセスメソッドはカスタム属性名を定義して、マッチするものをpropname
で確認する(コアコードとの一貫性のため、pg_strcasecmp
を使ってください)ことができます。
コアコードに既知の名前については、prop
を検査する方が良いです。
amproperty
メソッドがtrue
を返すなら、それは属性検査の結果が決定したということで、*res
を返すべき論理値にセットするか、NULLを返すために*isnull
をtrue
にセットするかしなければなりません。
(どちらの参照変数も、呼び出しの前にfalse
に初期化されます。)
amproperty
メソッドがfalse
を返すなら、コアコードは属性検査の結果を決定するために、通常の手続きを進めます。
順序付け演算子をサポートするアクセスメソッドは、AMPROP_DISTANCE_ORDERABLE
の属性検査を実装する必要があります。
なぜなら、コアコードはそれをどうすれば良いか知らないため、NULLを返すからです。
コアコードのデフォルトの動作であるインデックスのオープンとamcanreturn
の呼び出しよりも安価にできるのであれば、AMPROP_RETURNABLE
の検査を実装するのは利点となります。
その他のすべての標準属性に対しては、デフォルトの動作が満足できるもののはずです。
char * ambuildphasename (int64 phasenum);
指定されたビルドフェーズ番号のテキスト名を返します。
フェーズ番号は、pgstat_progress_update_param
インタフェースを介してインデックス構築中に報告されたものです。
それから、フェーズ名はpg_stat_progress_create_index
ビューで公開されます。
bool amvalidate (Oid opclassoid);
指定の演算子クラスについて、アクセスメソッドが合理的に可能な範囲でカタログエントリを検証します。
例えば、これには必要なすべてのサポート関数が提供されていることのテストが含まれるかもしれません。
amvalidate
関数は演算子クラスが無効なときは偽を返さなければなりません。
問題があれば典型的にはINFO
レベルでereport
メッセージにより報告されます。
void amadjustmembers (Oid opfamilyoid, Oid opclassoid, List *operators, List *functions);
アクセスメソッドが合理的に可能な範囲で提案された新しい演算子族の演算子と関数メンバーを検証し、デフォルトが不十分なら依存型を設定します。
これはCREATE OPERATOR CLASS
とALTER OPERATOR FAMILY ADD
の実行中に呼び出されます。
後者の場合、opclassoid
はInvalidOid
です。
List
引数はamapi.h
で定義されているOpFamilyMember
構造体のリストです。
この関数で実施されるテストは典型的にはamvalidate
が行うテストのサブセットです。
なぜなら、amadjustmembers
はメンバーの集合のすべてを観察しているとは仮定することができないからです。
たとえば、サポート関数の呼び出し形式を検証することは妥当ですが、必要なすべてのサポート関数が提供されていることを検証するのは妥当ではないからです。
問題が発生すればどの場合でもエラーが生じます。
OpFamilyMember
構造体の依存性に関するフィールドに、CREATE OPERATOR CLASS
の場合にはopclassにコアコードがハード依存性で初期化します。ALTER OPERATOR FAMILY ADD
ならばopfamilyをソフト依存性で初期化します。
それ以外の振る舞いがより適正ならば、amadjustmembers
でこれらのフィールドを調整することができます。
たとえば、GIN、GiST、SP-GiSTのようなインデックス形式では演算子とopclassの関連性が相対的低く、演算子メンバーの追加削除を自由に行うことが合理的であるため、これらの演算子メンバーにおいてはopfamilyに常にソフト依存性が設定されます。
また、必要ならば削除可能にするために、追加のサポート関数にソフト依存性を設定するのが一般的です。
当然ながらインデックスの目的は、よく修飾子やスキャンキーと呼ばれる、インデックス可能なWHERE
条件を満たすタプルのスキャンをサポートすることです。
インデックススキャンのセマンティクスは後の62.3でより詳しく説明します。
インデックスアクセスメソッドは「単純」インデックススキャン、「ビットマップ」インデックススキャン、またはこれら双方を提供します。
インデックスアクセスメソッドが提供しなければならない、もしくは提供する可能性のあるスキャン関連の関数を以下に示します。
IndexScanDesc ambeginscan (Relation indexRelation, int nkeys, int norderbys);
インデックススキャンを準備します。
nkeys
およびnorderbys
パラメータは、スキャンで使用される等価性演算子と順序付け演算子の個数を表します。
これらは領域を割り当てる目的で便利かもしれません。
スキャンキーの実値がまだ提供されていないことに注意してください。
結果はpallocした構造体でなければなりません。
実装上の理由により、インデックスアクセスメソッドはRelationGetIndexScan()
呼び出しによってこの構造体を作成しなければなりません。
ほとんどの場合、ambeginscan
はこの呼び出しとおそらくロックの獲得の他にはほとんど何も行いません。
インデックススキャンを始める際の興味深い部分は、amrescan
にあります。
void amrescan (IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys);
インデックススキャンを起動または再起動します。
スキャンキーを新しくすることもできます。
(過去に渡されたキーを使用して再起動するには、key
、orderbys
、またはその両方にNULLを渡します。)
ambeginscan
に渡したキー演算子、順序付け演算子の個数より多くを使用することはできないことに注意してください。
実際には、ネステッドループ結合によって新しい外部タプルが選択され、同じスキャンキー構造体で新しいキー比較値が必要とされた場合に、この再起動機能は使用されます。
bool amgettuple (IndexScanDesc scan, ScanDirection direction);
指定されたスキャン内から指定された方向(インデックス内の前方または後方)で次のタプルを取り出します。
タプルを取り出した場合は真を返します。
一致するタプルが残っていない場合は偽を返します。
真の場合、そのタプルのTIDがscan
に格納されます。
「成功」とは、単にインデックスにスキャンキーに一致する項目があったことを意味しているだけです。
タプルが必ずヒープ内に存在することや、呼び出し元のスナップショットの試験を通過したことを意味してはいません。
成功の暁には、amgettuple
はscan->xs_recheck
を真か偽かに設定しなければなりません。
偽の意味は、インデックス項目が確実にスキャンキーに一致することです。
真の意味は、これが確かなことではなく、スキャンキーで表示された条件がヒープタプルを取り出された後で再検査されなければならないことです。
この対策は「非可逆」インデックス演算子をサポートします。
再検査はスキャン条件のみに拡大適用されることに注意してください。
部分インデックス述語(もしあれば)はamgettuple
呼び出し元で決して再検査されません。
そのインデックスがインデックスオンリースキャンをサポートしている場合(つまりamcanreturn
がすべての列に対して真を返す場合)、そのアクセスメソッドはスキャンが成功したならばscan->xs_want_itup
も確認し、それが真の場合、そのインデックスエントリに対応する元のインデックスされたデータを返さなければなりません。
amcanreturn
が列に対して偽を返す場合、その列はNULLとして返されます。
返却されるデータは、scan->xs_itupdesc
タプルディスクリプタとともにscan->xs_itup
に格納されたIndexTuple
ポインタの形式か、あるいは、scan->xs_hitupdesc
タプルディスクリプタとともにscan->xs_hitup
に格納されたHeapTuple
ポインタの形式です。
(後者の形式は、再構成されたデータがIndexTuple
に収まらない場合に使用するべきです。)
どちらの場合でも、そのポインタが参照するデータの管理はアクセスメソッドの責任です。
データは少なくともamgettuple
、amrescan
またはamendscan
によってスキャンされるまでよい状態を保たなくてはなりません。
amgettuple
関数は、アクセスメソッドが「単純」インデックススキャンをサポートするときのみ提供される必要があります。
そうでなければ、IndexAmRoutine
構造体のamgettuple
フィールドはNULLに設定されなければなりません。
int64 amgetbitmap (IndexScanDesc scan, TIDBitmap *tbm);
指定されたスキャンから全てのタプルを取り出し、呼び出し側が提供するTIDBitmap
にそれらを付加します
(つまり、既にビットマップ内にある集合とタプルIDの集合とのORを取ります)。
取り出されたタプル数が返されます(例えばいくつかのAMは重複を検出しませんので、これは単なる概算です)。
タプルIDをビットマップに挿入する間、amgetbitmap
は特定のタプルIDに必要なスキャン条件の再検査を示すことが可能です。
これはamgettuple
のxs_recheck
出力パラメータに類似しています。
注意:現在の実装においてこの機能の提供はビットマップそのものの非可逆格納を提供するのに結びついていて、したがって呼び出し側はスキャン条件と部分インデックスの述部(存在すれば)を再検査可能なタプルに対して再検査します。
とは言っても常に正しいとは限りません。
amgetbitmap
およびamgettuple
を同じインデックススキャン内で使用することはできません。
62.3で説明した通り、amgetbitmap
を使用する場合には他にも制限があります。
amgetbitmap
関数はアクセスメソッドが「ビットマップ」インデックススキャンをサポートしている場合のみ必要です。
そうでなければ、IndexAmRoutine
構造体の中のamgetbitmap
フィールドはNULLに設定されなければなりません。
void amendscan (IndexScanDesc scan);
スキャンを停止し、リソースを解放します。
scan
構造体自体は解放すべきではありません。
アクセスメソッドで内部的に取られたロックやピンは、ambeginscan
や他のスキャン関連の関数により確保されたメモリと同様に解放しなければなりません。
void ammarkpos (IndexScanDesc scan);
現在のスキャン位置を記録します。 アクセスメソッドは1スキャン当たり1つの記録済みスキャンのみをサポートしなければなりません。
ammarkpos
関数はアクセスメソッドが順序付けされたスキャンをサポートする場合にのみ提供する必要があります。
そうでなければ、そのIndexAmRoutine
構造体のammarkpos
フィールドはNULLに設定しても構いません。
void amrestrpos (IndexScanDesc scan);
もっとも最近に記録された位置にスキャンを戻します。
amrestrpos
関数はアクセスメソッドが順序付けされたスキャンをサポートする場合にのみ提供する必要があります。
そうでなければ、そのIndexAmRoutine
構造体のamrestrpos
フィールドはNULLに設定しても構いません。
通常のインデックススキャンのサポートに加え、ある種のインデックスは、複数のバックエンドが協調してインデックススキャンを実行するパラレルインデックススキャンをサポートすることができます。 インデックスアクセスメソッドは、協調するプロセスが、通常の非パラレルインデックススキャンが実行対象とする行のサブセットを返しつつ、しかもそれらのサブセットの合計が、通常の非パラレルインデックススキャンが返すタプルの集合と同じになるように調整しなければなりません。 それだけでなく、パラレルスキャンが返すタプル全体の順序付けが想定されていない場合でも、協調するバックエンドが返すサブセットのタプルの順序付けは、要求された順序付けと一致しなければなりません。 パラレルインデックススキャンをサポートするために、以下の関数を実装することができます。
Size amestimateparallelscan (void);
パラレルスキャンを実行するために、アクセスメソッドによって必要とされる動的共有メモリのバイト数を推測し、返します。
(この数値は、ParallelIndexScanDescData
のAM独立データに必要となる量に追加するための値であり、それを置き換えるものではありません。)
パラレルスキャンをサポートしない、あるいはメモリ領域への追加のバイト数が0のアクセスメソッドでは、この関数を実装する必要はありません。
void aminitparallelscan (void *target);
この関数は、パラレルスキャンの最初に動的共有メモリを初期化するために呼ばれます。
target
は、前もってamestimateparallelscan
が返したバイト数を少なくとも持つ領域を指し、この関数はその分だけのスペースを使って必要なデータを保管することができます。
パラレルスキャンをサポートしない、あるいは共有メモリスペースの初期化が必要ないアクセスメソッドでは、この関数を実装する必要はありません。
void amparallelrescan (IndexScanDesc scan);
実装された場合、この関数はパラレルインデックススキャンを再起動しなければならない時に呼ばれます。
この関数は、aminitparallelscan
が設定した共有状態を初期化し、スキャンが最初から再開できるようにします。