61.1. Опорные функции метода извлечения выборки #
Функция-обработчик TSM возвращает структуру TsmRoutine (выделенную вызовом palloc) с указателями на опорные функции, описанные ниже. Большинство этих функций обязательные, но некоторые — нет, и их указатели могут быть равны 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 ещё не настроен). Массив params, длины nparams, содержит значения параметров, переданных в предложении 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, BlockNumber nblocks);
Возвращает номер блока следующей сканируемой страницы либо InvalidBlockNumber, если страниц для сканирования не осталось.
Эту функцию можно опустить (присвоить её указателю NULL), в этом случае код ядра произведёт последовательное сканирование всего отношения. Такое сканирование может быть синхронизированным, так что метод выборки не должен полагать, что страницы отношения каждый раз просматриваются в одном и том же порядке.
OffsetNumber
NextSampleTuple (SampleScanState *node,
BlockNumber blockno,
OffsetNumber maxoffset); Возвращает номер смещения следующего кортежа, выбираемого с указанной страницы, либо InvalidOffsetNumber, если кортежей для выборки не осталось. В maxoffset задаётся максимальный номер смещения, допустимый на этой странице.
Примечание
NextSampleTuple не говорит явно, для каких из номеров смещений в диапазоне 1 .. maxoffset действительно содержатся актуальные кортежи. Это обычно не проблема, так как код ядра игнорирует запросы на выборку несуществующих или невидимых кортежей; это не должно приводить к отклонениям в выборке. Однако при необходимости функция может прочитать в поле node->donetuples количество кортежей, которые оказались актуальными и видимыми, из числа тех, что она выдала.
Примечание
Функция NextSampleTuple не должна полагать, что в blockno будет получен тот же номер страницы, что был выдан при последнем вызове NextSampleBlock. Этот номер определённо был выдан при каком-то предыдущем вызове NextSampleBlock, но код ядра может вызывать NextSampleBlock перед тем, как собственно сканировать страницы, для поддержки упреждающего чтения. Однако можно рассчитывать на то, что как только начнётся выборка кортежей с одной данной страницы, все последующие вызовы NextSampleTuple будут обращаться к этой странице, пока не будет возвращено значение InvalidOffsetNumber.
void EndSampleScan (SampleScanState *node);
Завершает сканирование и освобождает ресурсы. Обычно при этом не нужно освобождать память, выделенную через palloc, но все видимые извне ресурсы должны быть очищены. Эту функцию чаще всего можно опустить (присвоить её указателю NULL), если таких ресурсов нет.
61.1. Sampling Method Support Functions #
The TSM handler function returns a palloc'd TsmRoutine struct containing pointers to the support functions described below. Most of the functions are required, but some are optional, and those pointers can be NULL.
void
SampleScanGetSampleSize (PlannerInfo *root,
RelOptInfo *baserel,
List *paramexprs,
BlockNumber *pages,
double *tuples);
This function is called during planning. It must estimate the number of relation pages that will be read during a sample scan, and the number of tuples that will be selected by the scan. (For example, these might be determined by estimating the sampling fraction, and then multiplying the baserel->pages and baserel->tuples numbers by that, being sure to round the results to integral values.) The paramexprs list holds the expression(s) that are parameters to the TABLESAMPLE clause. It is recommended to use estimate_expression_value() to try to reduce these expressions to constants, if their values are needed for estimation purposes; but the function must provide size estimates even if they cannot be reduced, and it should not fail even if the values appear invalid (remember that they're only estimates of what the run-time values will be). The pages and tuples parameters are outputs.
void
InitSampleScan (SampleScanState *node,
int eflags);
Initialize for execution of a SampleScan plan node. This is called during executor startup. It should perform any initialization needed before processing can start. The SampleScanState node has already been created, but its tsm_state field is NULL. The InitSampleScan function can palloc whatever internal state data is needed by the sampling method, and store a pointer to it in node->tsm_state. Information about the table to scan is accessible through other fields of the SampleScanState node (but note that the node->ss.ss_currentScanDesc scan descriptor is not set up yet). eflags contains flag bits describing the executor's operating mode for this plan node.
When (eflags & EXEC_FLAG_EXPLAIN_ONLY) is true, the scan will not actually be performed, so this function should only do the minimum required to make the node state valid for EXPLAIN and EndSampleScan.
This function can be omitted (set the pointer to NULL), in which case BeginSampleScan must perform all initialization needed by the sampling method.
void
BeginSampleScan (SampleScanState *node,
Datum *params,
int nparams,
uint32 seed);
Begin execution of a sampling scan. This is called just before the first attempt to fetch a tuple, and may be called again if the scan needs to be restarted. Information about the table to scan is accessible through fields of the SampleScanState node (but note that the node->ss.ss_currentScanDesc scan descriptor is not set up yet). The params array, of length nparams, contains the values of the parameters supplied in the TABLESAMPLE clause. These will have the number and types specified in the sampling method's parameterTypes list, and have been checked to not be null. seed contains a seed to use for any random numbers generated within the sampling method; it is either a hash derived from the REPEATABLE value if one was given, or the result of random() if not.
This function may adjust the fields node->use_bulkread and node->use_pagemode. If node->use_bulkread is true, which it is by default, the scan will use a buffer access strategy that encourages recycling buffers after use. It might be reasonable to set this to false if the scan will visit only a small fraction of the table's pages. If node->use_pagemode is true, which it is by default, the scan will perform visibility checking in a single pass for all tuples on each visited page. It might be reasonable to set this to false if the scan will select only a small fraction of the tuples on each visited page. That will result in fewer tuple visibility checks being performed, though each one will be more expensive because it will require more locking.
If the sampling method is marked repeatable_across_scans, it must be able to select the same set of tuples during a rescan as it did originally, that is a fresh call of BeginSampleScan must lead to selecting the same tuples as before (if the TABLESAMPLE parameters and seed don't change).
BlockNumber NextSampleBlock (SampleScanState *node, BlockNumber nblocks);
Returns the block number of the next page to be scanned, or InvalidBlockNumber if no pages remain to be scanned.
This function can be omitted (set the pointer to NULL), in which case the core code will perform a sequential scan of the entire relation. Such a scan can use synchronized scanning, so that the sampling method cannot assume that the relation pages are visited in the same order on each scan.
OffsetNumber
NextSampleTuple (SampleScanState *node,
BlockNumber blockno,
OffsetNumber maxoffset);
Returns the offset number of the next tuple to be sampled on the specified page, or InvalidOffsetNumber if no tuples remain to be sampled. maxoffset is the largest offset number in use on the page.
Note
NextSampleTuple is not explicitly told which of the offset numbers in the range 1 .. maxoffset actually contain valid tuples. This is not normally a problem since the core code ignores requests to sample missing or invisible tuples; that should not result in any bias in the sample. However, if necessary, the function can use node->donetuples to examine how many of the tuples it returned were valid and visible.
Note
NextSampleTuple must not assume that blockno is the same page number returned by the most recent NextSampleBlock call. It was returned by some previous NextSampleBlock call, but the core code is allowed to call NextSampleBlock in advance of actually scanning pages, so as to support prefetching. It is OK to assume that once sampling of a given page begins, successive NextSampleTuple calls all refer to the same page until InvalidOffsetNumber is returned.
void EndSampleScan (SampleScanState *node);
End the scan and release resources. It is normally not important to release palloc'd memory, but any externally-visible resources should be cleaned up. This function can be omitted (set the pointer to NULL) in the common case where no such resources exist.