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列表中指定,并且已经被
   检查过不为空。seed包含用于在采样方法中生成任何随机数的
   种子。如果给定了REPEATABLE值,种子将是该值的哈希。如果
   没有指定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不能假定
    blockno是最近一次NextSampleBlock调用返回的
    同一个页面号。它由之前某次NextSampleBlock调用所返回,
    但是核心代码被允许在真正扫描页面之前调用NextSampleBlock,
    以便支持预取。假定一旦一个给定页面的采样开始,连续的
    NextSampleTuple调用都将引用同一个页面(直到返回
    InvalidOffsetNumber)。
   
void EndSampleScan (SampleScanState *node);
结束扫描并且释放资源。释放 palloc 过的内存通常并不重要,但是任何外部 可见的资源应该被清除。在没有这类资源存在的通常情况下,这个函数可以被 省略(设置指针为 NULL)。