TSMハンドラ関数は、以下に示すサポート関数へのポインタを含むpallocされたTsmRoutine構造体を返します。
大半の関数は必須ですが、いくつかオプションのものがあり、そうした関数へのポインタはNULLにできます。
void
SampleScanGetSampleSize (PlannerInfo *root,
RelOptInfo *baserel,
List *paramexprs,
BlockNumber *pages,
double *tuples);
この関数はプランニングの際に呼び出されます。
サンプルスキャン中に読み出すリレーションのページ数と、スキャン中に選択されるタプル行数の見積もりを行わなければなりません。
(たとえば、サンプル比率を推定し、それにbaserel->pagesとbaserel->tuplesを掛け、整数値になるように丸めることで見積もりが可能となるでしょう。)
paramexprsリストは、TABLESAMPLE句への引数となる式を格納します。
見積のためにその推測値が必要なら、estimate_expression_value()を使ってこれらの式を定数に簡約化してみることをおすすめします。
ただし、関数は簡約化ができない場合でもサイズに関する見積は提供しなければなりませんし、値が正しくない場合でも関数がエラーになってはいけません(推測値は、実行時には値がそうなるであろうということに過ぎないことを思い出してください)。
pagesとtuplesパラメータは出力です。
void
InitSampleScan (SampleScanState *node,
int eflags);
SampleScan計画ノードを実行するための初期化を行います。
この関数はエグゼキュータの起動時に呼び出されます。
処理を開始する前に必要な初期化をすべて行う必要があります。
SampleScanStateノードは作成済みですが、tsm_stateフィールドはNULLです。
InitSampleScan関数はサンプリングメソッドが必要とする内部データをすべてpallocし、node->tsm_stateに格納します。
スキャン対象のテーブルに関する情報はSampleScanStateノードの他のフィールドを通じてアクセスできます
(ただし、node->ss.ss_currentScanDescスキャンディスクリプタはまだ設定されていません)。
eflagsには、この計画ノードにおけるエグゼキュータの動作モードを記述するフラグビットが含まれます。
(eflags & EXEC_FLAG_EXPLAIN_ONLY)が真ならば、スキャンは実際には行われず、この関数はEXPLAINとEndSampleScanにとってノードの状態が意味のあるように最低限必要な処理を行うことになります。
この関数は(ポインタをNULLにすることにより)省略できますが、この場合、BeginSampleScanがサンプリングメソッドに必要なすべての初期化を行なわなければなりません。
void
BeginSampleScan (SampleScanState *node,
Datum *params,
int nparams,
uint32 seed);
サンプルスキャンの実行を開始します。
これははじめてタプルを取得する直前に呼び出されます。
また、再スキャンを行う必要が出た場合にも呼び出されます。
スキャン対象のテーブルに関する情報はSampleScanStateノードのフィールドを通じてアクセスできます
(ただし、node->ss.ss_currentScanDescスキャンディスクリプタはまだ設定されていません)。
nparamsの長さを持つparams配列は、TABLESAMPLE句で指定された引数の値を保持ししています。
これらは、サンプリングメソッドのparameterTypesリストで指定された数と型を持ち、NULLでないことがチェック済みです。
seedには、サンプリングメソッド内で使われる乱数のために使われるシードが格納されます。
これは、REPEATABLEの値が指定されている場合はそこから派生したハッシュか、でなければrandom()の結果です。
この関数はnode->use_bulkreadとnode->use_pagemodeフィールドによって動作を変更します
node->use_bulkreadがtrueなら(これはデフォルトです)、スキャンは使用後のバッファの再利用を推奨するバッファアクセス戦略を使います。
テーブルのわずかな部分だけをスキャンがアクセスするようなら、falseにするのが妥当かもしれません。
node->use_pagemodeがtrueなら(これはデフォルトです)、スキャンはアクセスするページ上のすべてのタプルに対して一括で可視性チェックを行います。
スキャンがアクセスするページ上のわずかな部分のタプルだけを選択するのであれば、false にするのが妥当かもしれません。
これにより、より少ないタプルに対して可視性チェックが行われます。
ただし、個々の操作はより高くつきます。
というのも、より多くのロックが必要になるからです。
サンプリングメソッドにrepeatable_across_scansという印があれば、最初にスキャンした時と同じタプルの集合を、再スキャンでも選択できることになります。
つまり、新しいBeginSampleScanが、前回と同じタプルを選択することになるわけです。
(もしTABLESAMPLEの引数とシードが変わらなければ、の話ですが)
BlockNumber NextSampleBlock (SampleScanState *node);
次にスキャンするページのブロック番号を返します。
もはやスキャンするページがない場合にはInvalidBlockNumberを返します。
この関数は(ポインタをNULLにすることにより)省略できます。 この場合コアのコードはリレーション全体を順スキャンします。 そのようなスキャンは同期スキャンを行う可能性があるので、毎回のスキャンで同じ順番でリレーションのページをアクセスするとは、サンプリングメソッドは仮定できません。
OffsetNumber
NextSampleTuple (SampleScanState *node,
BlockNumber blockno,
OffsetNumber maxoffset);
サンプル対象の指定ページ内の次のタプルのオフセットを返します。
サンプル対象のタプルが残っていない場合は、InvalidOffsetNumberを返します。
maxoffsetは、使用中のページ中の最大オフセットです。
NextSampleTupleは、範囲1 .. maxoffsetの中のどのオフセット番号が有効なタプルにあたるのかは明示的には教えてもらえません。
通常コアのコードは、存在しない、あるいは不可視のタプルは無視するため、通常はこれは問題になりません。
サンプルの偏りも起きません。
それでも必要ならば、関数はnode->ss.ss_currentScanDesc->rs_vistuples[]を調べ、どのタプルが有効で可視なのかを調べることもできます(このためにはnode->use_pagemodeがtrueである必要があります)。
NextSampleTupleは、直近のNextSampleBlockの呼び出しが返したページ番号とblocknoが同じであると見なすべきではありません。
ページ番号は、以前のNextSampleBlockの呼び出しが返したものではありますが、コアのコードは、先読みのために実際のスキャンに先立ってNextSampleBlockを呼び出すことが認められています。
一旦あるページのサンプリングが開始すれば、InvalidOffsetNumberが返るまでは、続くNextSampleTupleに呼び出しがすべて同じページを参照すると見なすことは問題ありません。
void EndSampleScan (SampleScanState *node);
スキャンを終了し、リソースを解放します。 通常pallocされたメモリを解放するのは重要なことではありませんが、外部から見えるリソースはすべて解放しなければなりません。 そのようなリソースが存在しない場合は、この関数は(ポインタをNULLにすることにより)省略できます。