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にすることにより)省略できます。