9.3 9.4 9.5 9.6 10 11 12 13
阿里云PostgreSQL 问题报告 纠错本页面

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

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

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

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

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

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

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

更多信息请见第 54.4 节

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

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

这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath节点),并且必须调用add_path把每一个这样的路径加入到baserel->pathlist中。我们推荐使用create_foreignscan_path来建立ForeignPath节点。该函数可以生成多个访问路径,例如一个具有合法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(在前面由GetForeignPathsGetForeignJoinPaths产生)。 由计划节点发出目标列表,计划节点强制执行限制子句以及ForeignScan的外部子查询, 用于RecheckForeignScan执行的重新检查。(如果路径是一个连接而不是基本关系, 那么foreigntableidInvalidOid。)

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

更多信息见第 54.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

被返回的行必须匹配提供的fdw_scan_tlist目标列。 否则,它们必须与被扫描的外表的行类型匹配。 如果你选择将那些不需要的列优化掉,你应该在那些列位置上插入空值。 否则产生忽略掉那些列的fdw_scan_tlist列表。

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

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, 这个函数为提供的joinrel产生ForeignPath路径, 并且调用add_path添加这些路径到考虑链接的路径集合中。 但不同于GetForeignPaths, 这个函数成功创建至少一个路径是没有必要的, 因为涉及本地链接的路径总是有可能的。

请注意,此函数为了相同的连接关系以及内、外部关系的不同组合将被重复调用; FDW有责任减少重复工作。

如果为链接选择ForeignPath路径, 它将代表整个链接过程;不会使用为组件表生成的路径以及子辅助链接。 链接路径过程的随后处理正如扫描单一外部表的路径一样。 不同的是产生的ForeignScan计划节点的scanrelid应设置为零, 因为它没有一个单一的关系,相反, ForeignScan节点的fs_relids字段代表要加入的关系集。 (较后面的字段是由核心策划代码自动设置的,不需要通过FDW填充。) 因为没有发现来自系统目录的远程连接的字段列表, 另一个区别是,FDW必须填充fdw_scan_tlist以及TargetEntry节点的适当列表。 代表一组列提供运行时返回的元组。

参阅第 54.4 节获取附加信息。

54.2.3. 更新外部表的FDW例程

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

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

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

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

更多信息见第 54.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子句或者 外部表具有一个AFTER ROW触发器时才被使用。触发器要求所有的列,但是 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子句或者 外部表具有一个AFTER ROW触发器时才被使用。触发器要求所有的列,但是 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子句或者 外部表具有一个AFTER ROW触发器时才被使用。触发器要求所有的列,但是 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视图中的可更新性)。

54.2.4. 行锁定的FDW例程

如果FDW希望支持末行锁(正如第 54.5 节所描述的), 它必须提供以下回调函数:

RowMarkType
GetForeignRowMarkType (RangeTblEntry *rte,
                       LockClauseStrength strength);

报告用于外部表的行标记选项。rte是表的RangeTblEntry节点, 并且strength描述通过相关的FOR UPDATE/SHARE子句请求的锁强度, 如果存在的情况下。结果必须是RowMarkType枚举类型成员。

在查询规划UPDATEDELETE或者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是一个输出参数。

如果无法获得行锁,这个函数应该返回获取的元组的复制或者NULL, 获取的行锁类型由erm->markType定义, 通过GetForeignRowMarkType返回之前的值。 (ROW_MARK_REFERENCE意味着重新获取没有获取任何锁的元组, 并且这个例程不会看到ROW_MARK_COPY。)

此外,如果获取的是一个元组的更新版本, 而不是先前获得的相同版本。*updated应该设置为true (如果FDW无法确定,建议总是返回true。)

请注意缺省情况下,未能获得行锁导致产生错误; 如果通过erm->waitPolicy指定SKIP LOCKED选项, 则NULL返回是唯一适当的。

rowid是之前行重新抓取的ctid值。 尽管rowid值作为Datum传递,它目前只为tid。 选择函数API希望允许将来行ID其它数据类型。

如果RefetchForeignRow指针被设置为NULL, 尝试重新获取行出现错误消息而失败。它目前只能为tid

参见第 54.5 节获取更多信息。

 
bool
RecheckForeignScan (ForeignScanState *node, TupleTableSlot *slot);

重新检查之前返回的元组仍然匹配相关的扫描和连接限定符, 并且有可能提供元组的修改版本。 外部数据封装器不执行连接下推, 通常设置为NULL更方便, 而不是设置为fdw_recheck_quals。 当外连接被向下推时,然而,它不足以重新应用所有的基表与结果元组的检查相关性, 即使所有需要的属性都是存在的, 因为没有匹配一些限定符可能会导致一些属性为空, 而不是没有返回元组。如果它们仍然满足并为假, 则RecheckForeignScan可以复查限定符并返回真, 否则,它也可以将替换的元组存储到所提供的狭槽中。

为了实现连接下推,外部数据包通常会建立用于复查的其它的本地连接计划; 这将成为ForeignScan的外部子查询。 当必须复查时,可以执行这个子计划并且产生的元组可以被存储在插槽中。 这个计划不必是有效的,因为没有基表会返回更多行; 例如,它可以作为嵌套循环实现所有的连接。

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

54.2.6. 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 )。

54.2.7. 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可能忽略ImportForeignSchemaStmtlocal_schema字段, 因为核心服务器自动地将该名称插入到解析的CREATE FOREIGN TABLE命令中。

FDW不用担心自身实现由list_typetable_list指定的过滤, 要么,作为核心服务器将自动为了依据这些选项排除的表而忽略任何返回的命令。 然而,它可以有效的避免为第一个位置处的排除表创建命令的工作。 函数IsImportableForeignTable()可以有效的测试给定的外表名称是否会通过过滤器。

如果FDW不支持导入表定义, 那么ImportForeignSchema指针可以设置为NULL

<
/BODY >