54.2. Подпрограммы обёртки сторонних данных
- 54.2.1. Подпрограммы FDW для сканирования сторонних таблиц
- 54.2.2. Подпрограммы FDW для сканирования сторонних соединений
- 54.2.3. Подпрограммы FDW для планирования обработки после сканирования/соединения
- 54.2.4. Подпрограммы FDW для изменения данных в сторонних таблицах
- 54.2.5. Подпрограммы FDW для блокировки строк
- 54.2.6. Подпрограммы FDW для
EXPLAIN
- 54.2.7. Подпрограммы FDW для
ANALYZE
- 54.2.8. Подпрограммы FDW для
IMPORT FOREIGN SCHEMA
- 54.2.9. Подпрограммы FDW для параллельного выполнения
- 54.2.2. Подпрограммы FDW для сканирования сторонних соединений
Функция-обработчик FDW возвращает структуру FdwRoutine
(выделенную с помощью palloc), содержащую указатели на подпрограммы, которые реализуют описанные ниже функции. Из всех функций обязательными являются только те, что касаются сканирования, а остальные могут отсутствовать.
Тип структуры FdwRoutine
объявлен в src/include/foreign/fdwapi.h
, там же можно узнать дополнительные подробности.
54.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
.)
За дополнительными сведениями обратитесь к Разделу 54.4.
void GetForeignPaths (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
Формирует возможные пути доступа для сканирования сторонней таблицы. Эта функция вызывается при планировании запроса. Ей передаются те же параметры, что и функции GetForeignRelSize
, которая к этому времени уже будет вызвана.
Эта функция должна выдать минимум один путь доступа (узел ForeignPath
) для сканирования сторонней таблицы и должна вызвать add_path
, чтобы добавить каждый такой путь в baserel->pathlist
. Для формирования узлов ForeignPath
рекомендуется вызывать create_foreignscan_path
. Данная функция может выдавать несколько путей доступа, то есть путей, для которых по заданным pathkeys
можно получить уже отсортированный результат. Каждый путь доступа должен содержать оценки стоимости и может содержать любую частную информацию FDW, необходимую для выбора целевого метода сканирования.
За дополнительными сведениями обратитесь к Разделу 54.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
.
За дополнительными сведениями обратитесь к Разделу 54.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
без этих столбцов.
Заметьте, что для исполнителя Postgres Pro не важно, удовлетворяют ли возвращаемые строки каким-либо ограничениям, определённым для сторонней таблицы — но это важно для планировщика, так что запросы могут оптимизироваться некорректно, если в сторонней таблице будут видны строки, не удовлетворяющие объявленному ограничению. Если ограничение нарушается, тогда как пользователь объявил, что оно должно выполняться, может быть уместно сообщить об ошибке (точно так же, как и при несовпадении типов данных).
void ReScanForeignScan (ForeignScanState *node);
Перезапускает сканирование с начала. Заметьте, что значения параметров, от которых зависит сканирование, могли измениться, так что новое сканирование не обязательно вернёт те же строки.
void EndForeignScan (ForeignScanState *node);
Завершает сканирование и освобождает ресурсы. Обычно при этом не нужно освобождать память, выделенную через palloc, но например, открытые файлы и подключения к удалённым серверам следует закрыть.
54.2.2. Подпрограммы FDW для сканирования сторонних соединений
Если FDW поддерживает соединения на удалённой стороне (вместо того, чтобы считывать данные обеих таблиц и выполнять соединения локально), она должна предоставить эту реализующую подпрограмму:
void GetForeignJoinPaths (PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra);
Формирует возможные пути доступа для соединения двух (и более) сторонних таблиц, принадлежащих одному стороннему серверу. Эта необязательная функция вызывается во время планирования запроса. Как и GetForeignPaths
, эта функция должна построить пути ForeignPath
для переданного joinrel
и вызвать add_path
, чтобы добавить эти пути в набор путей, подходящих для соединения. Но, в отличие от GetForeignPaths
, эта функция не обязательно должна возвращать минимум один путь, так как всегда возможен альтернативный путь с локальным соединением таблиц.
Заметьте, что эта функция будет вызываться неоднократно для одного и того же соединения с разными комбинациями внутреннего и внешнего отношений; минимизировать двойную работу должна сама FDW.
Если для соединения выбирается путь ForeignPath
, он будет представлять весь процесс соединения; пути, сформированные для задействованных таблиц и подчинённых соединений, в нём применяться не будут. Далее этот путь соединения обрабатывается во многом так же, как и путь сканирования одной сторонней таблицы. Одно различие состоит в том, что scanrelid
результирующего плана узла ForeignScan
должно быть равно нулю, так как он не представляет какое-либо одно отношение; вместо этого набор соединяемых отношений представляется в поле fs_relids
узла ForeignScan
. (Это поле заполняется автоматически кодом ядра планировщика, так что FDW делать это не нужно.) Ещё одно отличие в том, что список столбцов для удалённого соединения нельзя получить из системных каталогов и поэтому FDW должна выдать в fdw_scan_tlist
требуемый список узлов TargetEntry
, представляющий набор столбцов, которые будут выдаваться во время выполнения в возвращаемых кортежах.
За дополнительными сведениями обратитесь к Разделу 54.4.
54.2.3. Подпрограммы FDW для планирования обработки после сканирования/соединения
Если FDW поддерживает удалённое выполнение операций после сканирования/соединения, например, удалённое агрегирование, она должна предоставить эту реализующую подпрограмму:
void GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel);
Формирует возможные пути доступа для обработки верхнего отношения. Этот термин планировщика подразумевает любую обработку запросов после сканирования/соединения, в частности, агрегирование, вычисление оконных функций, сортировку и изменение таблиц. Эта необязательная функция вызывается во время планирования запроса. В настоящее время она вызывается, только если все базовые отношения, задействованные в запросе, относятся к одной FDW. Эта функция должна построить пути ForeignPath
для любых действий после сканирования/соединения, которые FDW умеет выполнять удалённо, и вызвать add_path
, чтобы добавить эти пути к указанному верхнему отношению. Как и GetForeignJoinPaths
, эта функция не обязательно должна возвращать какие-либо пути, так как всегда возможны пути с локальной обработкой.
Параметр stage
определяет, какой шаг после сканирования/соединения рассматривается в данный момент. Параметр output_rel
указывает на верхнее отношение, которое должно получить пути, представляющие вычисление этого шага, а input_rel
— на отношение, представляющее входные данные для этого шага. (Заметьте, что пути ForeignPath
, добавляемые в output_rel
, обычно не будут напрямую зависеть от путей input_rel
, так как ожидается, что они будут обрабатываться снаружи. Однако изучить пути, построенные для предыдущего шага обработки, может быть полезно для исключения лишних операций при планировании.)
За дополнительными сведениями обратитесь к Разделу 54.4.
54.2.4. Подпрограммы FDW для изменения данных в сторонних таблицах
Если FDW поддерживает запись в сторонние таблицы, она должна предоставить некоторые или все подпрограммы, реализующие следующие функции, в зависимости от потребностей и возможностей FDW:
void AddForeignUpdateTargets (Query *parsetree, RangeTblEntry *target_rte, Relation target_relation);
Операции UPDATE
и DELETE
выполняются со строками, ранее выбранными функциями сканирования таблицы. FDW может потребоваться дополнительная информация, например, ID строки или значения столбцов первичного ключа, чтобы точно знать, какую именно строку нужно изменить или удалить. Для этого данная функция может добавить дополнительные скрытые или «отбросовые» целевые столбцы в список столбцов, которые должны быть получены из сторонней таблицы во время UPDATE
или DELETE
.
Для этого добавьте в parsetree->targetList
элементы TargetEntry
, содержащие выражения для дополнительных выбираемых значений. У каждой такой записи должен быть признак resjunk
= true
и должно быть отдельное собственное имя resname
, по которому она будет идентифицироваться во время выполнения. Избегайте использования имён вида ctid
, N
wholerow
или wholerow
, так как столбцы с такими именами может генерировать ядро системы. Если дополнительные выражения сложнее, чем просто переменные, их нужно пропустить через функцию N
eval_const_expressions
прежде чем добавлять в целевой список.
Хотя эта функция вызывается во время планирования, передаваемая ей информация несколько отличается от той, что получают другие подпрограммы планирования. В parsetree
передаётся дерево разбора команды UPDATE
или DELETE
, а параметры target_rte
и target_relation
описывают целевую стороннюю таблицу.
Если указатель 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->plans
или другой вложенной структуре узла plan
.
За дополнительными сведениями обратитесь к Разделу 54.4.
Если указатель PlanForeignModify
равен NULL
, дополнительные действия во время планирования не предпринимаются, и в качестве fdw_private
в BeginForeignModify
поступит NULL.
void BeginForeignModify (ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, int eflags);
Начинает выполнение операции изменения данных в сторонней таблице. Эта подпрограмма выполняется при запуске исполнителя. Она должна выполнять любые подготовительные действия, необходимые для того, чтобы собственно произвести изменения в таблице. Впоследствии для каждого кортежа, который будет вставляться, изменяться или удаляться, будет вызываться ExecForeignInsert
, ExecForeignUpdate
или ExecForeignDelete
.
В параметре mtstate
передаётся общее состояние выполняемого плана узла ModifyTable
; через эту структуру доступны глобальные сведения о плане и состояние выполнения. В rinfo
передаётся структура ResultRelInfo
, описывающая целевую стороннюю таблицу. (Если FDW нужно сохранить частное состояние, необходимое для этой операции, она может воспользоваться полем ri_FdwState
структуры ResultRelInfo
.) В fdw_private
передаются частные данные, если они были сформированы процедурой PlanForeignModify
. Параметр subplan_index
определяет целевое отношение в данном узле ModifyTable
, а в eflags
передаются битовые флаги, описывающие режим работы исполнителя для этого узла плана.
Заметьте, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY)
не равно нулю, эта функция не должна выполнять какие-либо внешне проявляющиеся действия; она должна сделать только то, что необходимо для получения состояния узла, подходящего для 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
или для сторонней таблицы определён триггер AFTER ROW
. Триггерам нужны все столбцы, но для предложения RETURNING
FDW может ради оптимизации не возвращать некоторые или все столбцы, в зависимости от его содержания. Так или иначе, какой-либо слот необходимо вернуть, чтобы отметить, что операция успешна, иначе возвращённое число строк будет неверным.
Если указатель на ExecForeignInsert
равен NULL
, вставить данные в стороннюю таблицу не удастся, в ответ будет выдаваться сообщение об ошибке.
TupleTableSlot * ExecForeignUpdate (EState *estate, ResultRelInfo *rinfo, TupleTableSlot *slot, TupleTableSlot *planSlot);
Изменяет один кортеж в сторонней таблице. В estate
передаётся глобальное состояние выполнения запроса, а в rinfo
— структура ResultRelInfo
, описывающая целевую стороннюю таблицу. Параметр slot
содержит новые данные для кортежа; он будет соответствовать определению типа строки сторонней таблицы. Параметр planSlot
содержит кортеж, сформированный вложенным планом узла ModifyTable
; он отличается от slot
тем, что может содержать дополнительные «отбросовые» столбцы. В частности, в этом слоте можно получить любые отбросовые столбцы, запрошенные в AddForeignUpdateTargets
.
Возвращаемым значением будет либо слот, содержащий строку в состоянии после изменения (её содержимое может отличаться от переданного, например, в результате действий триггеров), либо NULL, если никакая строка фактически не была изменена (опять же, обычно в результате действий триггеров). Чтобы вернуть результат, также можно использовать передаваемый на вход slot
.
Данные в возвращаемом слоте используются, только если запрос UPDATE
содержит предложение RETURNING
или для сторонней таблицы определён триггер AFTER ROW
. Триггерам нужны все столбцы, но для предложения RETURNING
FDW может ради оптимизации не возвращать некоторые или все столбцы, в зависимости от его содержания. Так или иначе, какой-либо слот необходимо вернуть, чтобы отметить, что операция успешна, иначе возвращённое число строк будет неверным.
Если указатель на 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
, никакое действие при завершении исполнителя не выполняется.
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
структуры ForeignScan
должно быть установлено соответствующее значение перечисления CmdType
: то есть, CMD_UPDATE
для UPDATE
, CMD_INSERT
для INSERT
и CMD_DELETE
для DELETE
.
За дополнительными сведениями обратитесь к Разделу 54.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
, переданный с узлом). Данные, которые были фактически добавлены, изменены или удалены, нужно сохранить в es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple
в структуре EState
, переданной с узлом. Возвращает 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
, сервер не будет пытаться произвести прямую модификацию.
54.2.5. Подпрограммы FDW для блокировки строк
Если FDW желает поддержать функцию поздней блокировки строк (описанную в Разделе 54.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
никогда не будет вызываться, так что и её задавать не нужно.)
За подробностями обратитесь к Разделу 54.5.
HeapTuple RefetchForeignRow (EState *estate, ExecRowMark *erm, Datum rowid, bool *updated);
Повторно считывает один кортеж из сторонней таблицы после блокировки, если она требуется. В estate
передаётся глобальное состояние выполнения запроса. В erm
передаётся структура ExecRowMark
, описывающая целевую стороннюю таблицу и тип запрашиваемой блокировки (если требуется блокировка). Параметр rowid
идентифицирует считываемый кортеж. Параметр updated
используется как выходной.
Эта функция должна вернуть копию выбранного кортежа (размещённую в памяти palloc) или NULL
, если получить блокировку строки не удаётся. Тип запрашиваемой блокировки строки определяется значением erm->markType
, которое было до этого возвращено функцией GetForeignRowMarkType
. (Вариант ROW_MARK_REFERENCE
означает, что нужно просто повторно выбрать кортеж, не запрашивая никакую блокировку, а ROW_MARK_COPY
никогда не поступает в эту подпрограмму.)
Кроме того, переменной *updated
следует присвоить true
, если была считана изменённая версия кортежа, а не версия, полученная ранее. (Если FDW не знает этого наверняка, рекомендуется всегда возвращать true
.)
Заметьте, что по умолчанию в случае неудачи при попытке получить блокировку строки должна выдаваться ошибка; значение NULL
может возвращаться, только если в erm->waitPolicy
выбран вариант SKIP LOCKED
.
В rowid
передаётся значение ctid
, полученное ранее для строки, которую нужно считать повторно. Хотя значение rowid
передаётся в виде Datum
, в настоящее время это может быть только tid
. Такой интерфейс функции выбран с расчётом на то, чтобы в будущем в качестве идентификаторов строк могли приниматься и другие типы данных.
Если указатель на RefetchForeignRow
равен NULL
, повторно выбрать данные не удастся, в ответ будет выдаваться сообщение об ошибке.
За подробностями обратитесь к Разделу 54.5.
bool RecheckForeignScan (ForeignScanState *node, TupleTableSlot *slot);
Перепроверяет, соответствует ли по-прежнему ранее возвращённый кортеж применимым условиям сканирования и соединения, и возможно выдаёт изменённую версию кортежа. Для обёрток сторонних данных, которые не выносят соединение наружу, обычно удобнее присвоить этому указателю NULL
и задать fdw_recheck_quals
. Однако когда внешние соединения выносятся наружу, недостаточно повторно применить к результирующему кортежу проверки, относящиеся ко всем базовым таблицам, даже если присутствуют все атрибуты, так как невыполнение некоторого условия может приводить и к обнулению некоторых атрибутов, а не только исключению этого кортежа. RecheckForeignScan
может перепроверить условия и возвратить true, если они по-прежнему выполняются, или false в противном случае, но также она может записать в переданный слот кортеж на замену предыдущему.
Чтобы вынести соединение наружу, обёртка сторонних данных обычно конструирует альтернативный план локального соединения, применяемый только для перепроверок; он становится внешним подпланом узла ForeignScan
. Когда требуется перепроверка, может быть выполнен этот подплан и результирующий кортеж сохранён в слоте. Этот план может не быть эффективным, так как ни одна базовая таблица не выдаст больше одной строки; например, он может реализовывать все соединения в виде вложенных циклов. Для поиска подходящего локального пути соединения в существующих путях можно воспользоваться функцией GetExistingLocalJoinPath
. Функция GetExistingLocalJoinPath
ищет непараметризованный путь в списке путей заданного отношения соединения. (Если такой путь не находится, она возвращает NULL, и в этом случае обёртка сторонних данных может построить локальный путь сама или решить не создавать пути доступа для этого соединения.)
54.2.6. Подпрограммы 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
не выводится.
54.2.7. Подпрограммы 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.)
54.2.8. Подпрограммы 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
.
54.2.9. Подпрограммы 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, должны реализовывать этот метод.