PostgreSQL 外部数据包装器回调例程
- 56.2.1. 用于扫描外部表的FDW例程
- 56.2.2. 用于扫描外部连接的 FDW 例程
- 56.2.3. 用于规划扫描/连接后处理的 FDW 例程
- 56.2.4. 更新外部表的FDW例程
- 56.2.5. 用于行锁定的 FDW 例程
- 56.2.6.
EXPLAIN
的FDW例程 - 56.2.7.
ANALYZE
的FDW例程 - 56.2.8.
IMPORT FOREIGN SCHEMA
的 FDW 例程 - 56.2.9. 并行执行的 FDW 例程
- 56.2.10. 用于路径重新参数化的FDW例程
FDW处理器函数返回一个palloc过的FdwRoutine
结构,它包含下文描述的回调函数的指针。扫描相关的函数是必需的,剩下的是可选的。
FdwRoutine
结构类型被声明在src/include/foreign/fdwapi.h
中,可以查看它来获得额外的信息。
56.2.1. 用于扫描外部表的FDW例程
void
GetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
获取一个外部表的关系尺寸估计。在对一个扫描外部表的查询进行规划的开头将调用该函数。root
是规划器的关于该查询的全局信息;baserel
是规划器的关于该表的信息;foreigntableid
是外部表在pg_class
中的 OID (foreigntableid
可以从规划器的数据结构中获得,但是为了减少工作量,这里直接显式地将它传递给函数)。
这个函数应该更新baserel->rows
为表扫描根据限制条件完成了过滤后将返回的预期行数。 baserel->rows
的初始值只是一个常数的默认估计值,应该尽可能把它替换掉。 如果该函数能够计算出一个平均结果行宽度的更好的估计值,该函数也可能选择更新baserel->width
。 (初始值基于列数据类型和最后一个
ANALYZE
测量的列平均宽度值。) 此外,如果该函数能够更好地计算出外部表的总行数,则该函数可以更新baserel->tuples
。 (初始值来自pg_class
。reltuples
表示最后一次ANALYZE
看到的总行数。)
更多信息请见第 56.4 节。
void
GetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
为一个外部表上的扫描创建可能的访问路径。这个函数在查询规划过程中被调用。参数和GetForeignRelSize
相同,后者已经被调用过了。
这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath
节点),并且必须调用add_path
把每一个这样的路径加入到baserel->pathlist
中。我们推荐使用create_foreignscan_path
来建立ForeignPath
节点。该函数可以生成多个访问路径,例如一个具有合法
pathkeys
的路径表示一个预排序好的结果。每一个反问路径必须包含代价估计,并且能包含任何FDW的私有信息,这种信息被用来标识想要使用的指定扫描方法。
更多信息请见第 56.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
产生)、被计划节点发出的目标列表以及计划节点强制的限制子句以及被RecheckForeignScan
执行的复查所使用的ForeignScan
的外子计划(如果该路径是用于一个连接而非基本关系,则foreigntableid
是InvalidOid
)。
这个函数必须创建并返回一个ForeignScan
计划节点,我们对剑使用make_foreignscan
来建立ForeignScan
节点。
更多信息见第 56.4 节。
void
BeginForeignScan(ForeignScanState *node,
int eflags);
开始执行一个外部扫描。这个函数在执行器启动阶段被调用。它应该执行任何在扫描能够开始之前需要完成的初始化工作,但是并不开始执行真正的扫描(会在第一次调用IterateForeignScan
时完成)。ForeignScanState
节点已经被创建好了,但是它的fdw_state
域仍然为 NULL。关于要被扫描的表的信息可以通过
ForeignScanState
节点访问(特殊地,从底层的ForeignScan
计划节点,它包含任何由GetForeignPlan
提供的FDW私有信息)。eflags
包含描述执行器对该计划节点操作模式的标志位。
注意当(eflags & EXEC_FLAG_EXPLAIN_ONLY)
为真时,这个函数不应该执行任何外部可见的动作;它应当只做最少的事情来创建对ExplainForeignScan
和EndForeignScan
有效的节点状态。
TupleTableSlot *
IterateForeignScan(ForeignScanState *node);
从外部源获得一行,将它放在一个元组表槽中返回(节点的ScanTupleSlot
应当被用于此目的)。如果没有更多的行可用则返回 NULL。元组表槽设施允许一个物理的或者虚拟的元组被返回;在大部分情况下出于性能的考虑会倾向于选择后者。注意这是在一个短期存在的内存上下文中被调用的,该内存上下文会在调用之间被重置。如果你需要长期存在的存储,请在BeginForeignScan
中创建内存上下文,或者使用节点的
EState
中的es_query_cxt
。
如果提供了fdw_scan_tlist
目标列表,被返回的行必须匹配它,如果没有提供则它们必须匹配被扫描的外部表的行类型。如果选择优化掉不需要的列,你应该在那些列的位置上插入控制或者生成一个忽略了那些列的fdw_scan_tlist
列表。
注意PostgreSQL的执行器并不在乎被返回的行是否违背了定义在该外部表上的任何约束 — 但是规划器会在乎这一点,并且如果在外部表中有可见行不满足一个约束,规划器可能会错误地优化查询。如果当用户已经声明一个约束应该为真时它却被违背,最合适的处理可能是产生一个错误(就像在数据类型失配的情况下所作的那样)。
void
ReScanForeignScan(ForeignScanState *node);
从头开始重启一个扫描。注意扫描所依赖的任何参数可能已经改变了值,因此新扫描不一定会返回完全相同的行。
void
EndForeignScan(ForeignScanState *node);
结束扫描并释放资源。通常释放palloc过的内存并不重要,但是打开的文件和到远程服务器的连接等应该被清理。
56.2.2. 用于扫描外部连接的 FDW 例程
如果一个 FDW 支持远程执行外部连接(而不是先把两个表的数据取到本地然后做本地连接),它应该提供这个回调函数:
void
GetForeignJoinPaths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra);
它为两个(或更多)同属于一台外部服务器的外部表的连接创建可能的访问路径。这个可选的函数会在查询规划过程中被调用。 和GetForeignPaths
一样,这个函数应该为提供的joinrel
生成ForeignPath
路径(用 create_foreign_join_path
构建它们),并且调用
add_path
把这些路径加入到该连接应该考虑的路径集合中。但是和GetForeignPaths
不一样的是,不需要这个函数产生最少一个路径,因为涉及本地连接的路径总是可用的。
注意为相同的连接关系将会重复地调用这个函数用来生成内外关系的不同组合。FDW 需要负责最小化其中重复的工作。
如果一个ForeignPath
路径被选中用于该连接,它将在整个连接处理中存在,为其中的成分表和子连接产生的路径将不会被使用。后续对该连接路径的处理大部分和扫描单个外部表的路径一样。一点不同是ForeignScan
计划节点的scanrelid
应该被设置为零,因为它表示的不是单个关系,而是用ForeignScan
节点的
fs_relids
域来表示被连接的关系集合(后一个域会被核心规划器代码自动设置,不需要由 FDW 填充)。另一点不同是,由于一个远程连接的列列表无法在系统目录中找到,FDW 必须用一个合适的TargetEntry
节点列表来填充fdw_scan_tlist
,表示运行时它返回的元组中提供的列的集合。
更多信息请见第 56.4 节。
56.2.3. 用于规划扫描/连接后处理的 FDW 例程
如果一个 FDW 支持执行远程的扫描/连接后处理,例如远程聚集,那么它应该提供这个回调函数:
void
GetForeignUpperPaths(PlannerInfo *root,
UpperRelationKind stage,
RelOptInfo *input_rel,
RelOptInfo *output_rel,
void *extra);
为上层关系处理创建可能的访问路径,这是规划器针对所有扫描/连接后查询处理的术语,例如聚集、窗口函数、排序和表更新。在查询规划期间会调用这个可选的函数。当前,只有当该查询中涉及的所有基本关系都属于同一个 FDW 时才会调用这个函数。 这个函数应该为 FDW 知道如何远程执行的任何扫描/连接后处理生成ForeignPath
路径(用 create_foreign_upper_path
构建它们),并且调用
add_path
把这些路径加入到上层关系中。就GetForeignJoinPaths
来说,并不要求这个函数在创建任何路径时都能成功,因为路径总是有可能涉及到本地处理。
stage
参数表示当前正在考虑的是哪一个扫描/连接后处理步骤。output_rel
是接收表示这一个步骤的路径的上层关系,而input_rel
是表示这个步骤输入的关系。 extra
参数提供额外的细节,当前只会为UPPERREL_PARTIAL_GROUP_AGG
或者
UPPERREL_GROUP_AGG
设置它,这种情况下它会指向一个GroupPathExtraData
结构;或者对于 UPPERREL_FINAL
, 在某种情况下它指向一个FinalPathExtraData
结构。(注意被加入到output_rel
中的
ForeignPath
路径通常对input_rel
的路径没有直接的依赖,因为它们的处理被认为是在外部处理的。不过,检查为前一个处理步骤生成的路径有助于避免冗余的规划工作)。
更多信息请见第 56.4 节。
56.2.4. 更新外部表的FDW例程
如果一个FDW支持可写的外部表,根据FDW的需要和功能它应该提供某些或者全部下列回调函数:
void
AddForeignUpdateTargets(Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
UPDATE
和DELETE
操作是在之前由表扫描函数取出的行上被执行的。FDW可能需要额外的信息(例如一个行ID或主键列的值)来保证它能够找到要更新或删除的准确行。要支持这些要求,这个函数可以项列列表中增加额外的隐藏或“junk”的目标列,它们在一个UPDATE
或
DELETE
期间会被从外部表中获取。
要做到这一点,向parsetree->targetList
中增加TargetEntry
项,它们包含要被获取的额外值的表达式。每一个这样的项必须被标记为resjunk
= true
,并且必须有一个可区分的resname
用于在执行期间标识它。请避免使用匹配
ctid
、N
wholerow
或wholerow
的名字,因为核心系统可能会生成使用这些名字的junk列。如果额外的表达式比简单的Var更加复杂,在把它们加入到目标列表之前必须把它们用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
节点的子结构中,请使用它。
更多信息见第 56.4 节。
如果PlanForeignModify
指针被设置为NULL
,则不会有额外的计划时动作被执行,并且传递给BeginForeignModify
的fdw_private
列表也将为 NIL。
void
BeginForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
int eflags);
开始执行一个外部表修改操作。这个例程在执行器启动期间被调用。它应该执行任何先于实际表修改的初始化工作。随后,ExecForeignInsert
、ExecForeignUpdate
或ExecForeignDelete
将被为每一个将被插入、更新或删除的元组调用。
mtstate
是要被执行的ModifyTable
计划节点的状态信息;通过这个结构可以得到关于规划和执行阶段的全局数据。rinfo
是描述目标外部表的ResultRelInfo
结构(ResultRelInfo
的
ri_FdwState
域用于FDW来存储它在此操作中需要的任何私有状态)。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
不同,它可能包含额外的“junk”列(INSERT
情况通常不关心planSlot
,但是为了完整性还是在这里提供它)。
返回值可以是一个包含实际被插入的数据的槽(这可能会和所提供的数据不同,例如一个触发器动作的结果),或者为 NULL 表示实际没有插入行(还是触发器的结果)。被传入的slot
可以被重用于这个目的。
在返回槽中的数据只有在INSERT
语句具有一个RETURNING
子句或包括一个视图WITH CHECK OPTION
; 或者如果外部表具有一个AFTER ROW
触发器时才被使用。 触发器要求所有的列,但是 FDW 应该选择优化成根据RETURNING
子句的内容或
WITH CHECK OPTION
约束返回某些或全部列。 不管怎样,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。
如果ExecForeignInsert
指针被设置为NULL
,尝试向外部表插入将会失败并报告一个错误消息。
请注意,在外表上将路由元组插入外表分区或执行 COPY FROM
时也会调用此函数,在这种情况下,它的调用方式与INSERT
中的情况有所不同。 请参阅下面所述的允许 FDW 支持的回调函数。
TupleTableSlot *
ExecForeignUpdate(EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
更新外部表中的一个元组。estate
是查询的全局执行状态。rinfo
是描述目标外部表的ResultRelInfo
结构。slot
包含元组的新数据;它将匹配外部表的行类型定义。planSlot
包含由
ModifyTable
计划节点的子计划生成的元组;它与slot
不同,它可能包含额外的“junk”列(INSERT
情况通常不关心planSlot
,但是为了完整性还是在这里提供它)。特殊地,任何AddForeignUpdateTargets
所要求的junk列在这个槽中都是有效的。
返回值可以是一个包含实际被更新的数据的槽(这可能会和所提供的数据不同,例如一个触发器动作的结果),或者为 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
触发器时才被使用。触发器要求所有的列,但是 FDW 应该选择优化成根据RETURNING
子句的内容返回某些或全部列。不管怎样,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。
如果ExecForeignDelete
指针被设置为NULL
,尝试从外部表中删除将会失败并报告一个错误消息。
void
EndForeignModify(EState *estate,
ResultRelInfo *rinfo);
结束表更新并释放资源。通常释放palloc的内存并不重要,但是打开的文件和到远程服务器的连接等应当被清除。
如果EndForeignModify
指针被设置为NULL
,在执行器关闭期间不会采取任何动作。
被INSERT
或者COPY FROM
插入到分区表中的元组会被路由到分区。如果一个FDW支持可路由的外部表分区,它还应该提供下面的回调函数。当在外部表上执行COPY FROM
时,也会调用这些函数。
void
BeginForeignInsert(ModifyTableState *mtstate,
ResultRelInfo *rinfo);
开始在外部表上执行插入操作。当外部表被选中作为元组路由的分区以及COPY FROM
命令中指定的目标时,在第一个元组被插入到该外部表之前会调用这个例程。它应该执行实际插入之前所需的任何初始化工作。随后,为每一个被插入到该外部表的元组都将调用ExecForeignInsert
。
mtstate
是正在被执行的ModifyTable
计划节点的总体状态,通过这个结构可以得到有关计划和执行的全局数据。rinfo
是描述目标外部表的ResultRelInfo
结构(对于FDW,ResultRelInfo
的
ri_FdwState
字段用来存放这个操作所需要的私有状态)。
当这个例程被一个COPY FROM
命令调用时,不会提供mtstate
中与计划相关的全局数据,并且后续为每个插入元组调用的ExecForeignInsert
的planSlot
参数为NULL
,不管该外部表是为元组路由选中的分区还是命令中指定的目标。
如果BeginForeignInsert
指针被设置为NULL
,则不会采取初始化动作。
请注意如果 FDW 不支持在外表上的可路由外表分区和/或执行 COPY FROM
,这个函数或 ExecForeignInsert
后续的调用必须根据需要发出错误。
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
计划节点(它在远程服务器上执行直接修改)重写目标子计划。ForeignScan
的operation
域必须被合适地设置为CmdType
枚举值,即CMD_UPDATE
表示
UPDATE
、CMD_INSERT
表示INSERT
而CMD_DELETE
表示DELETE
。
更多信息请见第 56.4 节。
如果PlanDirectModify
指针被设置为NULL
,不会尝试在远程服务器上执行直接修改。
void
BeginDirectModify(ForeignScanState *node,
int eflags);
准备在远程服务器上执行一次直接修改。这个函数会在执行器启动时被调用。它应该执行直接修改所需的任何初始化工作(应该在第一次IterateDirectModify
调用之前完成)。ForeignScanState
节点已经被创建,但是它的fdw_state
域仍然为 NULL。有关要被修改的表的信息可以通过
ForeignScanState
节点(具体地,从底层的ForeignScan
计划节点,它包含了PlanDirectModify
提供的 FDW-私有信息)访问。eflags
包含描述执行器对于这个计划节点操作模式的标志位。
注意当(eflags & EXEC_FLAG_EXPLAIN_ONLY)
为真时,这个函数不应该执行任何外部可见的动作。它应当只做最少的工作让该节点状态对ExplainDirectModify
和EndDirectModify
有效。
如果BeginDirectModify
指针被设置为NULL
,不会尝试在远程服务器上执行直接修改。
TupleTableSlot *
IterateDirectModify(ForeignScanState *node);
当INSERT
、UPDATE
或者DELETE
查询没有RETURNING
子句时,完成远程服务器上的直接修改后返回 NULL。当查询有该子句时,取出一个包含RETURNING
计算所需数据的结果,用一个元组表槽返回它(节点的
ScanTupleSlot
应被用于这一目的)。实际被插入、更新或者删除的数据必须被存储在该节点的EState
的es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple
中。如果没有更多行可用,则返回 NULL。注意这个函数会在一个短期生存的内存上下文中被调用,该上下文会在两次调用之间被重置。如果需要一个长期存在的存储,可以在
BeginDirectModify
中创建一个内存上下文,或者使用该节点的EState
中的es_query_cxt
。
如果提供了fdw_scan_tlist
目标列表,则被返回的行必须匹配它。否则,被返回的行必须匹配被更新的外部表的行类型。如果选择优化掉RETURNING
计算不需要的列,应该在这些列的位置上插入空值,或者生成一个忽略这些列的fdw_scan_tlist
列表。
不管该查询是否具有RETURNING
子句,查询所报告的行计数必须由 FDW 本身增加。当查询没有该子句时,FDW 还必须为EXPLAIN ANALYZE
情况下的ForeignScanState
节点增加行计数。
如果IterateDirectModify
指针被设置为NULL
,不会尝试在远程服务器上执行直接修改。
void
EndDirectModify(ForeignScanState *node);
在远程服务器上的直接修改后进行清理。通常释放用 palloc 分配的内存并不重要,但是诸如打开的文件和到远程服务器的连接应该被清除。
如果EndDirectModify
指针被设置为NULL
,不会尝试在远程服务器上执行直接修改。
56.2.5. 用于行锁定的 FDW 例程
如果一个 FDW 希望支持后期行锁定(如第 56.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
,因此也不必提供它)。
更多信息请见第 56.5 节。
void
RefetchForeignRow(EState *estate,
ExecRowMark *erm,
Datum rowid,
TupleTableSlot *slot,
bool *updated);
从外部表中重新取得一个元组槽,如有必要先锁定它。estate
是该查询的全局执行状态。erm
是描述目标外部表以及要获取的行锁类型(如果有)的ExecRowMark
结构。rowid
标识要取得的元组。 slot
在调用时不包含有用内容,但可用于保存返回的元组。
updated
是一个输出参数。
此函数应将元组存储到提供的槽中,或者在无法获得行锁时清除该槽。 要获得的行锁由erm->markType
定义,它是之前由GetForeignRowMarkType
返回的值(ROW_MARK_REFERENCE
标识只重新取得元组但不获得任何锁,这个例程将不会看到ROW_MARK_COPY
)。
此外,如果取得的是一个更新过的版本而不是之前获得的同一版本,*updated
应被设置为true
(如果 FDW 无法确定这一点,推荐总是返回true
)。
注意在默认情况下,获取行锁失败应该导致产生错误。如果erm->waitPolicy
指定了SKIP LOCKED
,只有返回空槽才是合适的。
rowid
是要被重新取得的行之前读到的ctid
值。尽管rowid
值被作为Datum
传递,但是目前它只能被读作tid
。选择该函数 API 是希望未来能允许其他的行 ID 数据类型。
如果RefetchForeignRow
指针被设置为NULL
,重新取得行的尝试将会失败并伴随有一个错误消息。
更多信息请见第 56.5 节。
bool
RecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot);
重新检查之前返回的元组是否仍然匹配相关的扫描和连接条件,并且可能提供该元组的一个修改版本。对于不执行连接下推的外部数据包装器,通常把这设置为NULL
并且恰当地设置fdw_recheck_quals
会更方便。不过当外部连接被下推时,把与所有基表相关的检查重新应用在结果元组上是不够的,即便所有需要的属性都存在也是如此,因为匹配某个条件失败可能会导致某些属性变成 NULL,而不是没有元组被返回。
RecheckForeignScan
能够重新检查条件,并且在它们仍然满足时返回真,否则返回假,但是它也能够在提供的槽中存储一个替换元组。
要实现连接下推,外部数据包装器通常将构造一个可替代的本地连接计划,它只被用来做重新检查。这将变成ForeignScan
的外子计划。在需要一次重新检查时,这个子计划可以被执行并且结果元组可以被存储在槽中。这个计划不需要效率很高,因为不会有基表返回超过一行。例如,它可以把所有的连接实现为嵌套循环。函数GetExistingLocalJoinPath
可以被用来在已有的路径中搜索合适的本地连接路径,它可以被用作替换的本地连接计划。
GetExistingLocalJoinPath
会在指定连接关系的路径列表中搜索一个非参数化路径(如果没有找到这样的路径,它会返回 NULL,这种情况下外部数据包装器可以自行构造本地路径或者可以选择不为这个连接创建访问路径)。
56.2.6. EXPLAIN
的FDW例程
void
ExplainForeignScan(ForeignScanState *node,
ExplainState *es);
为一个外部表扫描打印额外的EXPLAIN
输出。这个函数可以调用ExplainPropertyText
和相关函数来向EXPLAIN
输出中增加域。es
中的标志域可以被用来决定什么将被打印,并且ForeignScanState
节点的状态可以被检查来为
EXPLAIN ANALYZE
提供运行时统计数据。
如果ExplainForeignScan
指针被设置为NULL
,在EXPLAIN
期间不会打印任何额外的信息。
void
ExplainForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
struct ExplainState *es);
为一个外部表更新打印额外的EXPLAIN
输出。这个函数可以调用ExplainPropertyText
和相关函数来向EXPLAIN
输出中增加域。es
中的标志域可以被用来决定什么将被打印,并且ModifyTableState
节点的状态可以被检查来为
EXPLAIN ANALYZE
提供运行时统计数据。前四个参数和BeginForeignModify
相同。
如果ExplainForeignModify
指针被设置为NULL
,在EXPLAIN
期间不会打印任何额外的信息。
void
ExplainDirectModify(ForeignScanState *node,
ExplainState *es);
为远程服务器上的直接修改打印额外的EXPLAIN
输出。这个函数可以调用ExplainPropertyText
和相关函数来为EXPLAIN
输出增加域。es
中的标志域可以被用来判断要打印什么,并且在EXPLAIN ANALYZE
情况中可以观察
ForeignScanState
节点的状态来提供运行时统计信息。
如果ExplainDirectModify
指针被设置为NULL
,EXPLAIN
期间不会打印出额外的信息。
56.2.7. ANALYZE
的FDW例程
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 )。
56.2.8. IMPORT FOREIGN SCHEMA
的 FDW 例程
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 可能会忽略ImportForeignSchemaStmt
的local_schema
域,因为核心服务器会自动地向解析好的CREATE FOREIGN TABLE
命令中插入本地模式的名称。
FDW 也不必担心实现list_type
以及table_list
所指定的过滤,因为核心服务器将自动根据那些选项跳过为被排除的表所返回的命令。不过,起初就避免为被排除的表创建命令当然更好。函数IsImportableForeignTable()
可以用来测试一个给定的外部表名是否能通过该过滤器。
如果 FDW 不支持导入表定义,ImportForeignSchema
指针可以被设置为NULL
。
56.2.9. 并行执行的 FDW 例程
ForeignScan
节点可以选择支持并行执行。一个并行的ForeignScan
将在多个进程中被执行并且在相互合作的进程中每一个元组必须只被返回一次。要做到这样,进程可以通过动态共享内存的固定尺寸块来协作。并不保证在每一个进程中这部份共享内存都被映射到相同的地址,因此不能包含指针。下面的函数通常都是可选的,但是如果要支持并行执行就必须提供其中的大部分。
bool
IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
测试一个扫描是否可以在一个并行工作者中被执行。只有当规划器相信可以使用并行计划时才会调用这个函数,如果该扫描在并行工作者中可以安全运行这个函数应该返回真。如果远程数据源具有事务语义,情况通常都不是这样,除非工作者到数据的连接能够以某种方式共享与领导者相同的事务环境。
如果没有定义这个函数,则假定该扫描必须被放置在并行领导者中。注意返回真并不意味着该扫描本身可以被并行完成,只是说明该扫描可以在一个并行工作者中执行。因此,即便当不支持并行执行时,定义这个方法也是有用的。
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段将被销毁,希望在DMS段消失前采取某种行动的外部数据包装器应该实现这个方法。
56.2.10. 用于路径重新参数化的FDW例程
List *
ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
RelOptInfo *child_rel);
在把一个由给定子关系child_rel
的最顶层父关系参数化的路径转换成由该子关系参数化的路径时会调用这个函数。该函数被用于重新参数化任意路径或者转化一个ForeignPath
的给定fdw_private
成员中保存的任意表达式节点。该回调可能会根据需要使用reparameterize_path_by_child
、
adjust_appendrel_attrs
或者adjust_appendrel_attrs_multilevel
。