Las funciones de retrollamada de FDW GetForeignRelSize,
GetForeignPaths, GetForeignPlan,
PlanForeignModify, GetForeignJoinPaths,
GetForeignUpperPaths y PlanDirectModify deben encajar en el
funcionamiento del planificador de PostgreSQL.
A continuación se presentan algunas notas sobre lo que deben hacer.
La información en root y baserel se puede utilizar para reducir la
cantidad de información que se tiene que recuperar de la tabla externa (y por lo tanto reducir el costo).
baserel->baserestrictinfo es particularmente interesante, ya que contiene las
condiciones de restricción (cláusulas WHERE) que deben utilizarse para filtrar las filas
a recuperar. (El propio FDW no está obligado a imponer estas condiciones, ya que el ejecutor principal las
puede comprobar en su lugar).
baserel->reltarget->exprs se puede utilizar para determinar qué columnas deben
recuperarse; pero ten en cuenta que solo enumera las columnas que deben ser emitidas por el nodo de plan
ForeignScan, no las columnas que se utilizan en la evaluación de condiciones de
restricción pero que no son de salida de la consulta.
Hay varios campos privados disponibles para que las funciones de planificación de FDW guarden información. Por lo general, cualquier cosa que guardes en los campos privados de FDW debe asignarse con palloc, de modo que se libere al final de la planificación.
baserel->fdw_private es un puntero void que está disponible para que las
funciones de planificación de FDW guarden información relevante para la tabla externa en particular. El
planificador principal no lo toca excepto para inicializarlo a NULL cuando se crea el nodo
RelOptInfo. Es útil para pasar información de
GetForeignRelSize a GetForeignPaths y/o de
GetForeignPaths a GetForeignPlan, evitando así recalcularla.
GetForeignPaths puede identificar el significado de diferentes rutas de acceso
guardando información privada en el campo fdw_private de los nodos
ForeignPath.
fdw_private se declara como un puntero List, pero en realidad
podría contener cualquier cosa ya que el planificador principal no lo toca. Sin embargo, la mejor práctica
es usar una representación que sea descriptiva mediante nodeToString, para su uso con
el soporte de depuración disponible en el backend.
GetForeignPlan puede examinar el campo fdw_private del nodo
ForeignPath seleccionado, y puede generar listas fdw_exprs
y fdw_private para colocarlas en el nodo de plan
ForeignScan, donde estarán disponibles en el momento de la ejecución. Ambas listas
deben representarse en una forma que copyObject sepa cómo copiar. La lista
fdw_private no tiene otras restricciones y no es interpretada por el backend
principal de ninguna manera. Se espera que la lista fdw_exprs, si no es NIL,
contenga árboles de expresiones que se pretenden ejecutar en tiempo de ejecución. Estos árboles se
someterán a un procesamiento posterior por parte del planificador para hacerlos completamente ejecutables.
En GetForeignPlan, generalmente la lista de objetivos pasada se puede copiar en el
nodo de plan tal cual. La lista scan_clauses pasada contiene las mismas cláusulas que
baserel->baserestrictinfo, pero puede ser reordenada para una mejor eficiencia de
ejecución. En casos simples, el FDW puede simplemente extraer los nodos RestrictInfo
de la lista scan_clauses (usando extract_actual_clauses) y colocar
todas las cláusulas en la lista qual del nodo de plan, lo que significa que todas las cláusulas serán
comprobadas por el ejecutor en tiempo de ejecución. Los FDW más complejos pueden comprobar algunas de las
cláusulas internamente, en cuyo caso esas cláusulas se pueden eliminar de la lista qual del nodo de plan
para que el ejecutor no pierda tiempo volviendo a comprobarlas.
Como ejemplo, el FDW podría identificar algunas cláusulas de restricción de la forma
variable_externa = sub_expresion,
que determine que se pueden ejecutar en el servidor remoto dado el valor evaluado localmente de la
sub_expresion. La identificación real de dicha cláusula debe ocurrir durante
GetForeignPaths, ya que afectaría la estimación de costos de la ruta. El campo
fdw_private de la ruta probablemente incluiría un puntero al nodo
RestrictInfo de la cláusula identificada. Luego,
GetForeignPlan eliminaría esa cláusula de scan_clauses, pero
añadiría la sub_expresion a fdw_exprs para asegurar
que se transforme en forma ejecutable. Probablemente también colocaría información de control en el campo
fdw_private del nodo de plan para indicar a las funciones de ejecución qué hacer
en tiempo de ejecución. La consulta transmitida al servidor remoto involucraría algo como
WHERE , obteniéndose el valor del
parámetro en tiempo de ejecución a partir de la evaluación del árbol de expresiones
variable_externa = $1fdw_exprs.
Cualquier cláusula eliminada de la lista qual del nodo de plan debe, en su lugar, añadirse a
fdw_recheck_quals o volver a comprobarse mediante RecheckForeignScan
para garantizar el comportamiento correcto en el nivel de aislamiento READ COMMITTED.
Cuando ocurre una actualización concurrente para alguna otra tabla involucrada en la consulta, el ejecutor
puede necesitar verificar que todas las condiciones originales todavía se cumplan para la tupla,
posiblemente contra un conjunto diferente de valores de parámetros. Usar
fdw_recheck_quals suele ser más fácil que implementar comprobaciones dentro de
RecheckForeignScan, pero este método será insuficiente cuando las uniones externas se
hayan empujado hacia abajo, ya que las tuplas de unión en ese caso podrían hacer que algunos campos pasen a
ser NULL sin rechazar la tupla por completo.
Otro campo de ForeignScan que pueden completar los FDW es
fdw_scan_tlist, que describe las tuplas devueltas por el FDW para este nodo de
plan. Para escaneos simples de tablas externas, este campo se puede establecer en NIL,
lo que implica que las tuplas devueltas tienen el tipo de fila declarado para la tabla externa. Un valor no
NIL debe ser una lista de objetivos (lista de TargetEntrys) que
contenga variables Vars y/o expresiones que representen las columnas devueltas. Esto podría usarse, por
ejemplo, para mostrar que el FDW ha omitido algunas columnas de las que se dio cuenta de que no serán
necesarias para la consulta. Además, si el FDW puede calcular las expresiones utilizadas por la consulta
de forma más barata de lo que se puede hacer localmente, podría añadir esas expresiones a
fdw_scan_tlist. Ten en cuenta que los planes de unión (creados a partir de rutas
realizadas por GetForeignJoinPaths) siempre deben suministrar
fdw_scan_tlist para describir el conjunto de columnas que devolverán.
El FDW siempre debe construir al menos una ruta que dependa únicamente de las cláusulas de restricción de
la tabla. En las consultas de unión, también podría optar por construir ruta(s) que dependan de las
cláusulas de unión, por ejemplo, variable_externa =
variable_local. Dichas cláusulas no se encontrarán en
baserel->baserestrictinfo sino que deben buscarse en las listas de unión de la
relación. Una ruta que utiliza dicha cláusula se denomina “ruta parametrizada”. Debe
identificar las otras relaciones utilizadas en la(s) cláusula(s) de unión seleccionada(s) con un valor
adecuado de param_info; usa get_baserel_parampathinfo para calcular
ese valor. En GetForeignPlan, la parte variable_local de la
cláusula de unión se añadiría a fdw_exprs, y luego, en tiempo de ejecución, el
caso funciona igual que para una cláusula de restricción ordinaria.
Si un FDW admite uniones remotas, GetForeignJoinPaths debe producir
ForeignPaths para posibles uniones remotas de manera muy similar a como funciona
GetForeignPaths para las tablas base. La información sobre la unión prevista se puede
pasar a GetForeignPlan de la misma manera descrita anteriormente. Sin embargo,
baserestrictinfo no es relevante para las relaciones de unión; en su lugar, las
cláusulas de unión pertinentes para una unión en particular se pasan a
GetForeignJoinPaths como un parámetro separado
(extra->restrictlist).
An FDW podría, además, admitir la ejecución directa de algunas acciones del plan que están por encima del
nivel de escaneos y uniones, como la agrupación o la agregación. Para ofrecer tales opciones, el FDW debe
generar rutas e insertarlas en la relación superior adecuada. Por ejemplo, una ruta
que representa una agregación remota debe insertarse en la relación UPPERREL_GROUP_AGG,
utilizando add_path. Esta ruta se comparará en función del costo con la agregación
local realizada al leer una ruta de escaneo simple para la relación externa (ten en cuenta que dicha ruta
también debe suministrarse, de lo contrario habrá un error en el tiempo del plan). Si la ruta de agregación
remota gana, lo cual sucedería habitualmente, se convertirá en un plan de la manera habitual, llamando a
GetForeignPlan. El lugar recomendado para generar tales rutas es en la función de
retrollamada GetForeignUpperPaths, que se llama para cada relación superior (es decir,
cada paso de procesamiento posterior al escaneo/unión), si todas las relaciones base de la consulta
provienen del mismo FDW.
PlanForeignModify y las demás retrollamadas descritas en la
Section 58.2.4 se diseñaron en torno al supuesto de que la relación externa se
escaneará de la manera habitual y luego las actualizaciones de filas individuales serán impulsadas por un
nodo de plan ModifyTable local. Este enfoque es necesario para el caso general en el que
una actualización requiere leer tanto tablas locales como externas. Sin embargo, si la operación se pudiera
ejecutar por completo en el servidor remoto, el FDW podría generar una ruta que la represente e insertarla
en la relación superior UPPERREL_FINAL, donde competiría contra el enfoque de
ModifyTable. Este enfoque también podría usarse para implementar
SELECT FOR UPDATE remotos, en lugar de usar las retrollamadas de bloqueo de filas
descritas en la Section 58.2.6. Ten en cuenta que una ruta insertada en
UPPERREL_FINAL es responsable de implementar todo el comportamiento
de la consulta.
Al planificar un UPDATE o un DELETE,
PlanForeignModify y PlanDirectModify pueden buscar la estructura
RelOptInfo para la tabla externa y hacer uso de los datos de
baserel->fdw_private creados previamente por las funciones de planificación de
escaneo. Sin embargo, en INSERT la tabla de destino no se escanea, por lo que no hay un
RelOptInfo para ella. La lista List devuelta por
PlanForeignModify tiene las mismas restricciones que la lista
fdw_private de un nodo de plan ForeignScan, es decir,
debe contener solo estructuras que copyObject sepa cómo copiar.
INSERT con una cláusula ON CONFLICT no admite especificar el objetivo
del conflicto, ya que las restricciones únicas o las restricciones de exclusión en tablas remotas no se
conocen localmente. Esto a su vez implica que no se admite ON CONFLICT DO UPDATE, ya que
la especificación es obligatoria allí.