59.2. Подпрограммы обёртки сторонних данных #
- 59.2.1. Подпрограммы FDW для сканирования сторонних таблиц
- 59.2.2. Подпрограммы FDW для сканирования сторонних соединений
- 59.2.3. Подпрограммы FDW для планирования обработки после сканирования/соединения
- 59.2.4. Подпрограммы FDW для изменения данных в сторонних таблицах
- 59.2.5. Подпрограммы FDW для
TRUNCATE
- 59.2.6. Подпрограммы FDW для блокировки строк
- 59.2.7. Подпрограммы FDW для
EXPLAIN
- 59.2.8. Подпрограммы FDW для
ANALYZE
- 59.2.9. Подпрограммы FDW для
IMPORT FOREIGN SCHEMA
- 59.2.10. Подпрограммы FDW для параллельного выполнения
- 59.2.11. Подпрограммы FDW для асинхронного выполнения
- 59.2.12. Подпрограммы FDW для изменения параметризации путей
Функция-обработчик FDW возвращает структуру FdwRoutine
(выделенную с помощью palloc), содержащую указатели на подпрограммы, которые реализуют описанные ниже функции. Из всех функций обязательными являются только те, что касаются сканирования, а остальные могут отсутствовать.
Тип структуры FdwRoutine
объявлен в src/include/foreign/fdwapi.h
, там же можно узнать дополнительные подробности.
59.2.1. Подпрограммы 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
для этой сторонней таблицы ещё не выполнялся.)
За дополнительными сведениями обратитесь к Разделу 59.4.
void GetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
Формирует возможные пути доступа для сканирования сторонней таблицы. Эта функция вызывается при планировании запроса. Ей передаются те же параметры, что и функции GetForeignRelSize
, которая к этому времени уже будет вызвана.
Эта функция должна выдать минимум один путь доступа (узел ForeignPath
) для сканирования сторонней таблицы и должна вызвать add_path
, чтобы добавить каждый такой путь в baserel->pathlist
. Для формирования узлов ForeignPath
рекомендуется вызывать create_foreignscan_path
. Данная функция может выдавать несколько путей доступа, то есть путей, для которых по заданным pathkeys
можно получить уже отсортированный результат. Каждый путь доступа должен содержать оценки стоимости и может содержать любую частную информацию FDW, необходимую для выбора целевого метода сканирования.
За дополнительными сведениями обратитесь к Разделу 59.4.
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
; для формирования этого узла рекомендуется использовать make_foreignscan
.
За дополнительными сведениями обратитесь к Разделу 59.4.
void BeginForeignScan(ForeignScanState *node, int eflags);
Начинает сканирование сторонней таблицы. Эта функция вызывается при запуске исполнителя. Она должна выполнить все подготовительные действия, необходимые для осуществления сканирования, но не должна собственно производить сканирование (оно должно начаться с первым вызовом IterateForeignScan
). Узел ForeignScanState
уже был создан, но его поле fdw_state
по-прежнему NULL. Информацию о сканируемой таблице можно получить через узел ForeignScanState
(в частности, из нижележащего узла ForeignScan
, содержащего частную информацию FDW, заданную функцией GetForeignPlan
). Параметр eflags
содержит битовые флаги, описывающие режим работы исполнителя для этого узла плана.
Заметьте, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY)
не равно нулю, эта функция не должна выполнять какие-либо внешне проявляющиеся действия; она должна сделать только то, что необходимо для получения состояния узла, подходящего для ExplainForeignScan
и EndForeignScan
.
TupleTableSlot * IterateForeignScan(ForeignScanState *node);
Выбирает одну строку из стороннего источника и возвращает её в слоте таблицы кортежей (для этой цели следует использовать ScanTupleSlot
, переданный с узлом). Когда строки заканчиваются, возвращает NULL. Инфраструктура слотов таблицы кортежей позволяет возвращать как физические, так и виртуальные кортежи; в большинстве случаев второй вариант предпочтительнее с точки зрения производительности. Заметьте, что эта функция вызывается в кратковременном контексте памяти, который будет сбрасываться между вызовами. Если вам нужна более долгоживущая память, создайте соответствующий контекст в BeginForeignScan
либо используйте es_query_cxt
из структуры EState
, переданной с узлом.
Возвращаемые строки должны соответствовать целевому списку fdw_scan_tlist
, если он передаётся, а в противном случае — типу строки сканируемой сторонней таблицы. Если вы решите для оптимизации не возвращать ненужные столбцы, в их позиции нужно вставить NULL, либо сформировать список fdw_scan_tlist
без этих столбцов.
Заметьте, что для исполнителя PostgreSQL не важно, удовлетворяют ли возвращаемые строки каким-либо ограничениям, определённым для сторонней таблицы — но это важно для планировщика, так что запросы могут оптимизироваться некорректно, если в сторонней таблице будут видны строки, не удовлетворяющие объявленному ограничению. Если ограничение нарушается, тогда как пользователь объявил, что оно должно выполняться, может быть уместно сообщить об ошибке (точно так же, как и при несовпадении типов данных).
void ReScanForeignScan(ForeignScanState *node);
Перезапускает сканирование с начала. Заметьте, что значения параметров, от которых зависит сканирование, могли измениться, так что новое сканирование не обязательно вернёт те же строки.
void EndForeignScan(ForeignScanState *node);
Завершает сканирование и освобождает ресурсы. Обычно при этом не нужно освобождать память, выделенную через palloc, но например, открытые файлы и подключения к удалённым серверам следует закрыть.
59.2.2. Подпрограммы 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
, представляющий набор столбцов, которые будут выдаваться во время выполнения в возвращаемых кортежах.
Примечание
Начиная с PostgreSQL 16, в fs_relids
учитываются наборы строк, полученные в результате внешних соединений, если они были задействованы в данном соединении. Новое поле fs_base_relids
включает только базовые отношения, то есть копирует старую семантику fs_relids
.
За дополнительными сведениями обратитесь к Разделу 59.4.
59.2.3. Подпрограммы 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
, эта функция не обязательно должна возвращать какие-либо пути, так как всегда возможны пути с локальной обработкой.
Параметр stage
определяет, какой шаг после сканирования/соединения рассматривается в данный момент. Параметр output_rel
указывает на верхнее отношение, которое должно получить пути, представляющие вычисление этого шага, а input_rel
— на отношение, представляющее входные данные для этого шага. В параметре extra
передаётся дополнительная информация; в настоящее время он устанавливается только для UPPERREL_PARTIAL_GROUP_AGG
и UPPERREL_GROUP_AGG
(в этом случае указывает на структуру GroupPathExtraData
) и для UPPERREL_FINAL
(тогда он указывает на структуру FinalPathExtraData
). Заметьте, что пути ForeignPath
, добавляемые в output_rel
, обычно не будут напрямую зависеть от путей input_rel
, так как ожидается, что они будут обрабатываться снаружи. Однако изучить пути, построенные для предыдущего шага обработки, может быть полезно для исключения лишних операций при планировании.
За дополнительными сведениями обратитесь к Разделу 59.4.
59.2.4. Подпрограммы FDW для изменения данных в сторонних таблицах #
Если FDW поддерживает запись в сторонние таблицы, она должна предоставить некоторые или все подпрограммы, реализующие следующие функции, в зависимости от потребностей и возможностей FDW:
void AddForeignUpdateTargets(PlannerInfo *root, Index rtindex, RangeTblEntry *target_rte, Relation target_relation);
Операции UPDATE
и DELETE
выполняются со строками, ранее выбранными функциями сканирования таблицы. FDW может потребоваться дополнительная информация, например, ID строки или значения столбцов первичного ключа, чтобы точно знать, какую именно строку нужно изменить или удалить. Для этого данная функция может добавить дополнительные скрытые или «отбросовые» целевые столбцы в список столбцов, которые должны быть получены из сторонней таблицы во время UPDATE
или DELETE
.
Для этого создайте структуру Var
, представляющую нужное дополнительное значение, и передайте её функции add_row_identity_var
вместе с именем отбросового столбца. (Это можно сделать несколько раз, если требуется добавить несколько столбцов.) Имя отбросового столбца в каждой структуре Var
должно быть уникальным, за исключением случая с одноимёнными структурами Var, отличающимися только значением varno
— в таких структурах имена могут и должны быть одинаковыми. Основная система использует следующие имена отбросовых столбцов: tableoid
для столбца tableoid
таблицы, ctid
или ctid
для N
ctid
, wholerow
для переменной Var
, содержащей целую строку и отмеченной vartype
= RECORD
, и wholerow
для переменной N
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
, соответствующим целевым отношениям.
За дополнительными сведениями обратитесь к Разделу 59.4.
Если указатель PlanForeignModify
равен NULL
, дополнительные действия во время планирования не предпринимаются, и в качестве fdw_private
в BeginForeignModify
поступит NULL.
void BeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, int eflags);
Начинает выполнение операции изменения данных в сторонней таблице. Эта подпрограмма выполняется при запуске исполнителя. Она должна выполнять любые подготовительные действия, необходимые для того, чтобы собственно произвести изменения в таблице. Впоследствии для кортежей, которые будут вставляться, изменяться или удаляться, будет вызываться ExecForeignInsert/ExecForeignBatchInsert
, ExecForeignUpdate
или ExecForeignDelete
.
В параметре mtstate
передаётся общее состояние выполняемого плана узла ModifyTable
; через эту структуру доступны глобальные сведения о плане и состояние выполнения. В rinfo
передаётся структура ResultRelInfo
, описывающая целевую стороннюю таблицу. (Если FDW нужно сохранить частное состояние, необходимое для этой операции, она может воспользоваться полем ri_FdwState
структуры ResultRelInfo
.) В fdw_private
передаются частные данные, если они были сформированы процедурой PlanForeignModify
. Параметр subplan_index
определяет целевое отношение в данном узле ModifyTable
, а в eflags
передаются битовые флаги, описывающие режим работы исполнителя для этого узла плана.
Заметьте, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY)
не равно нулю, эта функция не должна выполнять какие-либо внешне проявляющиеся действия; она должна сделать только то, что необходимо для получения состояния узла, подходящего для 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
. Ниже описаны функции обратного вызова, позволяющие реализовать поддержку этих операций в обёртке сторонних данных.
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
.
Заметьте, что эта функция также вызывается при добавлении кортежей, перенаправленных в секцию в сторонней таблице, или при выполнении COPY FROM
со сторонней таблицей. В данных случаях она вызывается не так, как при выполнении обычного INSERT
. Ниже описаны функции обратного вызова, позволяющие реализовать поддержку этих операций в обёртке сторонних данных.
int GetForeignModifyBatchSize(ResultRelInfo *rinfo);
Выдаёт максимальное количество кортежей, которое может быть обработано за один вызов функции ExecForeignBatchInsert
для указанной сторонней таблицы. Исполнитель передаёт не более данного количества кортежей в ExecForeignBatchInsert
. rinfo
— это структура ResultRelInfo
, описывающая целевую стороннюю таблицу. Ожидается, что FDW даст пользователю возможность задать этот параметр на уровне стороннего сервера и/или таблицы, либо будет использоваться некоторое фиксированное значение.
Если указатель на ExecForeignInsert
или 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
.
Данные в возвращаемом слоте используются, только если оператор UPDATE
содержит предложение 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
. Триггерам нужны все столбцы, но для предложения RETURNING
FDW может ради оптимизации не возвращать некоторые или все столбцы, в зависимости от его содержания. Так или иначе, какой-либо слот необходимо вернуть, чтобы отметить, что операция успешна, иначе возвращённое число строк будет неверным.
Если указатель на ExecForeignDelete
равен NULL
, удалить данные из сторонней таблицы не удастся, а в ответ будет выдаваться сообщение об ошибке.
void EndForeignModify(EState *estate, ResultRelInfo *rinfo);
Завершает изменение данных в таблице и освобождает ресурсы. Обычно при этом не нужно освобождать память, выделенную через palloc, но например, открытые файлы и подключения к удалённым серверам следует закрыть.
Если указатель на EndForeignModify
равен NULL
, никакое действие при завершении исполнителя не выполняется.
Кортежи, вставляемые в секционированную таблицу командами INSERT
и COPY FROM
, направляются в соответствующие секции. Если обёртка сторонних данных поддерживает перенаправление в секции в сторонних таблицах, она также должна предоставить описанные ниже обработчики. Эти функции также вызываются, когда результат 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
— и когда сторонняя таблица является секцией, выбранной для помещения кортежа, и когда это целевое отношение данной команды.
Если указатель на BeginForeignInsert
равен NULL
, никакое действие при инициализации не выполняется.
Заметьте, что если обёртка сторонних данных не поддерживает перенаправление кортежей в секции и/или операцию COPY FROM
со сторонними таблицами, эта функция или вызываемая за ней ExecForeignInsert/ExecForeignBatchInsert
должны выдать соответствующую ошибку.
void EndForeignInsert(EState *estate, ResultRelInfo *rinfo);
Завершает операцию добавления и освобождает ресурсы. Обычно при этом не нужно освобождать память, выделенную через palloc, но например, открытые файлы и подключения к удалённым серверам следует закрыть.
Если указатель на EndForeignInsert
равен 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
.
За дополнительными сведениями обратитесь к Разделу 59.4.
Если указатель на PlanDirectModify
равен NULL
, сервер не будет пытаться произвести прямую модификацию.
void BeginDirectModify(ForeignScanState *node, int eflags);
Подготавливает прямую модификацию на удалённом сервере. Эта функция вызывается при запуске исполнителя. Она должна выполнить все подготовительные действия, необходимые для осуществления прямой модификации (модификация должна начаться с первым вызовом IterateDirectModify
). Узел ForeignScanState
уже был создан, но его поле fdw_state
по-прежнему NULL. Информацию о модифицируемой таблице можно получить через узел ForeignScanState
(в частности, из нижележащего узла ForeignScan
, содержащего частную информацию FDW, заданную функцией PlanDirectModify
). Параметр eflags
содержит битовые флаги, описывающие режим работы исполнителя для этого узла плана.
Заметьте, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY)
не равно нулю, эта функция не должна выполнять какие-либо внешне проявляющиеся действия; она должна сделать только то, что необходимо для получения состояния узла, подходящего для 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
, сервер не будет пытаться произвести прямую модификацию.
59.2.5. Подпрограммы FDW для TRUNCATE
#
void ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs);
Опустошает сторонние таблицы. Эта функция вызывается, когда для сторонней таблицы выполняется TRUNCATE. В rels
передаётся список структур Relation
, описывающих сторонние таблицы, подлежащие опустошению.
Значением behavior
может быть либо DROP_RESTRICT
, либо DROP_CASCADE
, в зависимости от того, какое указание (RESTRICT
или CASCADE
, соответственно) присутствовало в исходной команде TRUNCATE
.
Если restart_seqs
имеет значение true
, в исходной команде TRUNCATE
было запрошено поведение RESTART IDENTITY
, в противном случае было запрошено поведение CONTINUE IDENTITY
.
Заметьте, что параметры ONLY
, указанные в исходной команде TRUNCATE
, не передаются в функцию ExecForeignTruncate
. Подобным образом работают функции-обработчики SELECT
, UPDATE
и DELETE
для сторонней таблицы.
ExecForeignTruncate
вызывается отдельно для каждого стороннего сервера, на котором должны опустошаться сторонние таблицы. Это означает, что все сторонние таблицы, включённые в rels
, должны относиться к одному серверу.
Если указатель на ExecForeignTruncate
равен NULL
, опустошить данные в сторонней таблице не удастся, а в ответ будет выдаваться сообщение об ошибке.
59.2.6. Подпрограммы FDW для блокировки строк #
Если FDW желает поддержать функцию поздней блокировки строк (описанную в Разделе 59.5), она должна предоставить следующие реализующие подпрограммы:
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
никогда не будет вызываться, так что и её задавать не нужно.)
За подробностями обратитесь к Разделу 59.5.
void RefetchForeignRow(EState *estate, ExecRowMark *erm, Datum rowid, TupleTableSlot *slot, bool *updated);
Повторно считывает один кортеж из сторонней таблицы после блокировки, если она требуется. В estate
передаётся глобальное состояние выполнения запроса. В erm
передаётся структура ExecRowMark
, описывающая целевую стороннюю таблицу и тип запрашиваемой блокировки (если она требуется). В параметре slot
при вызове не содержится ничего полезного, но в него можно поместить возвращаемый кортеж. Параметр updated
является выходным.
Эта функция должна сохранить кортеж в переданном слоте или очистить его, если получить блокировку строки не удаётся. Тип запрашиваемой блокировки строки определяется значением erm->markType
, которое было до этого возвращено функцией GetForeignRowMarkType
. (Вариант ROW_MARK_REFERENCE
означает, что нужно просто повторно выбрать кортеж, не запрашивая никакую блокировку, а ROW_MARK_COPY
никогда не поступает в эту подпрограмму.)
Кроме того, переменной *updated
следует присвоить true
, если была считана изменённая версия кортежа, а не версия, полученная ранее. (Если FDW не знает этого наверняка, рекомендуется всегда возвращать true
.)
Заметьте, что по умолчанию в случае неудачи при попытке получить блокировку строки должна выдаваться ошибка; пустой слот может возвращаться, только если в erm->waitPolicy
выбран вариант SKIP LOCKED
.
В rowid
передаётся значение ctid
, полученное ранее для строки, которую нужно считать повторно. Хотя значение rowid
передаётся в виде Datum
, в настоящее время это может быть только tid
. Такой интерфейс функции выбран с расчётом на то, чтобы в будущем в качестве идентификаторов строк могли приниматься и другие типы данных.
Если указатель на RefetchForeignRow
равен NULL
, повторно выбрать данные не удастся, в ответ будет выдаваться сообщение об ошибке.
За подробностями обратитесь к Разделу 59.5.
bool RecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot);
Перепроверяет, соответствует ли по-прежнему ранее возвращённый кортеж применимым условиям сканирования и соединения, и возможно выдаёт изменённую версию кортежа. Для обёрток сторонних данных, которые не выносят соединение наружу, обычно удобнее присвоить этому указателю NULL
и задать fdw_recheck_quals
. Однако когда внешние соединения выносятся наружу, недостаточно повторно применить к результирующему кортежу проверки, относящиеся ко всем базовым таблицам, даже если присутствуют все атрибуты, так как невыполнение некоторого условия может приводить и к обнулению некоторых атрибутов, а не только исключению этого кортежа. RecheckForeignScan
может перепроверить условия и возвратить true, если они по-прежнему выполняются, или false в противном случае, но также она может записать в переданный слот кортеж на замену предыдущему.
Чтобы вынести соединение наружу, обёртка сторонних данных обычно конструирует альтернативный план локального соединения, применяемый только для перепроверок; он становится внешним подпланом узла ForeignScan
. Когда требуется перепроверка, может быть выполнен этот подплан и результирующий кортеж сохранён в слоте. Этот план может не быть эффективным, так как ни одна базовая таблица не выдаст больше одной строки; например, он может реализовывать все соединения в виде вложенных циклов. Для поиска подходящего локального пути соединения в существующих путях можно воспользоваться функцией GetExistingLocalJoinPath
. Функция GetExistingLocalJoinPath
ищет непараметризованный путь в списке путей заданного отношения соединения. (Если такой путь не находится, она возвращает NULL, и в этом случае обёртка сторонних данных может построить локальный путь сама или решить не создавать пути доступа для этого соединения.)
59.2.7. Подпрограммы 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
не выводится.
59.2.8. Подпрограммы 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
нужно записать 0.)
59.2.9. Подпрограммы FDW для IMPORT FOREIGN SCHEMA
#
List * ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);
Получает список команд, создающих сторонние таблицы. Эта функция вызывается при выполнении команды IMPORT FOREIGN SCHEMA; ей передаётся дерево разбора этого оператора и OID целевого стороннего сервера. Она должна вернуть набор строк C, в каждой из которых должна содержаться команда 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
.
59.2.10. Подпрограммы 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
. Так как сегмент DSM, используемый параллельным запросом, освобождается сразу после вызова этого обработчика, обёртки сторонних данных, которым нужно выполнять некоторые действия до ликвидации сегмента DSM, должны реализовывать этот метод.
59.2.11. Подпрограммы FDW для асинхронного выполнения #
Узел ForeignScan
может дополнительно поддерживать асинхронное выполнение, как описано в файле src/backend/executor/README
. Перечисленные ниже функции являются необязательными, но все они нужны для обеспечения поддержки асинхронного выполнения.
bool IsForeignPathAsyncCapable(ForeignPath *path);
Проверяет, возможно ли для данного пути ForeignPath
асинхронное сканирование нижележащего стороннего отношения. Эта функция будет вызываться только в конце планирования запроса, если данный путь является прямым потомком пути AppendPath
и когда планировщик считает, что асинхронное выполнение улучшает производительность. Если для данного пути возможно асинхронное сканирование, эта функция должна возвращать true.
Если эта функция не определена, предполагается, что для данного пути стороннее отношение сканируется с помощью функции IterateForeignScan
. (Это означает, что функции обратного вызова, описанные ниже, никогда не будут вызываться, поэтому их также не нужно задавать.)
void ForeignAsyncRequest(AsyncRequest *areq);
Асинхронно выдаёт один кортеж из узла ForeignScan
. areq
— это структура AsyncRequest
, описывающая узел ForeignScan
и родительский узел Append
, запросивший у него кортеж. Эта функция должна поместить кортеж в слот, переданный в areq->result
, и записать true
в areq->request_complete
, если кортеж готов. Если же он ещё не готов и необходимо дождаться внешнего по отношению к главному серверу события, например получения данных из сети, эта функция должна записать false
в areq->request_complete
и 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
. См. комментарии к функции ExecAsyncConfigureWait
в файле src/backend/executor/execAsync.c
для получения дополнительной информации. Когда произойдёт событие файлового дескриптора, будет вызвана функция ForeignAsyncNotify
.
void ForeignAsyncNotify(AsyncRequest *areq);
Обрабатывает соответствующее произошедшее событие, затем асинхронно выдаёт один кортеж из узла ForeignScan
. Эта функция должна устанавливать выходные параметры в areq
так же, как это делает функция ForeignAsyncRequest
.
59.2.12. Подпрограммы 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
.
59.2. Foreign Data Wrapper Callback Routines #
- 59.2.1. FDW Routines for Scanning Foreign Tables
- 59.2.2. FDW Routines for Scanning Foreign Joins
- 59.2.3. FDW Routines for Planning Post-Scan/Join Processing
- 59.2.4. FDW Routines for Updating Foreign Tables
- 59.2.5. FDW Routines for
TRUNCATE
- 59.2.6. FDW Routines for Row Locking
- 59.2.7. FDW Routines for
EXPLAIN
- 59.2.8. FDW Routines for
ANALYZE
- 59.2.9. FDW Routines for
IMPORT FOREIGN SCHEMA
- 59.2.10. FDW Routines for Parallel Execution
- 59.2.11. FDW Routines for Asynchronous Execution
- 59.2.12. FDW Routines for Reparameterization of Paths
The FDW handler function returns a palloc'd FdwRoutine
struct containing pointers to the callback functions described below. The scan-related functions are required, the rest are optional.
The FdwRoutine
struct type is declared in src/include/foreign/fdwapi.h
, which see for additional details.
59.2.1. FDW Routines for Scanning Foreign Tables #
void GetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
Obtain relation size estimates for a foreign table. This is called at the beginning of planning for a query that scans a foreign table. root
is the planner's global information about the query; baserel
is the planner's information about this table; and foreigntableid
is the pg_class
OID of the foreign table. (foreigntableid
could be obtained from the planner data structures, but it's passed explicitly to save effort.)
This function should update baserel->rows
to be the expected number of rows returned by the table scan, after accounting for the filtering done by the restriction quals. The initial value of baserel->rows
is just a constant default estimate, which should be replaced if at all possible. The function may also choose to update baserel->width
if it can compute a better estimate of the average result row width. (The initial value is based on column data types and on column average-width values measured by the last ANALYZE
.) Also, this function may update baserel->tuples
if it can compute a better estimate of the foreign table's total row count. (The initial value is from pg_class
.reltuples
which represents the total row count seen by the last ANALYZE
; it will be -1
if no ANALYZE
has been done on this foreign table.)
See Section 59.4 for additional information.
void GetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
Create possible access paths for a scan on a foreign table. This is called during query planning. The parameters are the same as for GetForeignRelSize
, which has already been called.
This function must generate at least one access path (ForeignPath
node) for a scan on the foreign table and must call add_path
to add each such path to baserel->pathlist
. It's recommended to use create_foreignscan_path
to build the ForeignPath
nodes. The function can generate multiple access paths, e.g., a path which has valid pathkeys
to represent a pre-sorted result. Each access path must contain cost estimates, and can contain any FDW-private information that is needed to identify the specific scan method intended.
See Section 59.4 for additional information.
ForeignScan * GetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan);
Create a ForeignScan
plan node from the selected foreign access path. This is called at the end of query planning. The parameters are as for GetForeignRelSize
, plus the selected ForeignPath
(previously produced by GetForeignPaths
, GetForeignJoinPaths
, or GetForeignUpperPaths
), the target list to be emitted by the plan node, the restriction clauses to be enforced by the plan node, and the outer subplan of the ForeignScan
, which is used for rechecks performed by RecheckForeignScan
. (If the path is for a join rather than a base relation, foreigntableid
is InvalidOid
.)
This function must create and return a ForeignScan
plan node; it's recommended to use make_foreignscan
to build the ForeignScan
node.
See Section 59.4 for additional information.
void BeginForeignScan(ForeignScanState *node, int eflags);
Begin executing a foreign scan. This is called during executor startup. It should perform any initialization needed before the scan can start, but not start executing the actual scan (that should be done upon the first call to IterateForeignScan
). The ForeignScanState
node has already been created, but its fdw_state
field is still NULL. Information about the table to scan is accessible through the ForeignScanState
node (in particular, from the underlying ForeignScan
plan node, which contains any FDW-private information provided by GetForeignPlan
). eflags
contains flag bits describing the executor's operating mode for this plan node.
Note that when (eflags & EXEC_FLAG_EXPLAIN_ONLY)
is true, this function should not perform any externally-visible actions; it should only do the minimum required to make the node state valid for ExplainForeignScan
and EndForeignScan
.
TupleTableSlot * IterateForeignScan(ForeignScanState *node);
Fetch one row from the foreign source, returning it in a tuple table slot (the node's ScanTupleSlot
should be used for this purpose). Return NULL if no more rows are available. The tuple table slot infrastructure allows either a physical or virtual tuple to be returned; in most cases the latter choice is preferable from a performance standpoint. Note that this is called in a short-lived memory context that will be reset between invocations. Create a memory context in BeginForeignScan
if you need longer-lived storage, or use the es_query_cxt
of the node's EState
.
The rows returned must match the fdw_scan_tlist
target list if one was supplied, otherwise they must match the row type of the foreign table being scanned. If you choose to optimize away fetching columns that are not needed, you should insert nulls in those column positions, or else generate a fdw_scan_tlist
list with those columns omitted.
Note that PostgreSQL's executor doesn't care whether the rows returned violate any constraints that were defined on the foreign table — but the planner does care, and may optimize queries incorrectly if there are rows visible in the foreign table that do not satisfy a declared constraint. If a constraint is violated when the user has declared that the constraint should hold true, it may be appropriate to raise an error (just as you would need to do in the case of a data type mismatch).
void ReScanForeignScan(ForeignScanState *node);
Restart the scan from the beginning. Note that any parameters the scan depends on may have changed value, so the new scan does not necessarily return exactly the same rows.
void EndForeignScan(ForeignScanState *node);
End the scan and release resources. It is normally not important to release palloc'd memory, but for example open files and connections to remote servers should be cleaned up.
59.2.2. FDW Routines for Scanning Foreign Joins #
If an FDW supports performing foreign joins remotely (rather than by fetching both tables' data and doing the join locally), it should provide this callback function:
void GetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra);
Create possible access paths for a join of two (or more) foreign tables that all belong to the same foreign server. This optional function is called during query planning. As with GetForeignPaths
, this function should generate ForeignPath
path(s) for the supplied joinrel
(use create_foreign_join_path
to build them), and call add_path
to add these paths to the set of paths considered for the join. But unlike GetForeignPaths
, it is not necessary that this function succeed in creating at least one path, since paths involving local joining are always possible.
Note that this function will be invoked repeatedly for the same join relation, with different combinations of inner and outer relations; it is the responsibility of the FDW to minimize duplicated work.
If a ForeignPath
path is chosen for the join, it will represent the entire join process; paths generated for the component tables and subsidiary joins will not be used. Subsequent processing of the join path proceeds much as it does for a path scanning a single foreign table. One difference is that the scanrelid
of the resulting ForeignScan
plan node should be set to zero, since there is no single relation that it represents; instead, the fs_relids
field of the ForeignScan
node represents the set of relations that were joined. (The latter field is set up automatically by the core planner code, and need not be filled by the FDW.) Another difference is that, because the column list for a remote join cannot be found from the system catalogs, the FDW must fill fdw_scan_tlist
with an appropriate list of TargetEntry
nodes, representing the set of columns it will supply at run time in the tuples it returns.
Note
Beginning with PostgreSQL 16, fs_relids
includes the rangetable indexes of outer joins, if any were involved in this join. The new field fs_base_relids
includes only base relation indexes, and thus mimics fs_relids
's old semantics.
See Section 59.4 for additional information.
59.2.3. FDW Routines for Planning Post-Scan/Join Processing #
If an FDW supports performing remote post-scan/join processing, such as remote aggregation, it should provide this callback function:
void GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra);
Create possible access paths for upper relation processing, which is the planner's term for all post-scan/join query processing, such as aggregation, window functions, sorting, and table updates. This optional function is called during query planning. Currently, it is called only if all base relation(s) involved in the query belong to the same FDW. This function should generate ForeignPath
path(s) for any post-scan/join processing that the FDW knows how to perform remotely (use create_foreign_upper_path
to build them), and call add_path
to add these paths to the indicated upper relation. As with GetForeignJoinPaths
, it is not necessary that this function succeed in creating any paths, since paths involving local processing are always possible.
The stage
parameter identifies which post-scan/join step is currently being considered. output_rel
is the upper relation that should receive paths representing computation of this step, and input_rel
is the relation representing the input to this step. The extra
parameter provides additional details, currently, it is set only for UPPERREL_PARTIAL_GROUP_AGG
or UPPERREL_GROUP_AGG
, in which case it points to a GroupPathExtraData
structure; or for UPPERREL_FINAL
, in which case it points to a FinalPathExtraData
structure. (Note that ForeignPath
paths added to output_rel
would typically not have any direct dependency on paths of the input_rel
, since their processing is expected to be done externally. However, examining paths previously generated for the previous processing step can be useful to avoid redundant planning work.)
See Section 59.4 for additional information.
59.2.4. FDW Routines for Updating Foreign Tables #
If an FDW supports writable foreign tables, it should provide some or all of the following callback functions depending on the needs and capabilities of the FDW:
void AddForeignUpdateTargets(PlannerInfo *root, Index rtindex, RangeTblEntry *target_rte, Relation target_relation);
UPDATE
and DELETE
operations are performed against rows previously fetched by the table-scanning functions. The FDW may need extra information, such as a row ID or the values of primary-key columns, to ensure that it can identify the exact row to update or delete. To support that, this function can add extra hidden, or “junk”, target columns to the list of columns that are to be retrieved from the foreign table during an UPDATE
or DELETE
.
To do that, construct a Var
representing an extra value you need, and pass it to add_row_identity_var
, along with a name for the junk column. (You can do this more than once if several columns are needed.) You must choose a distinct junk column name for each different Var
you need, except that Var
s that are identical except for the varno
field can and should share a column name. The core system uses the junk column names tableoid
for a table's tableoid
column, ctid
or ctid
for N
ctid
, wholerow
for a whole-row Var
marked with vartype
= RECORD
, and wholerow
for a whole-row N
Var
with vartype
equal to the table's declared row type. Re-use these names when you can (the planner will combine duplicate requests for identical junk columns). If you need another kind of junk column besides these, it might be wise to choose a name prefixed with your extension name, to avoid conflicts against other FDWs.
If the AddForeignUpdateTargets
pointer is set to NULL
, no extra target expressions are added. (This will make it impossible to implement DELETE
operations, though UPDATE
may still be feasible if the FDW relies on an unchanging primary key to identify rows.)
List * PlanForeignModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index);
Perform any additional planning actions needed for an insert, update, or delete on a foreign table. This function generates the FDW-private information that will be attached to the ModifyTable
plan node that performs the update action. This private information must have the form of a List
, and will be delivered to BeginForeignModify
during the execution stage.
root
is the planner's global information about the query. plan
is the ModifyTable
plan node, which is complete except for the fdwPrivLists
field. resultRelation
identifies the target foreign table by its range table index. subplan_index
identifies which target of the ModifyTable
plan node this is, counting from zero; use this if you want to index into per-target-relation substructures of the plan
node.
See Section 59.4 for additional information.
If the PlanForeignModify
pointer is set to NULL
, no additional plan-time actions are taken, and the fdw_private
list delivered to BeginForeignModify
will be NIL.
void BeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, int eflags);
Begin executing a foreign table modification operation. This routine is called during executor startup. It should perform any initialization needed prior to the actual table modifications. Subsequently, ExecForeignInsert/ExecForeignBatchInsert
, ExecForeignUpdate
or ExecForeignDelete
will be called for tuple(s) to be inserted, updated, or deleted.
mtstate
is the overall state of the ModifyTable
plan node being executed; global data about the plan and execution state is available via this structure. rinfo
is the ResultRelInfo
struct describing the target foreign table. (The ri_FdwState
field of ResultRelInfo
is available for the FDW to store any private state it needs for this operation.) fdw_private
contains the private data generated by PlanForeignModify
, if any. subplan_index
identifies which target of the ModifyTable
plan node this is. eflags
contains flag bits describing the executor's operating mode for this plan node.
Note that when (eflags & EXEC_FLAG_EXPLAIN_ONLY)
is true, this function should not perform any externally-visible actions; it should only do the minimum required to make the node state valid for ExplainForeignModify
and EndForeignModify
.
If the BeginForeignModify
pointer is set to NULL
, no action is taken during executor startup.
TupleTableSlot * ExecForeignInsert(EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot);
Insert one tuple into the foreign table. estate
is global execution state for the query. rinfo
is the ResultRelInfo
struct describing the target foreign table. slot
contains the tuple to be inserted; it will match the row-type definition of the foreign table. planSlot
contains the tuple that was generated by the ModifyTable
plan node's subplan; it differs from slot
in possibly containing additional “junk” columns. (The planSlot
is typically of little interest for INSERT
cases, but is provided for completeness.)
The return value is either a slot containing the data that was actually inserted (this might differ from the data supplied, for example as a result of trigger actions), or NULL if no row was actually inserted (again, typically as a result of triggers). The passed-in slot
can be re-used for this purpose.
The data in the returned slot is used only if the INSERT
statement has a RETURNING
clause or involves a view WITH CHECK OPTION
; or if the foreign table has an AFTER ROW
trigger. Triggers require all columns, but the FDW could choose to optimize away returning some or all columns depending on the contents of the RETURNING
clause or WITH CHECK OPTION
constraints. Regardless, some slot must be returned to indicate success, or the query's reported row count will be wrong.
If the ExecForeignInsert
pointer is set to NULL
, attempts to insert into the foreign table will fail with an error message.
Note that this function is also called when inserting routed tuples into a foreign-table partition or executing COPY FROM
on a foreign table, in which case it is called in a different way than it is in the INSERT
case. See the callback functions described below that allow the FDW to support that.
TupleTableSlot ** ExecForeignBatchInsert(EState *estate, ResultRelInfo *rinfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int *numSlots);
Insert multiple tuples in bulk into the foreign table. The parameters are the same for ExecForeignInsert
except slots
and planSlots
contain multiple tuples and *numSlots
specifies the number of tuples in those arrays.
The return value is an array of slots containing the data that was actually inserted (this might differ from the data supplied, for example as a result of trigger actions.) The passed-in slots
can be re-used for this purpose. The number of successfully inserted tuples is returned in *numSlots
.
The data in the returned slot is used only if the INSERT
statement involves a view WITH CHECK OPTION
; or if the foreign table has an AFTER ROW
trigger. Triggers require all columns, but the FDW could choose to optimize away returning some or all columns depending on the contents of the WITH CHECK OPTION
constraints.
If the ExecForeignBatchInsert
or GetForeignModifyBatchSize
pointer is set to NULL
, attempts to insert into the foreign table will use ExecForeignInsert
. This function is not used if the INSERT
has the RETURNING
clause.
Note that this function is also called when inserting routed tuples into a foreign-table partition or executing COPY FROM
on a foreign table, in which case it is called in a different way than it is in the INSERT
case. See the callback functions described below that allow the FDW to support that.
int GetForeignModifyBatchSize(ResultRelInfo *rinfo);
Report the maximum number of tuples that a single ExecForeignBatchInsert
call can handle for the specified foreign table. The executor passes at most the given number of tuples to ExecForeignBatchInsert
. rinfo
is the ResultRelInfo
struct describing the target foreign table. The FDW is expected to provide a foreign server and/or foreign table option for the user to set this value, or some hard-coded value.
If the ExecForeignBatchInsert
or GetForeignModifyBatchSize
pointer is set to NULL
, attempts to insert into the foreign table will use ExecForeignInsert
.
TupleTableSlot * ExecForeignUpdate(EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot);
Update one tuple in the foreign table. estate
is global execution state for the query. rinfo
is the ResultRelInfo
struct describing the target foreign table. slot
contains the new data for the tuple; it will match the row-type definition of the foreign table. planSlot
contains the tuple that was generated by the ModifyTable
plan node's subplan. Unlike slot
, this tuple contains only the new values for columns changed by the query, so do not rely on attribute numbers of the foreign table to index into planSlot
. Also, planSlot
typically contains additional “junk” columns. In particular, any junk columns that were requested by AddForeignUpdateTargets
will be available from this slot.
The return value is either a slot containing the row as it was actually updated (this might differ from the data supplied, for example as a result of trigger actions), or NULL if no row was actually updated (again, typically as a result of triggers). The passed-in slot
can be re-used for this purpose.
The data in the returned slot is used only if the UPDATE
statement has a RETURNING
clause or involves a view WITH CHECK OPTION
; or if the foreign table has an AFTER ROW
trigger. Triggers require all columns, but the FDW could choose to optimize away returning some or all columns depending on the contents of the RETURNING
clause or WITH CHECK OPTION
constraints. Regardless, some slot must be returned to indicate success, or the query's reported row count will be wrong.
If the ExecForeignUpdate
pointer is set to NULL
, attempts to update the foreign table will fail with an error message.
TupleTableSlot * ExecForeignDelete(EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot);
Delete one tuple from the foreign table. estate
is global execution state for the query. rinfo
is the ResultRelInfo
struct describing the target foreign table. slot
contains nothing useful upon call, but can be used to hold the returned tuple. planSlot
contains the tuple that was generated by the ModifyTable
plan node's subplan; in particular, it will carry any junk columns that were requested by AddForeignUpdateTargets
. The junk column(s) must be used to identify the tuple to be deleted.
The return value is either a slot containing the row that was deleted, or NULL if no row was deleted (typically as a result of triggers). The passed-in slot
can be used to hold the tuple to be returned.
The data in the returned slot is used only if the DELETE
query has a RETURNING
clause or the foreign table has an AFTER ROW
trigger. Triggers require all columns, but the FDW could choose to optimize away returning some or all columns depending on the contents of the RETURNING
clause. Regardless, some slot must be returned to indicate success, or the query's reported row count will be wrong.
If the ExecForeignDelete
pointer is set to NULL
, attempts to delete from the foreign table will fail with an error message.
void EndForeignModify(EState *estate, ResultRelInfo *rinfo);
End the table update and release resources. It is normally not important to release palloc'd memory, but for example open files and connections to remote servers should be cleaned up.
If the EndForeignModify
pointer is set to NULL
, no action is taken during executor shutdown.
Tuples inserted into a partitioned table by INSERT
or COPY FROM
are routed to partitions. If an FDW supports routable foreign-table partitions, it should also provide the following callback functions. These functions are also called when COPY FROM
is executed on a foreign table.
void BeginForeignInsert(ModifyTableState *mtstate, ResultRelInfo *rinfo);
Begin executing an insert operation on a foreign table. This routine is called right before the first tuple is inserted into the foreign table in both cases when it is the partition chosen for tuple routing and the target specified in a COPY FROM
command. It should perform any initialization needed prior to the actual insertion. Subsequently, ExecForeignInsert
or ExecForeignBatchInsert
will be called for tuple(s) to be inserted into the foreign table.
mtstate
is the overall state of the ModifyTable
plan node being executed; global data about the plan and execution state is available via this structure. rinfo
is the ResultRelInfo
struct describing the target foreign table. (The ri_FdwState
field of ResultRelInfo
is available for the FDW to store any private state it needs for this operation.)
When this is called by a COPY FROM
command, the plan-related global data in mtstate
is not provided and the planSlot
parameter of ExecForeignInsert
subsequently called for each inserted tuple is NULL
, whether the foreign table is the partition chosen for tuple routing or the target specified in the command.
If the BeginForeignInsert
pointer is set to NULL
, no action is taken for the initialization.
Note that if the FDW does not support routable foreign-table partitions and/or executing COPY FROM
on foreign tables, this function or ExecForeignInsert/ExecForeignBatchInsert
subsequently called must throw error as needed.
void EndForeignInsert(EState *estate, ResultRelInfo *rinfo);
End the insert operation and release resources. It is normally not important to release palloc'd memory, but for example open files and connections to remote servers should be cleaned up.
If the EndForeignInsert
pointer is set to NULL
, no action is taken for the termination.
int IsForeignRelUpdatable(Relation rel);
Report which update operations the specified foreign table supports. The return value should be a bit mask of rule event numbers indicating which operations are supported by the foreign table, using the CmdType
enumeration; that is, (1 << CMD_UPDATE) = 4
for UPDATE
, (1 << CMD_INSERT) = 8
for INSERT
, and (1 << CMD_DELETE) = 16
for DELETE
.
If the IsForeignRelUpdatable
pointer is set to NULL
, foreign tables are assumed to be insertable, updatable, or deletable if the FDW provides ExecForeignInsert
, ExecForeignUpdate
, or ExecForeignDelete
respectively. This function is only needed if the FDW supports some tables that are updatable and some that are not. (Even then, it's permissible to throw an error in the execution routine instead of checking in this function. However, this function is used to determine updatability for display in the information_schema
views.)
Some inserts, updates, and deletes to foreign tables can be optimized by implementing an alternative set of interfaces. The ordinary interfaces for inserts, updates, and deletes fetch rows from the remote server and then modify those rows one at a time. In some cases, this row-by-row approach is necessary, but it can be inefficient. If it is possible for the foreign server to determine which rows should be modified without actually retrieving them, and if there are no local structures which would affect the operation (row-level local triggers, stored generated columns, or WITH CHECK OPTION
constraints from parent views), then it is possible to arrange things so that the entire operation is performed on the remote server. The interfaces described below make this possible.
bool PlanDirectModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index);
Decide whether it is safe to execute a direct modification on the remote server. If so, return true
after performing planning actions needed for that. Otherwise, return false
. This optional function is called during query planning. If this function succeeds, BeginDirectModify
, IterateDirectModify
and EndDirectModify
will be called at the execution stage, instead. Otherwise, the table modification will be executed using the table-updating functions described above. The parameters are the same as for PlanForeignModify
.
To execute the direct modification on the remote server, this function must rewrite the target subplan with a ForeignScan
plan node that executes the direct modification on the remote server. The operation
and resultRelation
fields of the ForeignScan
must be set appropriately. operation
must be set to the CmdType
enumeration corresponding to the statement kind (that is, CMD_UPDATE
for UPDATE
, CMD_INSERT
for INSERT
, and CMD_DELETE
for DELETE
), and the resultRelation
argument must be copied to the resultRelation
field.
See Section 59.4 for additional information.
If the PlanDirectModify
pointer is set to NULL
, no attempts to execute a direct modification on the remote server are taken.
void BeginDirectModify(ForeignScanState *node, int eflags);
Prepare to execute a direct modification on the remote server. This is called during executor startup. It should perform any initialization needed prior to the direct modification (that should be done upon the first call to IterateDirectModify
). The ForeignScanState
node has already been created, but its fdw_state
field is still NULL. Information about the table to modify is accessible through the ForeignScanState
node (in particular, from the underlying ForeignScan
plan node, which contains any FDW-private information provided by PlanDirectModify
). eflags
contains flag bits describing the executor's operating mode for this plan node.
Note that when (eflags & EXEC_FLAG_EXPLAIN_ONLY)
is true, this function should not perform any externally-visible actions; it should only do the minimum required to make the node state valid for ExplainDirectModify
and EndDirectModify
.
If the BeginDirectModify
pointer is set to NULL
, no attempts to execute a direct modification on the remote server are taken.
TupleTableSlot * IterateDirectModify(ForeignScanState *node);
When the INSERT
, UPDATE
or DELETE
query doesn't have a RETURNING
clause, just return NULL after a direct modification on the remote server. When the query has the clause, fetch one result containing the data needed for the RETURNING
calculation, returning it in a tuple table slot (the node's ScanTupleSlot
should be used for this purpose). The data that was actually inserted, updated or deleted must be stored in node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple
. Return NULL if no more rows are available. Note that this is called in a short-lived memory context that will be reset between invocations. Create a memory context in BeginDirectModify
if you need longer-lived storage, or use the es_query_cxt
of the node's EState
.
The rows returned must match the fdw_scan_tlist
target list if one was supplied, otherwise they must match the row type of the foreign table being updated. If you choose to optimize away fetching columns that are not needed for the RETURNING
calculation, you should insert nulls in those column positions, or else generate a fdw_scan_tlist
list with those columns omitted.
Whether the query has the clause or not, the query's reported row count must be incremented by the FDW itself. When the query doesn't have the clause, the FDW must also increment the row count for the ForeignScanState
node in the EXPLAIN ANALYZE
case.
If the IterateDirectModify
pointer is set to NULL
, no attempts to execute a direct modification on the remote server are taken.
void EndDirectModify(ForeignScanState *node);
Clean up following a direct modification on the remote server. It is normally not important to release palloc'd memory, but for example open files and connections to the remote server should be cleaned up.
If the EndDirectModify
pointer is set to NULL
, no attempts to execute a direct modification on the remote server are taken.
59.2.5. FDW Routines for TRUNCATE
#
void ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs);
Truncate foreign tables. This function is called when TRUNCATE is executed on a foreign table. rels
is a list of Relation
data structures of foreign tables to truncate.
behavior
is either DROP_RESTRICT
or DROP_CASCADE
indicating that the RESTRICT
or CASCADE
option was requested in the original TRUNCATE
command, respectively.
If restart_seqs
is true
, the original TRUNCATE
command requested the RESTART IDENTITY
behavior, otherwise the CONTINUE IDENTITY
behavior was requested.
Note that the ONLY
options specified in the original TRUNCATE
command are not passed to ExecForeignTruncate
. This behavior is similar to the callback functions of SELECT
, UPDATE
and DELETE
on a foreign table.
ExecForeignTruncate
is invoked once per foreign server for which foreign tables are to be truncated. This means that all foreign tables included in rels
must belong to the same server.
If the ExecForeignTruncate
pointer is set to NULL
, attempts to truncate foreign tables will fail with an error message.
59.2.6. FDW Routines for Row Locking #
If an FDW wishes to support late row locking (as described in Section 59.5), it must provide the following callback functions:
RowMarkType GetForeignRowMarkType(RangeTblEntry *rte, LockClauseStrength strength);
Report which row-marking option to use for a foreign table. rte
is the RangeTblEntry
node for the table and strength
describes the lock strength requested by the relevant FOR UPDATE/SHARE
clause, if any. The result must be a member of the RowMarkType
enum type.
This function is called during query planning for each foreign table that appears in an UPDATE
, DELETE
, or SELECT FOR UPDATE/SHARE
query and is not the target of UPDATE
or DELETE
.
If the GetForeignRowMarkType
pointer is set to NULL
, the ROW_MARK_COPY
option is always used. (This implies that RefetchForeignRow
will never be called, so it need not be provided either.)
See Section 59.5 for more information.
void RefetchForeignRow(EState *estate, ExecRowMark *erm, Datum rowid, TupleTableSlot *slot, bool *updated);
Re-fetch one tuple slot from the foreign table, after locking it if required. estate
is global execution state for the query. erm
is the ExecRowMark
struct describing the target foreign table and the row lock type (if any) to acquire. rowid
identifies the tuple to be fetched. slot
contains nothing useful upon call, but can be used to hold the returned tuple. updated
is an output parameter.
This function should store the tuple into the provided slot, or clear it if the row lock couldn't be obtained. The row lock type to acquire is defined by erm->markType
, which is the value previously returned by GetForeignRowMarkType
. (ROW_MARK_REFERENCE
means to just re-fetch the tuple without acquiring any lock, and ROW_MARK_COPY
will never be seen by this routine.)
In addition, *updated
should be set to true
if what was fetched was an updated version of the tuple rather than the same version previously obtained. (If the FDW cannot be sure about this, always returning true
is recommended.)
Note that by default, failure to acquire a row lock should result in raising an error; returning with an empty slot is only appropriate if the SKIP LOCKED
option is specified by erm->waitPolicy
.
The rowid
is the ctid
value previously read for the row to be re-fetched. Although the rowid
value is passed as a Datum
, it can currently only be a tid
. The function API is chosen in hopes that it may be possible to allow other data types for row IDs in future.
If the RefetchForeignRow
pointer is set to NULL
, attempts to re-fetch rows will fail with an error message.
See Section 59.5 for more information.
bool RecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot);
Recheck that a previously-returned tuple still matches the relevant scan and join qualifiers, and possibly provide a modified version of the tuple. For foreign data wrappers which do not perform join pushdown, it will typically be more convenient to set this to NULL
and instead set fdw_recheck_quals
appropriately. When outer joins are pushed down, however, it isn't sufficient to reapply the checks relevant to all the base tables to the result tuple, even if all needed attributes are present, because failure to match some qualifier might result in some attributes going to NULL, rather than in no tuple being returned. RecheckForeignScan
can recheck qualifiers and return true if they are still satisfied and false otherwise, but it can also store a replacement tuple into the supplied slot.
To implement join pushdown, a foreign data wrapper will typically construct an alternative local join plan which is used only for rechecks; this will become the outer subplan of the ForeignScan
. When a recheck is required, this subplan can be executed and the resulting tuple can be stored in the slot. This plan need not be efficient since no base table will return more than one row; for example, it may implement all joins as nested loops. The function GetExistingLocalJoinPath
may be used to search existing paths for a suitable local join path, which can be used as the alternative local join plan. GetExistingLocalJoinPath
searches for an unparameterized path in the path list of the specified join relation. (If it does not find such a path, it returns NULL, in which case a foreign data wrapper may build the local path by itself or may choose not to create access paths for that join.)
59.2.7. FDW Routines for EXPLAIN
#
void ExplainForeignScan(ForeignScanState *node, ExplainState *es);
Print additional EXPLAIN
output for a foreign table scan. This function can call ExplainPropertyText
and related functions to add fields to the EXPLAIN
output. The flag fields in es
can be used to determine what to print, and the state of the ForeignScanState
node can be inspected to provide run-time statistics in the EXPLAIN ANALYZE
case.
If the ExplainForeignScan
pointer is set to NULL
, no additional information is printed during EXPLAIN
.
void ExplainForeignModify(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, struct ExplainState *es);
Print additional EXPLAIN
output for a foreign table update. This function can call ExplainPropertyText
and related functions to add fields to the EXPLAIN
output. The flag fields in es
can be used to determine what to print, and the state of the ModifyTableState
node can be inspected to provide run-time statistics in the EXPLAIN ANALYZE
case. The first four arguments are the same as for BeginForeignModify
.
If the ExplainForeignModify
pointer is set to NULL
, no additional information is printed during EXPLAIN
.
void ExplainDirectModify(ForeignScanState *node, ExplainState *es);
Print additional EXPLAIN
output for a direct modification on the remote server. This function can call ExplainPropertyText
and related functions to add fields to the EXPLAIN
output. The flag fields in es
can be used to determine what to print, and the state of the ForeignScanState
node can be inspected to provide run-time statistics in the EXPLAIN ANALYZE
case.
If the ExplainDirectModify
pointer is set to NULL
, no additional information is printed during EXPLAIN
.
59.2.8. FDW Routines for ANALYZE
#
bool AnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages);
This function is called when ANALYZE is executed on a foreign table. If the FDW can collect statistics for this foreign table, it should return true
, and provide a pointer to a function that will collect sample rows from the table in func
, plus the estimated size of the table in pages in totalpages
. Otherwise, return false
.
If the FDW does not support collecting statistics for any tables, the AnalyzeForeignTable
pointer can be set to NULL
.
If provided, the sample collection function must have the signature
int AcquireSampleRowsFunc(Relation relation, int elevel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows);
A random sample of up to targrows
rows should be collected from the table and stored into the caller-provided rows
array. The actual number of rows collected must be returned. In addition, store estimates of the total numbers of live and dead rows in the table into the output parameters totalrows
and totaldeadrows
. (Set totaldeadrows
to zero if the FDW does not have any concept of dead rows.)
59.2.9. FDW Routines for IMPORT FOREIGN SCHEMA
#
List * ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);
Obtain a list of foreign table creation commands. This function is called when executing IMPORT FOREIGN SCHEMA, and is passed the parse tree for that statement, as well as the OID of the foreign server to use. It should return a list of C strings, each of which must contain a CREATE FOREIGN TABLE command. These strings will be parsed and executed by the core server.
Within the ImportForeignSchemaStmt
struct, remote_schema
is the name of the remote schema from which tables are to be imported. list_type
identifies how to filter table names: FDW_IMPORT_SCHEMA_ALL
means that all tables in the remote schema should be imported (in this case table_list
is empty), FDW_IMPORT_SCHEMA_LIMIT_TO
means to include only tables listed in table_list
, and FDW_IMPORT_SCHEMA_EXCEPT
means to exclude the tables listed in table_list
. options
is a list of options used for the import process. The meanings of the options are up to the FDW. For example, an FDW could use an option to define whether the NOT NULL
attributes of columns should be imported. These options need not have anything to do with those supported by the FDW as database object options.
The FDW may ignore the local_schema
field of the ImportForeignSchemaStmt
, because the core server will automatically insert that name into the parsed CREATE FOREIGN TABLE
commands.
The FDW does not have to concern itself with implementing the filtering specified by list_type
and table_list
, either, as the core server will automatically skip any returned commands for tables excluded according to those options. However, it's often useful to avoid the work of creating commands for excluded tables in the first place. The function IsImportableForeignTable()
may be useful to test whether a given foreign-table name will pass the filter.
If the FDW does not support importing table definitions, the ImportForeignSchema
pointer can be set to NULL
.
59.2.10. FDW Routines for Parallel Execution #
A ForeignScan
node can, optionally, support parallel execution. A parallel ForeignScan
will be executed in multiple processes and must return each row exactly once across all cooperating processes. To do this, processes can coordinate through fixed-size chunks of dynamic shared memory. This shared memory is not guaranteed to be mapped at the same address in every process, so it must not contain pointers. The following functions are all optional, but most are required if parallel execution is to be supported.
bool IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte);
Test whether a scan can be performed within a parallel worker. This function will only be called when the planner believes that a parallel plan might be possible, and should return true if it is safe for that scan to run within a parallel worker. This will generally not be the case if the remote data source has transaction semantics, unless the worker's connection to the data can somehow be made to share the same transaction context as the leader.
If this function is not defined, it is assumed that the scan must take place within the parallel leader. Note that returning true does not mean that the scan itself can be done in parallel, only that the scan can be performed within a parallel worker. Therefore, it can be useful to define this method even when parallel execution is not supported.
Size EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);
Estimate the amount of dynamic shared memory that will be required for parallel operation. This may be higher than the amount that will actually be used, but it must not be lower. The return value is in bytes. This function is optional, and can be omitted if not needed; but if it is omitted, the next three functions must be omitted as well, because no shared memory will be allocated for the FDW's use.
void InitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt, void *coordinate);
Initialize the dynamic shared memory that will be required for parallel operation. coordinate
points to a shared memory area of size equal to the return value of EstimateDSMForeignScan
. This function is optional, and can be omitted if not needed.
void ReInitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt, void *coordinate);
Re-initialize the dynamic shared memory required for parallel operation when the foreign-scan plan node is about to be re-scanned. This function is optional, and can be omitted if not needed. Recommended practice is that this function reset only shared state, while the ReScanForeignScan
function resets only local state. Currently, this function will be called before ReScanForeignScan
, but it's best not to rely on that ordering.
void InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc, void *coordinate);
Initialize a parallel worker's local state based on the shared state set up by the leader during InitializeDSMForeignScan
. This function is optional, and can be omitted if not needed.
void ShutdownForeignScan(ForeignScanState *node);
Release resources when it is anticipated the node will not be executed to completion. This is not called in all cases; sometimes, EndForeignScan
may be called without this function having been called first. Since the DSM segment used by parallel query is destroyed just after this callback is invoked, foreign data wrappers that wish to take some action before the DSM segment goes away should implement this method.
59.2.11. FDW Routines for Asynchronous Execution #
A ForeignScan
node can, optionally, support asynchronous execution as described in src/backend/executor/README
. The following functions are all optional, but are all required if asynchronous execution is to be supported.
bool IsForeignPathAsyncCapable(ForeignPath *path);
Test whether a given ForeignPath
path can scan the underlying foreign relation asynchronously. This function will only be called at the end of query planning when the given path is a direct child of an AppendPath
path and when the planner believes that asynchronous execution improves performance, and should return true if the given path is able to scan the foreign relation asynchronously.
If this function is not defined, it is assumed that the given path scans the foreign relation using IterateForeignScan
. (This implies that the callback functions described below will never be called, so they need not be provided either.)
void ForeignAsyncRequest(AsyncRequest *areq);
Produce one tuple asynchronously from the ForeignScan
node. areq
is the AsyncRequest
struct describing the ForeignScan
node and the parent Append
node that requested the tuple from it. This function should store the tuple into the slot specified by areq->result
, and set areq->request_complete
to true
; or if it needs to wait on an event external to the core server such as network I/O, and cannot produce any tuple immediately, set the flag to false
, and set areq->callback_pending
to true
for the ForeignScan
node to get a callback from the callback functions described below. If no more tuples are available, set the slot to NULL or an empty slot, and the areq->request_complete
flag to true
. It's recommended to use ExecAsyncRequestDone
or ExecAsyncRequestPending
to set the output parameters in the areq
.
void ForeignAsyncConfigureWait(AsyncRequest *areq);
Configure a file descriptor event for which the ForeignScan
node wishes to wait. This function will only be called when the ForeignScan
node has the areq->callback_pending
flag set, and should add the event to the as_eventset
of the parent Append
node described by the areq
. See the comments for ExecAsyncConfigureWait
in src/backend/executor/execAsync.c
for additional information. When the file descriptor event occurs, ForeignAsyncNotify
will be called.
void ForeignAsyncNotify(AsyncRequest *areq);
Process a relevant event that has occurred, then produce one tuple asynchronously from the ForeignScan
node. This function should set the output parameters in the areq
in the same way as ForeignAsyncRequest
.
59.2.12. FDW Routines for Reparameterization of Paths #
List * ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private, RelOptInfo *child_rel);
This function is called while converting a path parameterized by the top-most parent of the given child relation child_rel
to be parameterized by the child relation. The function is used to reparameterize any paths or translate any expression nodes saved in the given fdw_private
member of a ForeignPath
. The callback may use reparameterize_path_by_child
, adjust_appendrel_attrs
or adjust_appendrel_attrs_multilevel
as required.