Провайдер пользовательского сканирования

QHB поддерживает набор экспериментальных объектов, которые позволяют расширять модули путем добавления новых типов сканирования в систему. В отличие от оболочки внешних данных, которая отвечает только за то, как сканировать свои собственные внешние таблицы, пользовательский провайдер сканирования может предоставить альтернативный метод сканирования любого отношения в системе. Как правило, мотивация для написания пользовательского провайдера сканирования будет состоять в том, чтобы разрешить использование некоторой оптимизации, не поддерживаемой основной системой, такой как кэширование или некоторая форма аппаратного ускорения. В этой главе описывается, как создать новый пользовательский провайдер сканирования.

Реализация нового типа пользовательского сканирования - это трехэтапный процесс. Во-первых, во время планирования необходимо сгенерировать пути доступа, представляющие собой сканирование с использованием предложенной стратегии. Во-вторых, если один из этих путей доступа выбран планировщиком в качестве оптимальной стратегии для сканирования конкретного отношения, путь доступа должен быть преобразован в план. Наконец, должна быть предусмотрена возможность выполнения плана и получения тех же результатов, которые были бы получены для любого другого пути доступа, нацеленного на то же отношение.

Создание пользовательских путей сканирования

Пользовательский провайдер сканирования обычно добавляет пути для базового отношения, устанавливая следующий обработчик, который вызывается после того, как основной код создал все пути доступа, которые он может для отношения (за исключением путей Gather, которые создаются после этого вызова, чтобы они могли использовать частичные пути, добавленные обработчиком):

typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root,
                                            RelOptInfo *rel,
                                            Index rti,
                                            RangeTblEntry *rte);
extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook;

Хотя функция подключения может использоваться для проверки, изменения или удаления путей, созданных основной системой, пользовательский провайдер сканирования обычно ограничивается созданием CustomPath объектов и добавление их к rel с помощью add_path. провайдер пользовательского сканирования отвечает за инициализацию CustomPath объекта, который объявляется следующим образом:

typedef struct CustomPath
{
    Path      path;
    uint32    flags;
    List     *custom_paths;
    List     *custom_private;
    const CustomPathMethods *methods;
} CustomPath;

path необходимо инициализировать как и для любого другого пути, включая оценку количества строк, начальную и общую стоимость, а также порядок сортировки, предоставляемый этим путем. flags это битовая маска, которая должна включать в себя CUSTOMPATH_SUPPORT_BACKWARD_SCAN если пользовательский путь может поддерживать обратную проверку и CUSTOMPATH_SUPPORT_MARK_RESTORE если он может поддерживать пометку и восстановление. Обе возможности являются необязательными. Необязательный параметр custom_paths это список Path узлов, используемых этим узлом пользовательского пути; они будут преобразованы в Plan узлы планировщиком. custom_private может использоваться для хранения собственных данных пользовательского пути. Собственные данные должны храниться в форме, которая может быть обработана с помощью nodeToString, так что отладочные процедуры, которые пытаются напечатать пользовательский путь, будут работать как задумано. methods должен указывать на объект (обычно статически выделенный), реализующий необходимые методы пользовательского пути, из которых в настоящее время существует только один.

Пользовательский провайдер сканирования также может предоставить пути соединения. Так же, как и для базовых отношений, такой путь должен давать тот же результат, что и при соединении, которое он заменяет. Для этого провайдер соединения должен установить следующий обработчик, а затем в ней создать CustomPath путь(и) для связи соединения.

typedef void (* set_join_pathlist_hook_type) (PlannerInfo * root,
                                             RelOptInfo * joinrel,
                                             RelOptInfo * externalrel,
                                             RelOptInfo * innerrel,
                                             JoinType jointype,
                                             JoinPathExtraData * extra);
extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook;

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

Обратные вызовы пользовательских путей сканирования

Plan *(*PlanCustomPath) (PlannerInfo *root,
                         RelOptInfo *rel,
                         CustomPath *best_path,
                         List *tlist,
                         List *clauses,
                         List *custom_plans);

Преобразует пользовательский путь в готовый план. Возвращаемое значение обычно будет CustomScan объектом, который аллоцируется и инициализируется обратным вызовом. Смотрите раздел Создание пользовательских планов сканирования для получения более подробной информации.

Создание пользовательских планов сканирования

Пользовательское сканирование представлено в конечном дереве планов в виде следующей структуры:

typedef struct CustomScan
{
    Scan      scan;
    uint32    flags;
    List     *custom_plans;
    List     *custom_exprs;
    List     *custom_private;
    List     *custom_scan_tlist;
    Bitmapset *custom_relids;
    const CustomScanMethods *methods;
} CustomScan;

scan необходимо инициализировать, как и для любого другого сканирования, включая расчетные затраты, списки целевых объектов, и т.д. Битовая маска flags имеет тот же смысл, что и в CustomPath. custom_plans может использоваться для хранения дочерних Plan узлов. custom_exprs следует использовать для хранения деревьев выражений, которые необходимо будет исправить с помощью setrefs.c и subselect.c, в то время как custom_private следует использовать для хранения других личных данных, которые используются только самим провайдером пользовательского сканирования. custom_scan_tlist может иметь значение NIL при сканировании базового отношения, указывающее, что пользовательское сканирование возвращает кортежи сканирования, соответствующие типу строки базового отношения. В противном случае это целевой список, описывающий фактические кортежи сканирования. custom_scan_tlist должен быть указан для соединений, а также может быть предоставлено для сканирования, если пользовательский провайдер сканирования может вычислить некоторые выражения без переменных. custom_relids устанавливается ядром и задается для набора отношений (индексов таблицы диапазонов), которые обрабатывает этот узел сканирования; за исключением случаев, когда это сканирование заменяет соединение, оно будет иметь только один элемент. methods необходимо указать на объект (обычно статически выделенный), реализующий необходимые пользовательские методы сканирования, которые более подробно описаны ниже.

Когда CustomScan сканирует отношение, scan.scanrelid должен быть индексом таблицы диапазонов сканируемой таблицы. Когда он заменяет соединение, scan.scanrelid должен быть нулевым.

Деревья плана должны иметь возможность быть продублированы с помощью copyObject, поэтому все данные, хранящиеся в "пользовательских" полях, должны состоять из узлов, которые эта функция может обрабатывать. Кроме того, настраиваемые провайдеры сканирования не могут заменить более крупную структуру, которая встраивает CustomScan для самой структуры, как это было бы возможно для CustomPath или CustomScanState.

Обратный вызов пользовательского плана сканирования

Node *(*CreateCustomScanState) (CustomScan *cscan);

Выделяет CustomScanState для переданного объекта CustomScan. Фактический размер выделенной памяти часто будет больше, чем требуется для обычного CustomScanState, потому что многие провайдеры захотят встроить этот объект в качестве поля более крупной структуры. Возвращаемое значение должно иметь метку узла и методы установите соответствующим образом, но другие поля должны быть оставлены как нули на этом этапе; после базовой инициализации в ExecInitCustomScan, будет вызван BeginCustomScan обработчику, для передачи управления провайдеру пользовательского сканирования.

Выполнение пользовательских сканирований

Когда CustomScan выполняется, его состояние исполнения находится в CustomScanState, который определен следующим образом:

typedef struct CustomScanState
{
    ScanState ss;
    uint32    flags;
    const CustomExecMethods *methods;
} CustomScanState;

ss инициализируется как и для любого другого состояния сканирования, за исключением того, что сканирование выполняется для соединения, а не для базового отношения, ss.ss_currentRelation остается NULL. flags — это битовая маска аналогичная соответствующему полю в CustomPath и CustomScan. methods должен указывать на объект (обычно статически выделенный), реализующий требуемые пользовательские методы состояния сканирования, которые более подробно описаны ниже. Как правило, CustomScanState, который не обязан поддерживать copyObject, на самом деле будет являться частью большей структуры.

Обратные вызовы выполнения пользовательского сканирования

void (*BeginCustomScan) (CustomScanState *node,
                         EState *estate,
                         int eflags);

Инициализирует переданный объект CustomScanState. Стандартные поля инициализируются с помощью ExecInitCustomScan, но любые собственные поля должны быть инициализированы в этом месте.

TupleTableSlot *(*ExecCustomScan) (CustomScanState *node);

Выбирает следующий кортеж для сканирования. Если какие-либо кортежи остаются, функция заполняет ps_ResultTupleSlot следующим кортежем в текущем направлении сканирования, а затем возвращает слот кортежа. Если кортежи закончились, должен быть возвращаен NULL или пустой слот.

void (*EndCustomScan) (CustomScanState *node);

Очищает данные, связанные с CustomScanState. Этот метод является обязательным, но он ничего не обязан делать, если нет никаких связанных данных или он будет очищен автоматически.

void (*ReScanCustomScan) (CustomScanState *node);

Смещение текущего указателя сканирование в начало и подготовка к повторному сканированию отношения.

void (*MarkPosCustomScan) (CustomScanState *node);

Сохраняет текущее положение сканирования, чтобы впоследствии его можно было восстановить с помощью обработчика RestrPosCustomScan. Этот обратный вызов является необязательным и должен быть предоставлен только в том случае, если установлен флаг CUSTOMPATH_SUPPORT_MARK_RESTORE.

void (*RestrPosCustomScan) (CustomScanState *node);

Восстанавливает предыдущую позицию сканирования, сохраненную с помощью обработчика MarkPosCustomScan. Этот обратный вызов является необязательным и должен быть предоставлен только в том случае, если установлен флаг CUSTOMPATH_SUPPORT_MARK_RESTORE.

Size (*EstimateDSMCustomScan) (CustomScanState *node,
                               ParallelContext *pcxt);

Оценивает объем общей динамической памяти, который потребуется для параллельной работы. Значение может быть больше, чем фактически использованная сумма, но она не должна быть меньше. Возвращаемое значение представлено в байтах. Этот обратный вызов является необязательным и должен быть предоставлен только в том случае, если этот пользовательский провайдер сканирования поддерживает параллельное выполнение.

void (*InitializeDSMCustomScan) (CustomScanState *node,
                                 ParallelContext *pcxt,
                                 void *coordinate);

Инициализирует общую динамическую память, которая необходима для параллельной работы. coordinate указывает на общую область памяти размером равным возвращаемым значением EstimateDSMCustomScan. Этот обратный вызов является необязательным и должен быть предоставлен только в том случае, если этот пользовательский провайдер сканирования поддерживает параллельное выполнение.

void (*ReInitializeDSMCustomScan) (CustomScanState *node,
                                   ParallelContext *pcxt,
                                   void *coordinate);

Повторно инициализирует общую динамическую память, необходимую для параллельной работы, когда узел пользовательского плана сканирования будет повторно сканироваться. Этот обратный вызов является необязательным и должен быть предоставлен только в том случае, если этот пользовательский провайдер сканирования поддерживает параллельное выполнение. Рекомендуемая практика заключается в том, что этот обратный вызов сбрасывает только общее состояние, в то время как обработчик ReScanCustomScan сбрасывает только локальное состояние. В настоящее время этот обратный вызов будет вызван перед вызовом ReScanCustomScan но лучше всего не полагаться на этот порядок.

void (*InitializeWorkerCustomScan) (CustomScanState *node,
                                    shm_toc *toc,
                                    void *coordinate);

Инициализирует локальное состояние параллельного процесса на основе общего состояния, заданного лидером во время выполнения InitializeDSMCustomScan. Этот обратный вызов является необязательным и должен быть предоставлен только в том случае, если этот пользовательский провайдер сканирования поддерживает параллельное выполнение.

void (*ShutdownCustomScan) (CustomScanState *node);

Высвобождает ресурсы, когда ожидается, что узел не будет выполнен до завершения. Функция вызывается не во всех случаях; иногда, EndCustomScan может вызываться без предшествующего вызова ShutdownCustomScan. Поскольку сегмент DSM, используемый параллельным запросом, уничтожается сразу после вызова этого обратного вызова, этот метод следует реализовать пользовательским провайдерам сканирования, которые хотят предпринять некоторые действия до исчезновения сегмента DSM.

void (*ExplainCustomScan) (CustomScanState *node,
                           List *ancestors,
                           ExplainState *es);

Выводит дополнительную информацию для EXPLAIN узла пользовательского плана сканирования. Этот обратный вызов является необязательным. Общие данные, хранящиеся в ScanState, такие как целевой список и отношение сканирования, будут показаны даже без этого обратного вызова, но обратный вызов позволяет отображать дополнительную информацию о внутреннем состоянии.