52.2. 外部数据包装器回调例程

FDW处理器函数返回一个palloc过的FdwRoutine结构,它包含下文描述的回调函数的指针。扫描相关的函数是必需的,剩下的是可选的。

FdwRoutine结构类型被声明在src/include/foreign/fdwapi.h中,可以查看它来获得额外的信息。

52.2.1. 用于扫描外部表的FDW例程

void
GetForeignRelSize (PlannerInfo *root,
                   RelOptInfo *baserel,
                   Oid foreigntableid);

获取一个外部表的关系尺寸估计。在对一个扫描外部表的查询进行规划的开头将调用该函数。root是规划器的关于该查询的全局信息;baserel是规划器的关于该表的信息;foreigntableid是外部表在pg_class中的 OID (foreigntableid可以从规划器的数据结构中获得,但是为了减少工作量,这里直接显式地将它传递给函数)。

这个函数应该更新baserel->rows为表扫描根据限制条件完成了过滤后将返回的预期行数。baserel->rows的初始值只是一个常数的默认估计值,应该尽可能把它替换掉。如果该函数能够计算出一个平均结果行宽度的更好的估计值,该函数也可能选择更新baserel->width

更多信息请见Section 52.4

void
GetForeignPaths (PlannerInfo *root,
                 RelOptInfo *baserel,
                 Oid foreigntableid);

为一个外部表上的扫描创建可能的访问路径。这个函数在查询规划过程中被调用。参数和GetForeignRelSize相同,后者已经被调用过了。

这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath节点),并且必须调用add_path把每一个这样的路径加入到baserel->pathlist中。我们推荐使用create_foreignscan_path来建立ForeignPath节点。该函数可以生成多个访问路径,例如一个具有合法pathkeys的路径表示一个预排序好的结果。每一个反问路径必须包含代价估计,并且能包含任何FDW的私有信息,这种信息被用来标识想要使用的指定扫描方法。

更多信息请见Section 52.4

ForeignScan *
GetForeignPlan (PlannerInfo *root,
                RelOptInfo *baserel,
                Oid foreigntableid,
                ForeignPath *best_path,
                List *tlist,
                List *scan_clauses);

从选择的外部访问路径创建一个ForeignScan计划节点。这个函数在查询规划的末尾被调用。参数和GetForeignRelSize的一样,外加选中的ForeignPath(在前面由GetForeignPaths产生)、被计划节点发出的目标列表以及计划节点强制的限制子句。

这个函数必须创建并返回一个ForeignScan计划节点,我们对剑使用make_foreignscan来建立ForeignScan节点。

更多信息见Section 52.4

void
BeginForeignScan (ForeignScanState *node,
                  int eflags);

开始执行一个外部扫描。这个函数在执行器启动阶段被调用。它应该执行任何在扫描能够开始之前需要完成的初始化工作,但是并不开始执行真正的扫描(会在第一次调用IterateForeignScan时完成)。ForeignScanState节点已经被创建好了,但是它的fdw_state域仍然为 NULL。关于要被扫描的表的信息可以通过ForeignScanState节点访问(特殊地,从底层的ForeignScan计划节点,它包含任何由GetForeignPlan提供的FDW私有信息)。eflags包含描述执行器对该计划节点操作模式的标志位。

注意当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应该执行任何外部可见的动作;它应当只做最少的事情来创建对ExplainForeignScanEndForeignScan有效的节点状态。

TupleTableSlot *
IterateForeignScan (ForeignScanState *node);

从外部源获得一行,将它放在一个元组表槽中返回(节点的ScanTupleSlot应当被用于此目的)。如果没有更多的行可用则返回 NULL。元组表槽设施允许一个物理的或者虚拟的元组被返回;在大部分情况下出于性能的考虑会倾向于选择后者。注意这是在一个短期存在的内存上下文中被调用的,该内存上下文会在调用之间被重置。如果你需要长期存在的存储,请在BeginForeignScan中创建内存上下文,或者使用节点的EState中的es_query_cxt

被返回的行必须匹配被扫描的外部表的列签名。如果你选择将那些不需要的列优化掉,你应该在那些列位置上插入空值。

注意PostgreSQL的执行器并不关心被返回的行是否违反任何定义在外部表列上的NOT NULL约束 — 但是规划器却关心,并且如果NULL值出现在一个被声明为不可能包含它们的列中,优化器可能会不正确地优化查询。如果用户已经声明了不会出现空值时遇到一个NULL值,可能抛出一个错误更为合适(就像你在数据类型失配的情况下你需要做的一样)。

void
ReScanForeignScan (ForeignScanState *node);

从头开始重启一个扫描。注意扫描所依赖的任何参数可能已经改变了值,因此新扫描不一定会返回完全相同的行。

void
EndForeignScan (ForeignScanState *node);

结束扫描并释放资源。通常释放palloc过的内存并不重要,但是打开的文件和到远程服务器的连接等应该被清理。

52.2.2. 更新外部表的FDW例程

如果一个FDW支持可写的外部表,根据FDW的需要和功能它应该提供某些或者全部下列回调函数:

void
AddForeignUpdateTargets (Query *parsetree,
                         RangeTblEntry *target_rte,
                         Relation target_relation);

UPDATEDELETE操作是在之前由表扫描函数取出的行上被执行的。FDW可能需要额外的信息(例如一个行ID或主键列的值)来保证它能够找到要更新或删除的准确行。要支持这些要求,这个函数可以项列列表中增加额外的隐藏或"junk"的目标列,它们在一个UPDATEDELETE期间会被从外部表中获取。

要做到这一点,向parsetree->targetList中增加TargetEntry项,它们包含要被获取的额外值的表达式。每一个这样的项必须被标记为resjunk = true,并且必须有一个可区分的resname用于在执行期间标识它。请避免使用匹配ctidNwholerowN的名字,因为核心系统可能会生成使用这些名字的junk列。

这个函数在重写器中被调用,而不是在规划器中,因此可用的信息与在规划例程中的有点区别。parsetreeUPDATEDELETE命令的分析树,而target_rtetarget_relation描述目标外部表。

如果AddForeignUpdateTargets指针被设置为NULL,则不会有额外的目标表达式被加入(这将使得我们不可能实现DELETE操作,而UPDATE则还有可能是可行的,前提是FDW依赖一个未改变的主键来标识行)。

List *
PlanForeignModify (PlannerInfo *root,
                   ModifyTable *plan,
                   Index resultRelation,
                   int subplan_index);

执行外部表上插入、更新或删除所需的任何附加规划动作。这个函数生成FDW私有信息,该信息将被附加到执行该更新动作的ModifyTable计划节点。这个私有信息的形式必须是一个List,并将会在执行阶段被传递给BeginForeignModify

root是规划器关于该查询的全局信息。 planModifyTable计划节点,它除了fdwPrivLists域之外是完整的。 resultRelation通过目标外部表的范围表索引来标识它。subplan_index标识这是ModifyTable计划节点的哪个目标,从零开始计数;如果你希望索引到plan->plans或其他plan节点的子结构中,请使用它。

更多信息见Section 52.4

如果PlanForeignModify指针被设置为NULL,则不会有额外的计划时动作被执行,并且传递给BeginForeignModifyfdw_private列表也将为 NIL。

void
BeginForeignModify (ModifyTableState *mtstate,
                    ResultRelInfo *rinfo,
                    List *fdw_private,
                    int subplan_index,
                    int eflags);

开始执行一个外部表修改操作。这个例程在执行器启动期间被调用。它应该执行任何先于实际表修改的初始化工作。随后,ExecForeignInsertExecForeignUpdateExecForeignDelete将被为每一个将被插入、更新或删除的元组调用。

mtstate是要被执行的ModifyTable计划节点的状态信息;通过这个结构可以得到关于规划和执行阶段的全局数据。rinfo是描述目标外部表的ResultRelInfo结构(ResultRelInfori_FdwState域用于FDW来存储它在此操作中需要的任何私有状态)。fdw_private包含PlanForeignModify生成的私有数据。subplan_index标识这是ModifyTable计划节点的哪个目标。eflags包含描述执行器对该计划节点操作模式的标志位。

注意当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真,这个函数不应执行任何外部可见的动作;它只应该做最少的工作来创建ExplainForeignModifyEndForeignModify可用的节点状态。

如果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子句时才被使用。因此,FDW应该选择优化成根据RETURNING子句的内容返回某些或全部列。但是,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。

如果ExecForeignInsert指针被设置为NULL,尝试向外部表插入将会失败并报告一个错误消息。

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子句时才被使用。因此,FDW应该选择优化成根据RETURNING子句的内容返回某些或全部列。但是,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。

如果ExecForeignUpdate指针被设置为NULL,尝试更新外部表将会失败并报告一个错误消息。

TupleTableSlot *
ExecForeignDelete (EState *estate,
                   ResultRelInfo *rinfo,
                   TupleTableSlot *slot,
                   TupleTableSlot *planSlot);

从外部表删除一个元组。estate是查询的全局执行状态。rinfo是描述目标外部表的ResultRelInfo结构。slot在调用时不包含任何有用的东西,但是可以被用于保持被返回的元组。planSlot包含由ModifyTable计划节点的子计划生成的元组;特殊地,它将携带AddForeignUpdateTargets所要求的任意垃圾列。垃圾列被用来标识要被删除的元组。

返回值可以是一个包含实际被删除的数据的槽(这可能会和所提供的数据不同,例如一个触发器动作的结果),或者为 NULL 表示实际没有删除行(还是触发器的结果)。被传入的slot可以被重用于这个目的。

在返回槽中的数据只有在DELETE查询具有一个RETURNING子句时才被使用。因此,FDW应该选择优化成根据RETURNING子句的内容返回某些或全部列。但是,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。

如果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提供了ExecForeignInsertExecForeignUpdateExecForeignDelete,则外部表分别被假定为可插入、可更新或可删除。只有在FDW支持某些表是可更新的而某些不是可更新的时候,才需要这个函数(即便如此,也允许在执行例程中抛出一个错误而不是在这个函数中检查。但是,这个函数被用来决定显示在information_schema视图中的可更新性)。

52.2.3. 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期间不会打印任何额外的信息。

52.2.4. 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数组中。实际被收集的行的数量必须被返回。另外,将表中有效行和死亡行的总数存储到输出参数totalrowstotaldeadrows中(如果FDW没有死亡行的概念,将totaldeadrows设置为 0 )。