Написание метода отбора выборки из таблицы

В дополнение к методам BERNOULLI и SYSTEM, требуемым стандартом SQL, реализация предложения TABLESAMPLE в QHB поддерживает пользовательские методы отбора выборки из таблицы. Метод отбора выборки определяет, какие строки таблицы будут выбраны при использовании предложения TABLESAMPLE.

На уровне SQL метод отбора выборки представлен единственной функцией SQL (как правило, реализованной на C/RUST) с сигнатурой

method_name(internal) RETURNS tsm_handler

Имя функции совпадает с именем метода, фигурирующим в предложении TABLESAMPLE. Аргумент internal является фиктивным (всегда принимающим нулевое значение) и просто предотвращает вызов этой функции непосредственно из команды SQL. Результатом функции должна быть выделенная palloc структура типа TsmRoutine, содержащая указатели на вспомогательные функции для метода отбора выборки. Эти вспомогательные функции представляют собой простые функции на C/RUST, которые не видны и не могут вызываться на уровне SQL. Эти вспомогательные функции описаны в разделе Вспомогательные функции метода отбора выборки.

Кроме указателей на функций, структура TsmRoutine должна содержать следующие поля:

  • List *parameterTypes
    Это список OID типов данных параметров, которые будет принимать предложение TABLESAMPLE при использовании данного метода отбора выборки. Например, у встроенных методов этот список содержит один элемент со значением FLOAT4OID, представляющим собой процент выборки. Пользовательские методы могут иметь дополнительные или иные параметры.

  • bool repeatable_across_queries
    Если true, то этот метод отбора выборки может возвращать идентичные выборки в последовательных запросах, если каждый раз предоставляются одни и те же параметры и начальное значение REPEATABLE, а содержимое таблицы не изменяется. Если же значение равно false, предложение REPEATABLE не принимается для использования с этим методом отбора выборки.

  • bool repeatable_across
    Если true, то метод отбора выборки может возвращать идентичные выборки при последовательных сканированиях в одном запросе (при неизменных параметрах, начальном значении и снимке). Если же значение равно false, то планировщик не будет выбирать планы, требующие более одного сканирования таблицы с выборкой, так как это может привести к несогласованному результату запроса.

Методы отбора выборки из таблицы, входящие в стандартный дистрибутив, могут послужить хорошим примером при реализации собственных методов.


Вспомогательные функции метода отбора выборки

Функция обработки 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) равно true, сканирование фактически не будет выполняться, и функция должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для 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_bulkread равен true (по умолчанию), то при сканировании проверка видимости будет выполняться за один проход для всех кортежей на каждой просмотренной странице. Может иметь смысл установить в этом поле false, если при сканировании будет выбираться лишь малая часть кортежей на каждой просмотренной странице. Это снизит число выполняемых проверок видимости кортежей, хотя каждая из них будет дороже, так как потребует больше блокировок.

Если метод отбора выборки помечен как repeatable_across_scans, он должен быть способен выбирать во время повторного сканирования тот же набор кортежей, что и в первый раз, то есть вызов BeginSampleScan должен приводить к выбору тех же кортежей, что и раньше (если параметры TABLESPACE и начальное значение не изменились).

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).