Планирование запросов с оберткой для сторонних данных
Функции обратного вызова FDW GetForeignRelSize
, GetForeignPaths
, GetForeignPlan
, PlanForeignModify
, GetForeignJoinPaths
, GetForeignUpperPaths
и PlanDirectModify
должны вписываться в работу планировщика PostgreSQL. Дальше описаны несколько замечаний о том, что они должны делать.
Информация в root
и baserel
может быть использована для уменьшения количества информации, которую нужно извлекать из внешней таблицы (и, следовательно, для уменьшения стоимости). baserel->baserestrictinfo
особенно интересна, поскольку она содержит квоты ограничений (WHERE
-клаузы), которые должны использоваться для фильтрации извлекаемых строк. (Сам FDW не обязан обеспечивать выполнение этих условий, так как вместо него их может проверить исполнитель ядра). basel->reltarget->exprs
может быть использован для определения столбцов, которые необходимо извлечь; но обратите внимание, что в нем перечислены только столбцы, которые должны быть выданы узлом плана ForeignScan
, а не столбцы, которые используются в оценке qual
, но не выводятся запросом.
Функ ции планирования FDW могут хранить информацию в различных частных полях. Как правило, все, что хранится в частных полях FDW, должно быть помещено в palloc, чтобы его можно было вернуть по окончании планирования.
baserel->fdw_private
- это указатель void
, который доступен для функций планирования FDW для хранения информации, относящейся к конкретной внешней таблице. Планировщик ядра не трогает его, за исключением инициализации в NULL при создании узла RelOptInfo
. Он полезен для передачи информации от GetForeignRelSize
к GetForeignPaths
и/или GetForeignPaths
к GetForeignPlan
, что позволяет избежать пересчета.
GetForeignPaths
может определить значение различных путей доступа, сохраняя приватную информацию в поле fdw_private
узлов ForeignPath
. fdw_private
объявлен как указатель List
, но на самом деле может содержать что угодно, так как планировщик ядра его не трогает. Тем не менее, лучше всего использовать представление, которое можно сбросить с помощью nodeToString
, чтобы использовать отладочный порт, доступный в бэкенде.
GetForeignPlan
может исследовать поле fdw_private
выбранного узла ForeignPath
и сгенерировать списки fdw_exprs
и fdw_private
для размещения в узле плана ForeignScan
, где они будут доступны во время выполнения. Оба этих списка должны быть представлены в форме, которую copyObject
умеет копировать. Список fdw_private
не имеет других ограничений и никак не интерпретируется бэкендом ядра. Список fdw_exprs
, если он не NIL
, должен содержать деревья выражений, предназначенные для выполнения во время исполнения. Эти деревья будут подвергнуты постобработке планировщиком, чтобы сделать их полностью исполняемыми.
В GetForeignPlan
, как правило, переданный список целей может быть скопирован в узел плана как есть. Переданный список scan_clauses
содержит те же пункты, что и baserel->baserestrictinfo
, но может быть переупорядочен для повышения эффективности выполнения. В простых случаях FDW может просто удалить узлы RestrictInfo
из списка scan_clauses
(используя extract_actual_clauses
) и поместить все клаузулы в список qual
узла плана, что означает, что все клаузулы будут проверены исполнителем во время выполнения. Более сложные FDW могут быть способны проверять некоторые клаузулы внутри, в этом случае эти клаузулы могут быть удалены из списка qual list
узла плана, чтобы исполнитель не тратил время на их повторную проверку.
Например, FDW может определить некоторые ограничительные условия вида foreign_variable = sub_expression
, которые, по его мнению, могут быть выполнены на удаленном сервере с учетом локально оцененного значения sub_expression
. Фактическая идентификация такой клаузы должна происходить во время GetForeignPaths
, поскольку это повлияет на оценку стоимости пути. Поле fdw_private
пути, вероятно, будет включать указатель на узел RestrictInfo
идентифицированной клаузы. Затем GetForeignPlan
удалит это положение из scan_clauses
, но добавит sub_expression
в fdw_exprs
, чтобы гарантировать, что оно будет переведено в исполняемую форму. Вероятно, он также поместит управляющую информацию в поле fdw_private
узла плана, чтобы указать функциям выполнения, что делать во время выполнения. Запрос, передаваемый на удаленный сервер, будет содержать что-то вроде WHERE foreign_variable = $1
, причем значение параметра будет получено во время выполнения из оценки дерева выражений fdw_exprs
.
Любые клаузулы, удаленные из списка qual узла плана, должны быть добавлены в fdw_recheck_quals или перепроверены RecheckForeignScan, чтобы обеспечить корректное поведение на уровне изоляции READ COM- MITTED. Когда происходит одновременное обновление какой-либо другой таблицы, участвующей в запросе, исполнителю может потребоваться проверить, что все исходные запросы все еще выполняются для кортежа, возможно, с другим набором значений параметров. Использование fdw_recheck_quals обычно проще, чем реализация проверок внутри RecheckForeignScan, но этот метод будет недостаточен, если внешние соединения были сдвинуты вниз, поскольку в кортежах соединений в этом случае некоторые поля могут стать NULL без полного отклонения кортежа.
Еще одно поле ForeignScan
, которое может быть заполнено FDW, - fdw_scan_tlist
, описывающее кортежи, возвращаемые FDW для данного узла плана. Для простого сканирования внешней таблицы это значение может быть равно NIL
, что означает, что возвращаемые кортежи имеют тип строки, объявленный для внешней таблицы. Значение, не являющееся NIL
, должно представлять собой целевой список (список TargetEntrys
), содержащий переменные и/или выражения, представляющие возвращаемые столбцы. Это может быть использовано, например, для того, чтобы показать, что FDW опустил некоторые столбцы, которые, по его мнению, не понадобятся для запроса. Кроме того, если FDW может вычислить выражения, используемые в запросе, дешевле, чем это можно сделать локально, он может добавить эти выражения в fdw_scan_tlist
. Обратите внимание, что планы присоединения (созданные из путей, созданных GetForeignJoinPaths
) всегда должны предоставлять fdw_scan_tlist
для описания набора столбцов, которые они будут возвращать.
FDW всегда должен построить хотя бы один путь, который зависит только от ограничительных условий таблицы. В запросах на соединение он может также выбрать построение пути (путей), зависящего от условий соединения, например foreign_variable = local_variable
. Такие клаузулы не будут найдены в baserel->baserestrictinfo
, а должны быть найдены в списках присоединения отношения. Путь, использующий такую оговорку, называется «параметризованным путем». Он должен идентифицировать другие отношения, используемые в выбранном(ых) предложении(ях) присоединения, с помощью подходящего значения param_info
; используйте get_baserel_parampathinfo
для вычисления этого значения. В GetForeignPlan
часть local_variable
предложения присоединения будет добавлена к fdw_exprs
, а затем во время выполнения это будет работать так же, как и для обычного предложения ограничения.
Если FDW поддерживает удаленные соединения, GetForeignJoinPaths
должен создавать ForeignPaths
для потенциальных удаленных соединений примерно так же, как GetForeignPaths
работает для базовых таблиц.
Информация о предполагаемом соединении может быть передана в GetForeignPlan
теми же способами, которые описаны выше. Однако baserestrictinfo
не имеет значения для отношений присоединения; вместо этого соответствующие условия присоединения для конкретного присоединения передаются в GetForeignJoinPaths
как отдельный параметр (extra->restrictlist
).
FDW может дополнительно поддерживать прямое выполнение некоторых действий плана, которые находятся выше уровня сканирования и объединения, таких как группировка или агрегирование. Чтобы предложить такие возможности, FDW должен генерировать пути и вставлять их в соответствующее верхнее отношение. Например, путь, представляющий удаленную агрегацию, должен быть вставлен в отношение UPPERREL_GROUP_AGG
с помощью add_path
. Этот путь будет сравниваться по стоимости с локальной агрегацией, выполняемой путем чтения простого пути сканирования для внешнего отношения (обратите внимание, что такой путь также должен быть предоставлен, иначе произойдет ошибка во время планирования). Если путь удаленной агрегации побеждает, что обычно и происходит, он будет преобразован в план обычным способом, вызовом GetForeignPlan
. Рекомендуемое место для генерации таких путей - функция обратного вызова GetForeignUpperPaths
, которая вызывается для каждого верхнего отношения (т.е. для каждого шага обработки после сканирования/соединения), если все базовые отношения запроса происходят из одного и того же FDW.
PlanForeignModify
и другие обратные вызовы, описанные в разделе «Подпрограммы FDW для обновления сторонних таблиц», основаны на предположении, что внешнее отношение будет сканироваться обычным способом, а затем обновления отдельных строк будут выполняться локальным узлом плана ModifyTable
. Такой подход необходим для общего случая, когда обновление требует чтения как локальных, так и внешних таблиц. Однако если операция может быть выполнена полностью внешним сервером, FDW может сгенерировать путь, представляющий это, и вставить его в верхнее отношение UPPERREL_FINAL
, где он будет конкурировать с подходом ModifyTable
. Этот подход также можно использовать для реализации удаленного SELECT FOR UPDATE
, вместо того чтобы использовать обратные вызовы блокировки строк, описанные в разделе «Подпрограммы FDW для блокировки строк». Следует помнить, что путь, вставленный в UPPERREL_FINAL
, отвечает за реализацию всего поведения запроса.
При планировании UPDATE
или DELETE
, PlanForeignModify
и PlanDirectModify
могут искать структуру RelOptInfo
для внешней таблицы и использовать данные baserel->fdw_private
, ранее созданные функциями планирования сканирования. Однако при INSERT
целевая таблица не сканируется, поэтому для нее не существует RelOptInfo
. Список, возвращаемый PlanForeignModify
, имеет те же ограничения, что и список fdw_private
узла плана ForeignScan
, то есть он должен содержать только структуры, которые copyObject
знает, как копировать.
INSERT
с предложением ON CONFLICT
не поддерживает указание цели конфликта, поскольку уникальные ограничения или ограничения исключения для удаленных таблиц не известны локально. Это, в свою очередь, подразумевает, что ON CONFLICT DO UPDATE
не поддерживается, поскольку в нем это указание является обязательным.