Написание обертки сторонних данных

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

Обертки сторонних данных, включенные в дистрибутив, являются хорошими примерами для написания собственных оберток (см. подкаталог share/extension дерева исходного кода). Кроме того, некоторые полезные сведения содержит справочная страница команды CREATE FOREIGN DATA WRAPPER.

Примечание
Стандарт SQL определяет интерфейс для написания оберток сторонних данных. Однако QHB не реализует этот API, потому что усилия по его размещению в QHB были бы велики, а стандартизованный API все равно не получил широкого распространения.



Функции оберток сторонних данных

Автор FDW (Foreign Data Wrapper, обертки сторонних данных) должен реализовать функцию обработки и при необходимости функцию проверки. Обе функции должны быть написаны на компилируемом языке, например C/RUST, с использованием интерфейса версии 1. Дополнительные сведения по соглашениям о вызовах на языке C/RUST и динамической загрузке см. в разделе Функции на нативном языке.

Функция обработки просто возвращает структуру указателей функций на функции обратного вызова, которые будут вызываться планировщиком, исполнителем и различными командами обслуживания. Большая часть усилий по написанию FDW заключается в реализации этих функций обратного вызова. Функция обработки должна быть зарегистрирована в QHB как не принимающая аргументы и возвращающая специальный псевдотип fdw_handler. Функции обратного вызова являются простыми функциями на C/RUST и не видны или не вызываются на уровне SQL. Функции обратного вызова описываются в разделе Подпрограммы обратного вызова обертки сторонних данных.

Функция проверки отвечает за проверку параметров, указанных в командах CREATE и ALTER для ее обертки сторонних данных, а также сторонних серверов, сопоставлений пользователей и сторонних таблиц, использующих эту обертку. Функция проверки должна быть зарегистрирована как принимающая два аргумента, текстовый массив, содержащий проверяемые параметры, и OID, представляющий тип объекта, с которым связаны эти параметры (в виде OID системного каталога объект будет сохранен: ForeignDataWrapperRelationId, ForeignServerRelationId, UserMappingRelationId или ForeignTableRelationId). Если функция проверки отсутствует, параметры не проверяются ни во время создания объекта, ни во время его изменения.



Подпрограммы обратного вызова обертки сторонних данных

Функция обработки FDW возвращает выделенную palloc структуру FdwRoutine, содержащую указатели на описанные ниже функции обратного вызова. Функции, связанные со сканированием, являются обязательными, остальные можно использовать по желанию.


Подпрограммы FDW для сканирования сторонних таблиц

void
GetForeignRelSize(PlannerInfo *root,
                  RelOptInfo *baserel,
                  Oid foreigntableid);

Получить оценку размера отношения для сторонней таблицы. Эта функция вызывается в начале планирования запроса, который сканирует стороннюю таблицу. Аргумент root — глобальная информация планировщика о запросе, baserel — информация планировщика об этой таблице, а foreigntableid — это OID сторонней таблицы в каталоге pg_class. (foreigntableid можно получить из структур данных планировщика, но он передается явно, чтобы сэкономить усилия.)

Эта функция должна изменить значение baserel->rows на ожидаемое количество строк, возвращаемых при сканировании таблицы, с учетом фильтра, заданного ограничительными отборами. Начальное значение baserel->rows — некая константа по умолчанию, которую следует заменить, если это вообще возможно. Также функция может изменять baserel->width, если есть возможность улучшить оценку средней ширины строки результата. (Начальное значение зависит от типов данных и значений среднего размера столбцов, измененных при последнем ANALYZE.) Кроме того, эта функция может изменить значение baserel->tuples, если она может вычислить лучшую оценку общего количества строк в сторонней таблице. (Начальное значение берется из поля pg_class.reltuples, представляющего общее количество строк, выведенное при последнем ANALYZE; оно будет равно -1, если для этой сторонней таблицы еще не выполнялась команда ANALYZE.)

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных

void
GetForeignPaths(PlannerInfo *root,
                RelOptInfo *baserel,
                Oid foreigntableid);

Создать возможные пути доступа для сканирования сторонней таблицы. Эта функция вызывается во время планирования запроса. Аргументы такие же, как и для GetForeignRelSize, которая к тому моменту уже будет вызвана.

Эта функция должна генерировать по крайней мере один путь доступа (узел ForeignPath) для сканирования сторонней таблицы и вызвать add_path, чтобы добавить каждый такой путь к baserel->pathlist. Для построения узлов ForeignPath рекомендуется использовать create_foreignscan_path. Эта функция может генерировать несколько путей доступа, например путь, который имеет допустимый pathkeys для представления предварительно отсортированного результата. Каждый путь доступа должен содержать оценки стоимости и может содержать любую внутреннюю информацию FDW, необходимую для определения предполагаемого метода сканирования.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

ForeignScan *
GetForeignPlan(PlannerInfo *root,
               RelOptInfo *baserel,
               Oid foreigntableid,
               ForeignPath *best_path,
               List *tlist,
               List *scan_clauses,
               Plan *outer_plan);

Создать узел плана ForeignScan из выбранного стороннего пути доступа. Эта функция вызывается в конце планирования запроса. Аргументы такие же, как для GetForeignRelSize, плюс выбранный путь ForeignPath (ранее созданный GetForeignPaths, GetForeignJoinPaths или GetForeignUpperPaths), целевой список, который должен быть выдан этим узлом плана, ограничивающие предложения, которые должны применяться этим узлом плана, и внешний вложенный план узла ForeignScan, который используется для перепроверок, выполняемых RecheckForeignScan. (Если путь предназначен для соединения, а не для базового отношения, аргумент foreigntableid равен InvalidOid.)

Эта функция должна создавать и возвращать узел плана ForeignScan; для построения узла ForeignScan рекомендуется использовать make_foreignscan.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

void
BeginForeignScan(ForeignScanState *node,
                 int eflags);

Начать выполнять внешнее сканирование. Эта функция вызывается во время запуска исполнителя запроса. Она должна выполнить всю необходимую подготовительную работу перед началом сканирования, но не начать выполнение собственно сканирования (это должно быть сделано при первом вызове IterateForeignScan). Узел ForeignScanState уже был создан, но его поле fdw_state по-прежнему имеет значение NULL. Информация о сканируемой таблице доступна через узел ForeignScanState (в частности, из нижележащего узла плана ForeignScan, содержащего всю внутреннюю информацию FDW, предоставляемую функцией GetForeignPlan). Поле eflags содержит флаговые биты, описывающие режим работы исполнителя для данного узла плана.

Обратите внимание, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, эта функция не должна выполнять никаких видимых извне действий; она должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для ExplainForeignScan и EndForeignScan.

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

Извлечь одну строку из внешнего источника, возвращая ее в слоте таблицы кортежей (для этой цели следует использовать узел ScanTupleSlot). Если больше нет доступных строк, возвращает NULL. Инфраструктура слотов таблицы кортежей позволяет возвращать либо физический, либо виртуальный кортеж; в большинстве случаев последний вариант предпочтительнее с точки зрения производительности. Обратите внимание, что эта функция вызывается в контексте кратковременной памяти, который будет сбрасываться между вызовами. Если вам нужно более долговечное хранение, создайте контекст памяти в BeginForeignScan или используйте es_query_cxt из принадлежащей узлу структуры EState.

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

Обратите внимание, что исполнителю QHB все равно, нарушают ли возвращенные строки какие-либо ограничения, определенные для внешней таблицы, — но это важно для планировщика, который может неправильно оптимизировать запросы, если во внешней таблице есть строки, не удовлетворяющие объявленному ограничению. Если ограничение нарушается, когда пользователь объявил, что оно должно выполняться, возможно, будет целесообразно вызвать ошибку (так же, как это необходимо сделать в случае несоответствия типа данных).

void
ReScanForeignScan(ForeignScanState *node);

Перезапустить сканирование с самого начала. Обратите внимание, что значения тех параметров, от которых зависит сканирование, могут измениться, поэтому новое сканирование необязательно возвращает точно такие же строки.

void
EndForeignScan(ForeignScanState *node);

Завершить сканирование и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.


Подпрограммы FDW для сканирования сторонних соединений

Если FDW поддерживает выполнение сторонних соединений удаленно (а не путем извлечения данных обеих таблиц и выполнения соединения локально), она должна предоставить эту функцию обратного вызова:

void
GetForeignJoinPaths(PlannerInfo *root,
                    RelOptInfo *joinrel,
                    RelOptInfo *outerrel,
                    RelOptInfo *innerrel,
                    JoinType jointype,
                    JoinPathExtraData *extra);

Создать возможные пути доступа для объединения двух (или более) сторонних таблиц, принадлежащих одному и тому же стороннему серверу. Эта необязательная функция вызывается во время планирования запроса. Как и в случае с GetForeignPaths, эта функция должна сгенерировать путь(и) ForeignPath для предоставляемого joinrel (для их построения используйте create_foreign_join_path), и вызвать add_path, чтобы добавить эти пути к набору путей, подходящих для этого соединения. Но, в отличие от GetForeignPaths, нет необходимости, чтобы эта функция успешно создавала хотя бы один путь, поскольку всегда доступны пути с использованием локального соединения.

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

Если для соединения выбирается путь ForeignPath, он будет представлять весь процесс соединения; пути, созданные для задействованных таблиц и дочерних соединений, использоваться не будут. Последующая обработка пути соединения выполняется во многом так же, как и для пути сканирования одной сторонней таблицы. Единственное отличие заключается в том, что поле scanrelid результирующего узла плана ForeignScan должно быть установлено в ноль, так как он не представляет никакого отношения; вместо этого набор соединенных отношений представляет поле fs_relids узла ForeignScan. (Последнее поле автоматически устанавливается кодом ядра планировщика и не должно заполняться FDW). Другое отличие состоит в том, что поскольку список столбцов для удаленного соединения нельзя найти в системных каталогах, FDW должна заполнить поле fdw_scan_tlist соответствующим перечнем узлов TargetEntry, представляющих набор столбцов, которые она будет выдавать во время выполнения в возвращаемых ею кортежах.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.


Подпрограммы FDW для планирования обработки после сканирования/соединения

Если FDW поддерживает выполнение удаленной обработки после сканирования/соединения, например, удаленную агрегацию, она должна предоставить эту функцию обратного вызова:

void
GetForeignUpperPaths(PlannerInfo *root,
                     UpperRelationKind stage,
                     RelOptInfo *input_rel,
                     RelOptInfo *output_rel,
                     void *extra);

Создать возможные пути доступа для обработки верхних отношений (это термин планировщика для всех процессов обработки запросов после сканирования/объединения, таких как агрегация, оконные функции, сортировка и изменения таблиц). Эта необязательная функция вызывается во время планирования запроса. В настоящее время она вызывается только в том случае, если все базовые отношения, участвующие в запросе, принадлежат одной FDW. Эта функция должна сгенерировать путь(и) ForeignPath для любой обработки после сканирования/объединения, которую FDW умеет выполнять удаленно (для их построения используйте create_foreign_upper_path), и вызвать add_path чтобы добавить эти пути к указанному верхнему отношению. Как и в случае с GetForeignJoinPaths, нет необходимости, чтобы эта функция успешно создавала какие-либо пути, поскольку всегда доступны пути с использованием локального соединения.

Аргумент stag определяет, какой шаг после сканирования/объединения в настоящее время рассматривается. Аргумент output_rel — верхнее отношение, которое должно принимать пути, представляющие вычисление этого шага, а input_rel — отношение, представляющее входные данные для этого шага. Аргумент extra предоставляет дополнительную информацию, в настоящее время он устанавливается только для UPPERREL_PARTIAL_GROUP_AGG или UPPERREL_GROUP_AGG, и тогда указывает на структуру GroupPathExtraData, либо для UPPERREL_FINAL, и тогда указывает на структуру FinalPathExtraData. (Обратите внимание, что пути ForeignPath, добавляемые в output_rel обычно не имеют прямой зависимости от путей input_rel, поскольку ожидается, что их обработка будет осуществляться извне. Однако изучение путей, ранее сгенерированных для предыдущего шага обработки, может помочь избежать лишней работы при планировании.)

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.


Подпрограммы FDW для изменения сторонних таблиц

Если FDW поддерживает записываемые сторонние таблицы, она должна предоставлять некоторые или все из нижеперечисленных функций обратного вызова, в зависимости от потребностей и возможностей FDW:

void
AddForeignUpdateTargets(Query *parsetree,
                        RangeTblEntry *target_rte,
                        Relation target_relation);

Операции UPDATE и DELETE выполняются для строк, ранее извлеченных функциями сканирования таблиц. FDW может потребоваться дополнительная информация, например идентификатор строки или значения столбцов первичного ключа, чтобы гарантированно определить нужную строку для изменения или удаления. Чтобы поспособствовать этому, данная функция может добавить дополнительные скрытые или «бросовые» целевые столбцы в список столбцов, которые должны быть получены из сторонней таблицы во время UPDATE или DELETE.

Чтобы сделать это, создайте структуру Var, представляющую нужное лишнее значение, и передайте ее в add_row_identity_var вместе с именем для бросового столбца. (Если требуется несколько столбцов, эту процедуру можно повторить несколько раз.) Для каждой необходимой структуры Var следует выбрать уникальное имя бросового столбца, но у структур Var, отличающихся только значением поля varno, имена столбцов могут и должны быть одинаковыми. Основная система использует следующие имена бросовых столбцов: tableoid для столбца tableoid таблицы, ctid или ctidN для ctid, wholerow для Var, содержащей целую строку и отмеченной vartype = RECORD, и wholerowN для Var, содержащей целую строку, с vartype, равным объявленному типу строки таблицы. По возможности используйте эти имена повторно (планировщик будет комбинировать дублирующиеся запросы для идентичных бросовых столбцов). Если в дополнение к этим вам нужен другой тип бросового столбца, возможно, будет разумно выбрать имя с префиксов вашего имени расширения во избежание конфликтов с другими FDW.

Если указатель AddForeignUpdateTargets имеет значение NULL, никакие дополнительные целевые выражения не добавляются. (Это сделает невозможным выполнение операций DELETE, хотя UPDATE все еще можно будет осуществить, если FDW идентифицирует строки с помощью неизменяемого первичного ключа.)

List *
PlanForeignModify(PlannerInfo *root,
                  ModifyTable *plan,
                  Index resultRelation,
                  int subplan_index);

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

Аргумент root — это глобальная информация планировщика о запросе. Аргумент plan — это узел плана ModifyTable, который является полным за исключением поля fdwPrivLists. Аргумент resultRelation идентифицирует целевую стороннюю таблицу по индексу в диапазоне таблиц. Аргумент subplan_index определяет, какая это цель узла плана ModifyTable, считая от нуля; используйте это, если хотите индексировать во вложенных структурах узла plan, соответствующих целевым отношениям.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

Если указатель PlanForeignModify имеет значение NULL, никакие дополнительные действия во время планирования не предпринимаются, а список fdw_private, передаваемый в BeginForeignModify, будет равен NIL.

void
BeginForeignModify(ModifyTableState *mtstate,
                   ResultRelInfo *rinfo,
                   List *fdw_private,
                   int subplan_index,
                   int eflags);

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

Аргумент mtstate — это общее состояние выполняемого узла плана ModifyTable; через эту структуру доступны глобальные данные о плане и состоянии выполнения. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. (Для сохранения любого внутреннего состояния, требуемого для этой операции, FDW доступно поле ri_FdwState структуры ResultRelInfo.) Аргумент fdw_private содержит внутренние данные, если они были сгенерированы подпрограммой PlanForeignModify. Аргумент subplan_index определяет, какая это цель узла плана ModifyTable. Аргумент eflags содержит флаговые биты, описывающие рабочий режим исполнителя для данного узла плана.

Обратите внимание, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, эта функция не должна выполнять никаких видимых извне действий; она должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для ExplainForeignModify и EndForeignModify.

Если указатель BeginForeignModify имеет значение NULL, никакие действия во время запуска исполнителя не предпринимаются.

TupleTableSlot *
ExecForeignInsert(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

Добавить один кортеж в стороннюю таблицу. Аргумент estate — это глобальное состояние выполнения запроса. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. Аргумент slot содержит добавляемый кортеж; он будет соответствовать определению типа строки сторонней таблицы. Аргумент planSlot содержит кортеж, сгенерированный вложенным планом узла плана ModifyTable; он отличается от slot тем, что может содержать дополнительные «бросовые» столбцы. (Обычно planSlot не особенно интересен в случаях INSERT, но предоставляется для полноты.)

Возвращаемое значение является либо слотом, содержащим фактически добавленные данные (они могут отличаться от предоставленных данных, например, в результате действий триггеров), либо NULL, если на самом деле никакая строка не была вставлена (опять же, как правило, в результате действий триггеров). Для этой цели можно повторно использовать переданное на вход значение slot.

Данные в возвращаемом слоте используются только в том случае, если оператор INSERT содержит предложение RETURNING или задействует представление WITH CHECK OPTION, либо если сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого предложения RETURNING или ограничений WITH CHECK OPTION. Независимо от этого, какой-то слот должен быть возвращен, чтобы подтвердить успешное выполнение, иначе число выданных запросом строк будет неверным.

Если указатель ExecForeignInsert имеет значение NULL, попытки добавления данных в стороннюю таблицу провалятся, и будет выдано сообщение об ошибке.

Обратите внимание, что эта функция также вызывается при добавлении перенаправляемых кортежей в партицию сторонней таблицы или выполнении команды COPY FROM для сторонней таблицы, и в этом случае она вызывается иначе, чем в случае INSERT. Функции обратного вызова, позволяющие FDW поддерживать это, описываются ниже.

TupleTableSlot **
ExecForeignBatchInsert(EState *estate,
                       ResultRelInfo *rinfo,
                       TupleTableSlot **slots,
                       TupleTableSlot **planSlots,
                       int *numSlots);

Массово вставить несколько кортежей в стороннюю таблицу. Аргументы те же, что и у функции ExecForeignInsert, за исключением того, что в slots и planSlots содержится несколько кортежей и в *numSlots задается количество кортежей в этих массивах.

Возвращаемое значение является массивом слотов, содержащих фактически добавленные данные (они могут отличаться от предоставленных данных, например, в результате действий триггеров). Для этой цели можно повторно использовать переданное на вход значение slots. Количество успешно добавленных кортежей возвращается в *numSlots.

Данные в возвращаемом слоте используются, только если оператор INSERT задействует представление WITH CHECK OPTION либо если сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого ограничений WITH CHECK OPTION.

Если указатель ExecForeignBatchInsert или GetForeignModifyBatchSize имеет значение NULL, при попытках добавления в стороннюю таблицу будет использоваться функция ExecForeignInsert. Также эта функция не используется, если INSERT имеет предложение RETURNING.

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

int
GetForeignModifyBatchSize(ResultRelInfo *rinfo);

Выдать максимальное количество кортежей, которое можно обработать за один вызов ExecForeignBatchInsert для указанной сторонней таблицы. Исполнитель передает в ExecForeignBatchInsert не более заданного количества кортежей. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. Ожидается, что FDW предоставит пользователю параметр стороннего сервера и/или сторонней таблицы для установки этого значения или использует некоторое жестко закодированное значение.

Если указатель ExecForeignBatchInsert или GetForeignModifyBatchSize имеет значение NULL, при попытках добавления в стороннюю таблицу будет использоваться функция ExecForeignInsert.

TupleTableSlot *
ExecForeignUpdate(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

Добавить один кортеж в стороннюю таблицу. Аргумент estate — это глобальное состояние выполнения запроса. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую внешнюю таблицу. Аргумент slot содержит новые данные для кортежа; он будет соответствовать определению типа строки сторонней таблицы. Аргумент planSlot содержит кортеж, сгенерированный вложенным планом узла плана ModifyTable. В отличие от slot, этот кортеж содержит только новые значения для изменяемых запросом столбцов, поэтому при индексации в planSlot не следует полагаться на номера атрибутов сторонней таблицы. Кроме того planSlot обычно содержит дополнительные «бросовые» столбцы. В частности, из этого слота будут доступны все бросовые столбцы, запрошенные функцией AddForeignUpdateTargets.

Возвращаемое значение является либо слотом, содержащим строку в том виде, в каком она фактически была обновлена (она может отличаться от предоставленных данных, например, в результате действий триггеров), или NULL, если на самом деле никакая строка не была изменена (опять же, как правило, в результате действий триггеров). Для этой цели можно повторно использовать переданное на вход значение slot.

Данные в возвращаемом слоте используются только в том случае, если оператор INSERT содержит предложение RETURNING или задействует представление WITH CHECK OPTION, либо если сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого предложения RETURNING или ограничений WITH CHECK OPTION. Независимо от этого, какой-то слот должен быть возвращен, чтобы подтвердить успешное выполнение, иначе число выданных запросом строк будет неверным.

Если указатель ExecForeignUpdate имеет значение NULL, попытки изменить стороннюю таблицу провалятся, и будет выдано сообщение об ошибке.

TupleTableSlot *
ExecForeignDelete(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

Удалить один кортеж из сторонней таблицы. Аргумент estate — это глобальное состояние выполнения запроса. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. Аргумент slot при вызове не содержит ничего полезного, но может использоваться для сохранения возвращаемого кортежа. Аргумент planSlot содержит кортеж, который был сгенерировать вложенным планом узла плана ModifyTable; в частности, он будет содержать все бросовые столбцы, запрошенные функцией AddForeignUpdateTargets. Эти бросовые столбцы нужны для идентификации удаляемого кортежа.

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

Данные в возвращаемом слоте используются только в том случае, если запрос DELETE содержит предложение RETURNING или сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого предложения RETURNING. Независимо от этого, какой-то слот должен быть возвращен, чтобы подтвердить успешное выполнение, иначе число выданных запросом строк будет неверным.

Если указатель ExecForeignDelete имеет значение NULL, попытки удаления из сторонней таблицы провалятся, и будет выдано сообщение об ошибке.

void
EndForeignModify(EState *estate,
                 ResultRelInfo *rinfo);

Завершить изменение таблицы и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Если указатель EndForeignModify имеет значение NULL, во время завершения работы исполнителя никакие действия не предпринимаются.

Кортежи, добавленные в партиционированную таблицу с помощью INSERT или COPY FROM, перенаправляются в партиции. Если FDW поддерживает перенаправляемые партиции сторонних таблиц, она должна также предоставлять следующие функции обратного вызова. Кроме того, эти функции вызываются, когда в сторонней таблице выполняется COPY FROM.

void
BeginForeignInsert(ModifyTableState *mtstate,
                   ResultRelInfo *rinfo);

Начать выполнение операции добавления в стороннюю таблицу. Эта подпрограмма вызывается непосредственно перед добавлением в стороннюю таблицу первого кортежа и в случае, когда это партиция, выбранная для перенаправления кортежа, и когда это цель, указанная в команде COPY FROM. Она должен выполнить всю необходимую подготовительную работу перед фактическим добавлением данных. Впоследствии для кортежей, добавляемых в стороннюю таблицу, будет вызвана функция ExecForeignInsert или ExecForeignBatchInsert.

Аргумент mtstate — это общее состояние выполняемого узла плана ModifyTable; через эту структуру доступны глобальные данные о плане и состоянии выполнения. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. (Для сохранения любого внутреннего состояния, требуемого для этой операции, FDW доступно поле ri_FdwState структуры ResultRelInfo.)

Когда эта функция вызывается командой COPY FROM, в mtstate не передаются связанные с планом глобальные данные, а аргумент planSlot функции ExecForeignInsert впоследствии вызываемой для каждого добавляемого кортежа, равен NULL, независимо от того является ли сторонняя таблица партицией, выбранной для перенаправления кортежа, или заданной в команде целью.

Если указатель Beginforeignsert имеет значение NULL, никаких действий для инициализации не предпринимается.

Обратите внимание, что если FDW не поддерживает перенаправляемые партиции сторонних таблиц и/или выполнение COPY FROM для сторонних таблиц, эта функция или впоследствии вызванная ExecForeignInsert/ExecForeignBatchInsert должна выдать соответствующую ошибку.

void
EndForeignInsert(EState *estate,
                 ResultRelInfo *rinfo);

Завершить операцию добавления и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Если указатель Endforeignsert имеет значение NULL, никакие действия для завершения не предпринимаются.

int
IsForeignRelUpdatable(Relation rel);

Выдать отчет о том, какие операции добавления поддерживает указанная сторонняя таблица. Возвращаемое значение должно быть битовой маской номеров событий правила, указывающих, какие операции поддерживаются сторонней таблицей, с помощью нумерации CmdType; то есть (1 << CMD_UPDATE) = 4 для UPDATE, (1 << CMD_INSERT) = 8 для INSERT, и (1 << CMD_DELETE) = 16 для DELETE.

Если указатель IsForeignRelUpdatable имеет значение NULL, сторонние таблицы считаются добавляемыми, изменяемыми или удаляемыми, если FDW предоставляет ExecForeignInsert, ExecForeignUpdate, или ExecForeignDelete соответственно. Эта функция необходима только в том случае, если некоторые поддерживаемые FDW таблицы можно обновить, а некоторые — нет. (Даже тогда вместо проверки этой функции допустимо выдать ошибку в подпрограмме выполнения. Однако эта функция используется для определения возможности изменения для отображения в представлениях information_schema.)

Некоторые добавления, изменения и удаления в сторонних таблицах можно оптимизировать, реализовав альтернативный набор интерфейсов. Обычные интерфейсы для добавления, изменения и удаления извлекают строки с удаленного сервера, а потом модифицируют их по одной за раз. В некоторых случаях такой пошаговый подход необходим, но он может быть неэффективен. Если сторонний сервер способен определить, какие строки следует изменить, в действительности не извлекая их, и если нет локальных структур, которые могли бы повлиять на операцию (локальные триггеры уровня строк, сохраненные сгенерированные столбцы или ограничения WITH CHECK OPTION из родительских представлений), то можно организовать эту операцию так, чтобы она целиком выполнялась на удаленном сервере. Это позволяют осуществить описанные ниже интерфейсы.

bool
PlanDirectModify(PlannerInfo *root,
                 ModifyTable *plan,
                 Index resultRelation,
                 int subplan_index);

Решить, безопасно ли выполнять прямую модификацию на удаленном сервере. Если да, вернуть true после выполнения необходимых для этого операций планирования. В противном случае вернуть false. Эта необязательная функция вызывается во время планирования запроса. Если она выполняется успешно, то на этапе выполнения будут вызываться BeginDirectModify, IterateDirectModify и EndDirectModify. В противном случае модификация таблицы будет выполняться с помощью вышеописанных функций изменения таблиц. Аргументы такие же, как и для PlanForeignModify.

Чтобы выполнить прямую модификацию на удаленном сервере, эта функция должна записать в целевой вложенный план узел плана ForeignScan, выполняющий прямую модификацию на удаленном сервере. Значения полей operation и resultRelation узла ForeignScan должны быть установлены соответственно: в поле operation должно быть задано перечисление CmdType, соответствующее виду оператора перечисление соответственно (то есть, CMD_UPDATE для UPDATE, CMD_INSERT для INSERT и CMD_DELETE для DELETE), а в поле resultRelation должен быть скопирован аргумент resultRelation.

Дополнительную информацию смотрите в разделе Планирование запросов с оберткой сторонних данных.

Если указатель PlanDirectModify имеет значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

void
BeginDirectModify(ForeignScanState *node,
                  int eflags);

Подготовить выполнение прямой модификации на удаленном сервере. Эта функция вызывается во время запуска исполнителя. Она должен выполнить всю необходимую подготовительную работу перед прямой модификацией (это должно быть сделано при первом вызове IterateDirectModify). Узел ForeignScanState уже был создан, но его поле fdw_state по-прежнему содержит NULL. Информация о модифицируемой таблице доступна через узел ForeignScanState (в частности, из нижележащего узла плана ForeignScan, содержащего всю внутреннюю информацию FDW, предоставляемую функцией PlanDirectModify). Аргумент eflags содержит флаговые биты, описывающие рабочий режим исполнителя для этого узла плана.

Обратите внимание, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, эта функция не должна выполнять никаких видимых извне действий; она должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для ExplainDirectModify и EndDirectModify.

Если указатель BeginDirectModify имеет значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

TupleTableSlot *
IterateDirectModify(ForeignScanState *node);

Когда запрос INSERT, UPDATE или DELETE не содержит предложение RETURNING, просто вернуть NULL после прямой модификации на удаленном сервере. Когда запрос содержит это предложение, извлечь один результат, содержащий данные, необходимые для вычисления RETURNING, возвращая его в слоте таблицы кортежей (для этой цели следует использовать принадлежащую узлу структуру ScanTupleSlot). Данные, которые были фактически добавлены, изменены или удалены, следует сохранить в node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple. Вернуть NULL, если не осталось доступных строк. Обратите внимание, что эта функция вызывается в контексте кратковременной памяти, который будет сбрасываться между вызовами. Если вам нужно более долговечное хранение, создайте контекст памяти в BeginDirectModify или используйте es_query_cxt из принадлежащей узлу структуры EState.

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

Независимо от того, содержит ли запрос предложение или нет, число выданных запросом строк должно быть увеличено самой FDW. Если запрос не содержит предложение, то FDW должна также увеличить число строк для узла ForeignScanState в случае EXPLAIN ANALYZE.

Если указатель IterateDirectModify имеет значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

void
EndDirectModify(ForeignScanState *node);

Освободить ресурсы после прямой модификации на удаленном сервере. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Если указатель EndDirectModify установлен в значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.


Подпрограммы FDW для TRUNCATE

void
ExecForeignTruncate(List *rels,
                    DropBehavior behavior,
                    bool restart_seqs);

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

Аргумент behavior имеет значение DROP_RESTRICT или DROP_CASCADE, указывающее, что в оригинальной команде TRUNCATE был задан параметр RESTRICT или CASCADE соответственно.

Если аргумент restart_seqs равен true, исходная команда TRUNCATE запросила поведение RESTART IDENTITY, в противном случае было запрошено поведение CONTINUE IDENTITY.

Обратите внимание, что параметры ONLY, заданные в исходной команде TRUNCATE, не передаются в ExecForeignTruncate. Это поведение сходно с таковым функций обратного вызова команд SELECT, UPDATE и DELETE для сторонней таблицы.

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

Если указатель ExecForeignTruncate имеет значение NULL, попытки опустошить сторонние таблицы провалятся, и будет выдано сообщение об ошибке.


Подпрограммы FDW для блокировки строк

Если FDW желает поддержать позднюю блокировку строк (как описывается в разделе Блокировка строк в обертках сторонних данных), она должна предоставить следующие функции обратного вызова:

RowMarkType
GetForeignRowMarkType(RangeTblEntry *rte,
                      LockClauseStrength strength);

Сообщить, какой вариант маркировки строк следует использовать для сторонней таблицы. Аргумент rte — это узел RangeTblEntry для таблицы, а аргумент strength описывает силу блокировки, запрошенную соответствующим предложением FOR UPDATE/SHARE, если таковое имеется. Результат должен быть членом перечислимого типа RowMarkType.

Эта функция вызывается во время планирования запроса для каждой сторонней таблицы, которая фигурирует в запросе UPDATE, DELETE или SELECT FOR UPDATE/SHARE и не является целью UPDATE или DELETE.

Если указатель GetForeignRowMarkType имеет значение NULL, всегда используется вариант ROW_MARK_COPY. (Это подразумевает, что функция RefetchForeignRow никогда не будет вызываться, поэтому предоставлять еще и ее нет необходимости.)

Дополнительную информацию см. в разделе Блокировка строк в обертках сторонних данных.

void
RefetchForeignRow(EState *estate,
                  ExecRowMark *erm,
                  Datum rowid,
                  TupleTableSlot *slot,
                  bool *updated);

Повторно извлечь один слот кортежа из сторонней таблицы после блокировки, если она требуется. Аргумент EState — это глобальное состояние выполнения запроса. Аргумент erm — это структура ExecRowMark, описывающая целевую стороннюю таблицу и тип запрашиваемой блокировки строк (если таковой имеется). В аргументе rowid задается извлекаемый кортеж. Аргумент slot не содержит ничего полезного при вызове, но может использоваться для хранения возвращаемого кортежа. Аргумент updated является выходным.

Эта функция должна сохранить Кортеж в предоставленном слоте или очистить его, если блокировку строк нельзя получить. Тип запрашиваемой блокировки строк определяется с помощью значения erm->markType, который ранее было возвращенным функцией GetForeignRowMarkType. (Вариант ROW_MARK_REFERENCE означает просто повторное извлечение кортежа, без запроса какой-либо блокировки, а ROW_MARK_COPY эта подпрограмма никогда не увидит.)

Кроме того, аргумент *updated должен иметь значение true, если извлеченный кортеж был обновленной версией, а не той, что уже была получена ранее. (Если FDW не в состоянии знать это наверняка, рекомендуется всегда возвращать true).

Обратите внимание, что по умолчанию неудачная попытка получить блокировку строк должна привести к возникновению ошибки; возврат пустого слота подходит только в том случае, если в erm->waitPolicy указывается вариант SKIP LOCKED.

Аргумент rowid — это значение ctid, ранее прочитанное для строки, которая должна быть повторно извлечена. Хотя значение rowid передается в виде Datum, в настоящее время оно может быть только tid. Такой API функции выбран в надежде, что в будущем для идентификаторов строк могут стать допустимы и другие типы данных.

Если указатель RefetchForeignRow имеет значение NULL, попытки повторно извлечь строки провалятся, и будет выдано сообщение об ошибке.

Дополнительную информацию см. в разделе Блокировка строк в обертках сторонних данных.

bool
RecheckForeignScan(ForeignScanState *node,
                   TupleTableSlot *slot);

Повторно проверить, что ранее возвращенный кортеж все еще соответствует подходящим квалификаторам сканирования и соединения и, возможно, предоставит измененную версию кортежа. Для оберток сторонних данных, которые не осуществляют вытеснение соединений, обычно будет удобнее установить в этом указателе NULL и вместо этого задать соответствующее значение fdw_recheck_quals. Однако при вытеснении внешних соединений недостаточно повторно применить к результирующему кортежу проверки, относящиеся ко всем базовым таблицам, даже если присутствуют все необходимые атрибуты, потому что из-за несоответствия некоторому квалификатору не только кортеж е будет возвращен, но и некоторые атрибуты могут получить значение NULL. Функция RecheckForeignScan может перепроверить квалификаторы и вернуть true, если они по-прежнему удовлетворяются, и false в противном случае, но кроме того она может сохранить в предоставляемом слоте кортеж, заменяющий предыдущий.

Для реализации вытеснения соединения обертка сторонних данных обычно создает альтернативный локальный план соединения, который используется только для повторных проверок; он становится внешним подпланом узла ForeignScan. Когда требуется перепроверка, этот подплан может быть выполнен, и результирующий кортеж может быть сохранен в слоте. Этот план не должен быть эффективным, поскольку ни одна базовая таблица не будет возвращать более одной строки; например, он может реализовать все соединения как вложенные циклы. Для поиска в существующих путях подходящего локального пути соединения, который можно использовать в качестве альтернативного локального плана соединения, можно вызвать функцию GetExistingLocalJoinPath. Эта функция ищет непараметризованный путь в списке путей указанного отношения соединения. (Если она не находит такой путь, то возвращает NULL, и в этом случае обертка сторонних данных может построить локальный путь самостоятельно или решить не создавать пути доступа для этого соединения.)


Подпрограммы FDW для EXPLAIN

void
ExplainForeignScan(ForeignScanState *node,
                   ExplainState *es);

Вывести дополнительные данные EXPLAIN для сканирования сторонней таблицы. Эта функция может вызывать ExplainPropertyText и связанные функции для добавления полей в вывод EXPLAIN. Для определения того, что именно нужно выводить, можно использовать поля флагов в es, а для предоставления статистики времени выполнения в случае EXPLAIN ANALYZE можно проверить состояние узла ForeignScanState.

Если указатель ExplainForeignScan имеет значение NULL, во время EXPLAIN не выводится никакая дополнительная информация.

void
ExplainForeignModify(ModifyTableState *mtstate,
                     ResultRelInfo *rinfo,
                     List *fdw_private,
                     int subplan_index,
                     struct ExplainState *es);

Вывести дополнительные данные EXPLAIN для изменения сторонней таблицы. Эта функция может вызывать ExplainPropertyText и связанные функции для добавления полей в вывод EXPLAIN. Для определения того, что именно нужно выводить, можно использовать поля флагов в es, а для предоставления статистики времени выполнения в случае EXPLAIN ANALYZE можно проверить состояние узла ModifyTableState. Первые четыре аргумента такие же, как и у функции BeginForeignModify.

Если указатель ExplainForeignModify имеет значение NULL, во время EXPLAIN не выводится никакая дополнительная информация.

void
ExplainDirectModify(ForeignScanState *node,
                    ExplainState *es);

Вывести дополнительные данные EXPLAIN для прямой модификации на удаленном сервере. Эта функция может вызывать ExplainPropertyText и связанные функции для добавления полей в вывод EXPLAIN. Для определения того, что именно нужно выводить, можно использовать поля флагов в es, а для предоставления статистики времени выполнения в случае EXPLAIN ANALYZE можно проверить состояние узла ForeignScanState.

Если указатель ExplainDirectModify имеет значение NULL, во время EXPLAIN не выводится никакая дополнительная информация.


Подпрограммы FDW для ANALYZE

bool
AnalyzeForeignTable(Relation relation,
                    AcquireSampleRowsFunc *func,
                    BlockNumber *totalpages);

Эта функция вызывается, когда в сторонней таблице выполняется ANALYZE. Если FDW может собирать статистику для этой сторонней таблицы, она должна вернуть true и передать в func указатель на функцию, которая будет собирать выборку строк из таблицы, плюс предполагаемый размер таблицы в страницах в totalpages. В противном случае вернуть false.

Если FDW не поддерживает сбор статистических данных ни для каких таблиц, то в указателе AnalyzeForeignTable можно установить NULL.

Если функция сбора выборки предоставляется, она должна иметь следующую сигнатуру:

int
AcquireSampleRowsFunc(Relation relation,
                      int elevel,
                      HeapTuple *rows,
                      int targrows,
                      double *totalrows,
                      double *totaldeadrows);

Из таблицы должна быть собрана случайная выборка количеством не более targrows строк, которая сохраняется в предоставленном вызывающей функцией массиве rows. Должно быть возвращено фактическое количество собранных строк. Кроме того, эта функция сохраняет предполагаемое общее количество активных и неиспользуемых строк в таблице в выходных аргументах totalrows и totaldeadrows. (Если для этой FDW нет концепта неиспользуемых строк, следует установить в totaldeadrows ноль.)


Подпрограммы FDW для IMPORT FOREIGN SCHEMA

List *
ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);

Получить список команд для создания сторонней таблицы. Эта функция вызывается при выполнении IMPORT FOREIGN SCHEMA и передает дерево синтаксического анализа для этого оператора, а также OID стороннего сервера для работы. Она должна возвращать список строк на C/RUST, каждая из которых должна содержать команду CREATE FOREIGN TABLE. Эти строки будут проанализированы и выполнены основным сервером.

Внутри структуры ImportForeignSchemaStmt поле remote_schema — это имя удаленной схемы, из которой должны быть импортированы таблицы. Поле list_type определяет способ фильтрации имен таблиц: FDW_IMPORT_SCHEMA_ALL означает, что должны быть импортированы все таблицы в удаленной схеме (в этом случае поле table_list пустое), FDW_IMPORT_SCHEMA_LIMIT_TO означает, что будут включены только таблицы, перечисленные в table_list, а FDW_IMPORT_SCHEMA_EXCEPT — что таблицы, перечисленные в table_list, будут исключены. Поле options — это список параметров, используемых для процесса импорта. Значения этих параметров зависят от FDW. Например, FDW может использовать параметр для определения того, следует ли импортировать атрибуты NOT NULL столбцов. Этим параметрам необязательно иметь какое-либо отношение к тем, которые FDW поддерживает в качестве параметров объектов базы данных.

FDW может игнорировать поле local_schema структуры ImportForeignSchemaStmt, поскольку основной сервер автоматически вставит это имя в анализируемые команды CREATE FOREIGN TABLE.

Также FDW не нужно заниматься реализацией фильтрации, заданной в list_type и table_list, поскольку основной сервер автоматически пропустит все возвращенные команды для таблиц, исключенных согласно этим параметрам. Однако часто бывает полезно сразу избежать работы по созданию команд для исключенных таблиц. Для проверки того, пройдет ли фильтр заданное имя сторонней таблицы, может быть полезна функция IsImportableForeignTable().

Если FDW не поддерживает импорт определений таблиц, то в указателе ImportForeignSchema можно установить NULL.


Подпрограммы FDW для параллельного выполнения

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

bool
IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
                          RangeTblEntry *rte);

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

Если эта функция не определена, предполагается, что сканирование должно выполняться в параллельном ведущем процессе. Обратите внимание, что возврат true не означает, что само сканирование можно выполнить параллельно, — только то, что сканирование может выполняться в параллельном рабочем процессе. Поэтому может быть полезно определить этот метод, даже если параллельное выполнение не поддерживается.

Size
EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);

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

void
InitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
                         void *coordinate);

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

void
ReInitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
                           void *coordinate);

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

void
InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc,
                            void *coordinate);

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

void
ShutdownForeignScan(ForeignScanState *node);

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


Подпрограммы FDW для асинхронного выполнения

При необходимости узел ForeignScan может поддерживать асинхронное выполнение.

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

bool
IsForeignPathAsyncCapable(ForeignPath *path);

Проверить, может ли данный путь ForeignPath асинхронно сканировать нижележащее стороннее отношение. Эта функция будет вызываться только в конце планирования запроса, если данный путь является прямым потомком пути AppendPath и когда планировщик уверен, что асинхронное выполнение повысит производительность, и должна возвращать true, если данный путь способен асинхронно сканировать нижележащее отношение.

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

void
ForeignAsyncRequest(AsyncRequest *areq);

Асинхронно выдать один кортеж из узла ForeignScan. Аргумент areq — это структура AsyncRequest, описывающая узел ForeignScan и родительский узел Append, запросивший у него кортеж. Эта функция должна сохранить кортеж в слот, заданный в areq->result, и установить в areq->request_complete значение true. Или же, если она не может выдать кортеж сразу и ей необходимо дождаться внешнего по отношению к основному серверу события, например сетевого ввода/вывода, ForeignAsyncRequest должна установить для в этом флаге false и true в areq->callback_pending, чтобы вызвать для уза ForeignScan описанные ниже функции обратного вызова. Если кортежей больше нет, нужно передать в качестве слота NULL или пустой слот и установить во флаге areq->request_complete значение true. Чтобы задать в поле areq выходные параметры, рекомендуется воспользоваться функцией ExecAsyncRequestDone или ExecAsyncRequestPending.

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

Сконфигурировать событие файлового дескриптора, которого желает дождаться узел ForeignScan. Эта функция будет вызываться, только когда узел ForeignScan установил флаг areq->callback_pending, и должна добавить событие в поле as_eventset родительского узла Append, описываемого аргументом areq.

Когда произойдет

событие файлового дескриптора, будет вызвана функция ForeignAsyncNotify.

void
ForeignAsyncNotify(AsyncRequest *areq);

Обработать соответствующее произошедшее событие, а затем асинхронно выдать один кортеж из узла ForeignScan. Эта функция должна установить выходные параметры в areq так же, как функция ForeignAsyncRequest.


Подпрограммы FDW для репараметризации путей

List *
ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
                                 RelOptInfo *child_rel);

Эта функция вызывается при преобразовании пути, параметризованного самым верхним родителем данного дочернего отношения child_rel, в путь, параметризованный дочерним отношением. Эта функция используется для репараметризации любых путей или преобразования любых узлов выражения, сохраненных в данном члене fdw_private структуры ForeignPath*. При необходимости обратный вызов может использовать функцию reparameterize_path_by_child, adjust_appendrel_attrs или adjust_appendrel_attrs_multilevel.



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

С основного сервера экспортируется несколько вспомогательных функций, чтобы авторы оберток сторонних данных могли легко получить доступ к атрибутам объектов, связанных с FDW, например параметрам FDW. Чтобы использовать любую из этих функций, нужно включить файл заголовка foreign/foreign.h в исходный файл. Этот заголовок также определяет типы структур, возвращаемые этими функциями.

ForeignDataWrapper *
GetForeignDataWrapperExtended(Oid fdwid, bits16 flags);

Эта функция возвращает объект ForeignDataWrapper для обертки сторонних данных с заданным OID. Объект ForeignDataWrapper содержит свойства FDW (подробности см. в foreign/foreign.h). Аргумент flags — это побитовая маска, указывающая на дополнительный набор параметров. Он может принять значение FDW_MISSING_OK, и в этом случае для неопределенного объекта вызывающей функции вместо ошибки возвращается результат NULL.

ForeignDataWrapper *
GetForeignDataWrapper(Oid fdwid);

Эта функция возвращает объект ForeignDataWrapper для обертки сторонних данных с заданным OID. Объект ForeignDataWrapper содержит свойства FDW (подробности см. в foreign/foreign.h).

ForeignServer *
GetForeignServerExtended(Oid serverid, bits16 flags);

Эта функция возвращает объект ForeignServer для стороннего сервера с заданным OID. Объект ForeignDataWrapper содержит свойства сервера (подробности см. в foreign/foreign.h). Аргумент flags — это побитовая маска, указывающая на дополнительный набор параметров. Он может принять значение FSW_MISSING_OK, и в этом случае для неопределенного объекта вызывающей функции вместо ошибки возвращается результат NULL.

ForeignServer *
GetForeignServer(Oid serverid);

Эта функция возвращает объект ForeignServer для стороннего сервера с заданным OID. Объект ForeignDataWrapper содержит свойства сервера (подробности см. в foreign/foreign.h).

UserMapping *
GetUserMapping(Oid userid, Oid serverid);

Эта функция возвращает объект UserMapping для сопоставления пользователя для данной роли на данном сервере. (Если нет сопоставления для конкретного пользователя, она вернет сопоставление для PUBLIC, или выдаст ошибку, если его нет). Объект UserMapping содержит свойства сопоставления пользователя (подробности см. в foreign/foreign.h).

ForeignTable *
GetForeignTable(Oid relid);

Эта функция возвращает объект ForeignTable для сторонней таблицы с заданным OID. Объект ForeignTable содержит свойства сторонней таблицы (подробности см. в foreign/foreign.h).

List *
GetForeignColumnOptions(Oid relid, AttrNumber attnum);

Эта функция возвращает параметры FDW уровня столбцов для столбца с заданными OID сторонней таблицы и номером атрибута в виде списка DefElem. Если столбец не имеет параметров, возвращается NIL.

Некоторые типы объектов в дополнение к функциям поиска на основе OID имеют функции на основе имен:

ForeignDataWrapper *
GetForeignDataWrapperByName(const char *name, bool missing_ok);

Эта функция возвращает объект ForeignDataWrapper для обертки сторонних данных с заданным именем. Если обертка не найдена, возвращает NULL, если аргумент missing_ok равен true, и выдает ошибку в противном случае.

ForeignServer *
GetForeignServerByName(const char *name, bool missing_ok);

Эта функция возвращает объект ForeignServer для стороннего сервера с заданным именем. Если сервер не найден, возвращает NULL, если аргумент missing_ok равен true, и выдает ошибку в противном случае.



Планирование запросов с оберткой сторонних данных

Функции обратного вызова FDW GetForeignRelSize, GetForeignPaths, GetForeignPlan, PlanForeignModify, GetForeignJoinPaths, GetForeignUpperPaths, и PlanDirectModify должны вписываться в работу планировщика QHB. Здесь представлены некоторые замечания о том, что они должны делать.

Для уменьшения объема информации, которая должна быть извлечена из сторонней таблицы (и, как следствие, снижения затрат), можно использовать информацию в root и baserel. Особый интерес представляет поле baserel->baserestrictinfo, поскольку оно содержит ограничительные условия (предложения WHERE), которые должны использоваться для фильтрации извлекаемых строк. (Самой FDW необязательно применять эти условия, поскольку их может проверить и основной исполнитель.) Для определения того, какие столбцы нужно извлечь, можно использовать поле baserel->reltarget->exprs, но обратите внимание, что оно только перечисляет столбцы, которые должен выдать узел плана ForeignScan, а не столбцы, которые используются в вычислении условий, но не выводятся запросом.

Для хранения информации функциям планирования FDW доступны различные внутренние поля. Как правило, все структуры, которые вы сохраняете в внутренние поля FDW, должны выделяться palloc, чтобы они освобождались в конце планирования.

Поле baserel->fdw_private — это указатель void, предоставляемый функциям планирования FDW для хранения информации, относящейся к конкретной сторонней таблице. Основной планировщик его не трогает, за исключением того, что записывает в него NULL при создании узла RelOptInfo. Это полезно для передачи информации из GetForeignRelSize в GetForeignPaths и/или из GetForeignPaths в GetForeignPlan во избежание повторных вычислений.

Функция GetForeignPaths может определить значение различных путей доступа, сохраняя внутреннюю информацию в поле fdw_private узлов ForeignPath. fdw_private объявляется как указатель List, но на самом деле может содержать что угодно, поскольку основной планировщик его не трогает. Тем не менее рекомендуется использовать представление, которое может выгрузить функция nodeToString, для использования с поддержкой отладки, доступной на сервере.

Функция GetForeignPlan может проверить поле fdw_private выбранного узла ForeignPath и сгенерировать списки fdw_exprs и fdw_private, подлежащие размещению в узле плана ForeignScan, где они будут доступны во время выполнения. Оба эти списка должны быть представлены в форме, которую умеет копировать функция copyObject. Список fdw_private не имеет никаких других ограничений и никак не интерпретируется основным сервером. Список fdw_exprs (если не содержит NIL) должен содержать деревья выражений, предназначенные для вычисления во время выполнения. Затем планировщик обработает эти деревья, чтобы сделать их полностью исполняемыми.

Обычно в функции GetForeignPlan переданный целевой список можно скопировать в узел плана как есть. Передаваемый список scan_clauses содержит те же предложения, что и baserel->baserestrictinfo, но может быть переупорядочен для для повышения эффективности выполнения. В простых случаях FDW может просто удалить узлы RestrictInfo из списка scan_clauses (с помощью функции extract_actual_clauses) и поместить все предложения в список условий узла плана, что означает, что все предложения будут проверены исполнителем во время выполнения. Более сложные FDW могут проверить некоторые предложения внутренне, и в этом случае эти предложения могут быть удалены из списка условий узла плана, чтобы исполнитель не тратил время на их повторную проверку.

К примеру, FDW может идентифицировать некоторые ограничительные предложения вида сторонняя_переменная = подвыражение, которые, по ее определению, могут быть выполнены на удаленном сервере с учетом локально вычисленного значения подвыражения. Фактическая идентификация такого предложения должна произойти во время выполнения функции GetForeignPaths, так как это может повлиять на оценку стоимости пути. Поле fdw_private этого пути, вероятно, будет содержать указатель на узел RestrictInfo идентифицированного предложения. Затем GetForeignPlan удалила бы это предложение из scan_clauses, но добавила бы подвыражение в fdw_exprs чтобы гарантированно получать сообщения в исполняемой форме. Вероятно, она также поместит управляющую информацию в поле fdw_private узла плана, чтобы указать исполняющим функциям, что следует делать во время выполнения. Запрос, передаваемый на удаленный сервер, будет включать что-то вроде WHERE сторонняя_переменная = $1 со значением параметра, полученным во время выполнения в результате вычисления дерева выражения fdw_exprs.

Все предложения, удаленные из списка условий узла плана, должны быть добавлены в fdw_recheck_quals или перепроверены с помощью функции RecheckForeignScan, чтобы обеспечить правильное поведение на уровне изоляции READ COMMITTED. Когда осуществляется параллельное изменение в некоторой другой таблицы, фигурирующей в запросе, исполнителю может потребоваться проверить, что все исходные условия все еще удовлетворяются для кортежа, возможно, относительно другого набора значений параметров. Обычно использовать fdw_recheck_quals проще, чем реализовать проверки в RecheckForeignScan, но этот метод будет недостаточным при вытеснении внешних соединений, так как в этом случае некоторые поля кортежей соединения могут получить значение NULL, но сами кортежи не будут исключаться.

Другим полем узла ForeignScan, которое могут заполнить FDW, является fdw_scan_tlist, которое описывает кортежи, возвращенные FDW для этого узла плана. Для простых сканирований сторонних таблиц в нем можно установить значение NIL, подразумевая, что возвращенные кортежи имеют тип строки, объявленный для сторонней таблицы. Значение, отличное от NIL, должно быть целевым списком (списком TargetEntry), содержащим переменные и/или выражения, представляющие возвращаемые столбцы. Это можно использовать, например, чтобы показать, что FDW опустила некоторые столбцы, которые по ее наблюдению не понадобятся для запроса. Кроме того, если FDW может вычислять выражения, используемые запросом, менее затратно, чем это можно сделать локально, она может добавить эти выражения в fdw_scan_tlist. Обратите внимание, что планы соединения (созданные из путей, построенных функцией GetForeignJoinPaths) должны всегда предоставлять fdw_scan_tlist, чтобы описать набор возвращаемых ими столбцов.

FDW всегда должна создавать по крайней мере один путь, который зависит только от ограничительных предложений таблицы. В запросах соединения она тоже может предпочесть создавать пути, зависящие от предложений соединения, например сторонняя_переменная = локальная_переменная. Такие предложения нельзя найти в baserel->baserestrictinfo но следует искать в списках соединения отношения. Путь, использующий такое предложение, называется «параметризованным путем». Он должен идентифицировать другие отношения, используемые в выбранных предложениях соединения, с подходящим значением param_info; для вычисления этого значения нужно использовать get_baserel_parampathinfo. В GetForeignPlan часть локальная_переменная предложения соединения будет добавлена в fdw_exprs, и тогда во время выполнения это будет работать так же, как для обычного ограничительного предложения.

Если FDW поддерживает удаленные соединения, функция GetForeignJoinPaths должна создавать ForeignPath для потенциально удаленных соединений во многом таким же образом, как функция GetForeignPaths работает для базовых таблиц. Информацию о предполагаемом соединении можно передать в GetForeignPlan так же, как описано выше. Однако поле baserestrictinfo не подходит для отношений соединения; вместо этого соответствующие предложения соединения для конкретного соединения передаются в GetForeignJoinPaths в виде отдельного параметра (extra->restrictlist).

FDW может дополнительно поддерживать прямое выполнение некоторых действий плана, находящихся выше уровня сканирования и соединения, например группировок или агрегации. Чтобы предложить такие варианты, FDW должна генерировать пути и добавлять их в соответствующее верхнее отношение. Например, путь, представляющий удаленную агрегацию, должен быть вставлен в отношение UPPERREL_GROUP_AGG с помощью add_path. Этот путь будет сравниваться по стоимости с локальной агрегацией, выполненной путем считывания простого пути сканирования для стороннего отношения (обратите внимание, что такой путь тоже должен быть предоставлен, иначе во время планирования возникнет ошибка ). Если путь удаленной агрегации выигрывает, что обычно и происходит, он будет преобразован в план обычным способом, путем вызова функции GetForeignPlan. Рекомендуемое место для создания таких путей находится в функции обратного вызова GetForeignUpperPaths, которая вызывается для каждого верхнего отношения (т. е. для каждого шага обработки после сканирования/соединения), если все базовые отношения запроса исходят из одной FDW.

PlanForeignModify и другие функции обратного вызова, описанные в подразделе Подпрограммы FDW для изменения сторонних таблиц, разрабатываются исходя из предположения, что стороннее отношение будет сканироваться обычным способом, а затем отдельные изменения строк будут проводиться локальным планом узла ModifyTable. Этот подход необходим для общего случая, когда для изменения требуется прочтение как локальных, так и сторонних таблиц. Тем не менее если операция может быть полностью выполнена сторонним сервером, FDW может создать путь, представляющий эту возможность, и вставить его в верхнее отношение UPPERREL_FINAL, где он будет конкурировать с подходом ModifyTable. Этот подход также можно использовать для реализации удаленной команды SELECT FOR UPDATE вместо функций обратного вызова для блокировки строк, описанных в подразделе Подпрограммы FDW для блокировки строк. Имейте в виду, что путь, добавляемый в UPPERREL_FINAL, отвечает за реализацию всего поведения запроса.

При планировании команд UPDATE или DELETE функции PlanForeignModify и PlanDirectModify могут искать структуру RelOptInfo для сторонней таблицы и использовать данные baserel->fdw_private, ранее созданные функциями планирования сканирования. Однако в INSERT целевая таблица не сканируется, поэтому для нее RelOptInfo не заполняется. Список List, возвращаемый функцией PlanForeignModify, имеет те же ограничения, что и список fdw_private узла плана ForeignScan, то есть он должен содержать только те структуры, которые умеет копировать copyObject.

КомандаINSERT с предложением ON CONFLICT не поддерживает указание целевого объекта конфликта, поскольку ограничения уникальности или исключения для удаленных таблиц неизвестны локально. Это, в свою очередь, подразумевает, что не поддерживается ON CONFLICT DO UPDATE, поскольку там это указание является обязательным.



Блокировка строк в обертках сторонних данных

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

Одним из ключевых решений, которое необходимо принять, это нужно ли проводить раннюю блокировку или позднюю блокировку. При ранней блокировке строка блокируется при первом извлечении из базового хранилища, тогда как при поздней блокировке строка блокируется, только когда известно, что она должна быть заблокирована. (Разница возникает из-за того, что некоторые строки могут быть отброшены локально проверяемым ограничением или условиями соединения). Ранняя блокировка намного проще и позволяет избежать дополнительных обращений к удаленному хранилищу, но она может привести к блокировке строк, которые было необязательно блокировать, что приводит к снижению параллелизма или даже неожиданным взаимоблокировкам. Кроме того, поздняя блокировка возможна только в том случае, если блокируемую строку позднее можно однозначно повторно идентифицировать. Поэтому предпочтительно, чтобы конкретную версию строки определял идентификатор строки, как это делают TID в QHB.

По умолчанию при взаимодействии с FDW QHB игнорирует факторы блокировки, но FDW может выполнять раннюю блокировку без какой-либо явной поддержки со стороны кода ядра. Функции API, описанные в подразделе Подпрограммы FDW для блокировки строк, позволяют FDW при желании использовать позднюю блокировку.

Дополнительным фактором является то, что в режиме изоляции READ COMMITTED QHB может потребоваться повторно проверить ограничение и условия соединения с обновленной версией некоторого целевого кортежа. Повторная проверка условий соединения требует повторного получения копий нецелевых строк, которые ранее были присоединены к целевому кортежу. При работе со стандартными таблицами QHB это делается путем включения TID нецелевых таблиц в список столбцов, проходящих через соединение, а затем, при необходимости, повторной выборки нецелевых строк. Этот подход сохраняет компактность набора данных соединения, но требует недорогой возможности повторного извлечения строк, а также TID, который может однозначно определить версию строки, подлежащую повторному извлечению. Поэтому по умолчанию метод, используемый для сторонних таблиц, заключается в том, чтобы включить в список столбцов, проходящих через соединение, копию всей строки, извлеченной из сторонней таблицы. Это не накладывает на FDW никаких особых требований, но может привести к снижению производительности соединений слиянием и по хэшу. FDW, способная удовлетворить требованиям повторной выборки, может выбрать для этого первый способ.

Для команд UPDATE или DELETE в сторонней таблице рекомендуется, чтобы операция ForeignScan с целевой таблицей выполняла раннюю блокировку извлекаемых ею строк, возможно, посредством эквивалента SELECT FOR UPDATE. FDW может определить, является ли таблица целью UPDATE/DELETE, во время планирования, сравнивая ее идентификатор отношения с root->parse->resultRelation, или во время выполнения с помощью функции ExecRelationIsTargetRelation(). Альтернативной возможностью является выполнение поздней блокировки в функции обратного вызова ExecForeignUpdate или ExecForeignDelete, но никакой специальной поддержки для этого не предусмотрено.

Для блокируемых сторонних таблиц, заданных в команде SELECT FOR UPDATE/SHARE, операция ForeignScan, опять же, может выполнить раннюю блокировку путем выборки кортежей посредством эквивалента SELECT FOR UPDATE/SHARE. Для выполнения вместо этого поздней блокировки предоставьте функции обратного вызова, определенные в подразделе Подпрограммы FDW для блокировки строк. Выберите в функции GetForeignRowMarkType параметр маркировки строк ROW_MARK_EXCLUSIVE, ROW_MARK_NOKEYEXCLUSIVE, ROW_MARK_SHARE или ROW_MARK_KEYSHARE, в зависимости от запрашиваемого режима блокировки. (Код ядра будет действовать одинаково независимо от того, какой из этих четырех вариантов вы выберете). Далее вы можете определить, была ли сторонняя таблица заблокирована этим типом команды, с помощью get_plan_rowmark во время планирования или с помощью ExecFindRowMark во время выполнения; следует проверить не только то, отлична ли от NULL возвращенная структура маркировки строк, но и то, что ее поле strength не содержит LCS_NONE.

Наконец, для сторонних таблиц, задействованных в команде UPDATE, DELETE или SELECT FOR UPDATE/SHARE, но не заданных для блокировки строк, можно переопределить выбор по умолчанию, то есть копирование строк целиком, выбрав в функции GetForeignRowMarkType вариант ROW_MARK_REFERENCE, чтобы сила блокировки стала LCS_NONE. Это приведет к тому, что функция RefetchForeignRow будет вызвана с этим значением для markType; затем она должна повторно извлечь строку, не не запрашивая новую блокировку. (Если у вас есть функция GetForeignRowMarkType, но вы не хотите повторно извлекать незаблокированные строки, выберите для LCS_NONE параметр ROW_MARK_COPY.)