PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 53. 写一个外部数据封装器 | 下一页

# 53.2. 外数据封装回调程序

FDW处理函数返回包含指向下面描述的回调函数指针的palloc'd `FdwRoutine`结构。 扫描相关函数是必须的，其余的是可选的。

在`src/include/foreign/fdwapi.h`中声明`FdwRoutine`结构类型， 参阅额外详情。

## 53.2.1. 扫描外表的FDW程序

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

获得评估外表关系大小。这就是所谓的开始扫描外表的查询规划。 `root`是关于查询的规划器的全局信息； `baserel`是关于这个表的规划器信息； `foreigntableid`是外表的`pg_class` OID。 （`foreigntableid`可以从规划器数据结构中获得， 但是它明确被传递用来节省力气。）

在说明限制资格测试执行过滤之后， 该函数应该更新`baserel->rows`为通过表扫描返回的行期望数。 `baserel->rows`的初始值仅仅是恒定缺省估计， 如果可能的话，这应该被替换。如果它可以对平均结果行宽度计算出更好的评估，那么 该函数可能也会选择更新`baserel->width`。

参阅第 53.4 节可以获取额外信息。

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

创建外表扫描的可能访问路径。 这就是所谓的查询规划。 它被调用的参数和`GetForeignRelSize`相同。

该函数必须为外表扫描产生至少1个访问路径(`ForeignPath`节点)而且 必须调用`add_path`添加每个这样的路径到`baserel->pathlist`中。 推荐使用`create_foreignscan_path`建立`ForeignPath`节点。 该函数可以生成多个访问路径，比如具有有效`pathkeys`表示预排序结果路径。 每个访问路径必须包含成本估计，并且包含需要标识具体预期扫描方法的任何FDW-私有信息。

参阅第 53.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`节点。

参阅第 53.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。元组表槽基础设施允许返回物理或者虚拟元组。 在大多数情况下后者选择从性能角度更可取。 注意这被称为在调用期间被重置的短暂内存语境。如果 你需要较长时间存储，或者使用节点的`EState`的`es_query_cxt`， 那么在`BeginForeignScan`中创建内存上下文。

返回的行必须匹配扫描外表的列标志。如果你选择优化掉不需要的列，那么 你应该在那些列位置插入空值。

注意PostgreSQL的执行器并不在乎返回的行是否违反任何在外表列定义的`NOT NULL` 约束— 但是规划器关心，如果`NULL`值出现在声明列中而不包含它们，那么可能错误地优化查询。 当用户声明不应该存在时，如果遇到`NULL`值，它可能会适当提高错误 （正如你需要在数据类型不匹配的情况下执行）。

    void
    ReScanForeignScan (ForeignScanState *node);

从开始重启扫描。注意任何参数扫描取决于已改变的值， 因此扫描不一定返回完全相同的行。

    void
    EndForeignScan (ForeignScanState *node);

结束扫描并且释放资源。释放palloc内存往往不重要，但是比如打开文件并且链接远程服务器应该 被清理干净。

## 53.2.2. 更新外表FDW程序

如果FDW支持可写外表， 那么它应该提供一些或者所有下面的依赖于 FDW的需要和能力的回调函数：

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

在通过表扫描函数预先读取行之前执行`UPDATE`和`DELETE`操作。 FDW可能需要额外信息，比如行ID或者主键列值，为了确保它可以找到确切行更新或者删除。 为了支持它，该函数可以添加额外隐藏，或者"junk"，在`UPDATE`或者 `DELETE`中从外表中检索列表中的目标列。

要做到这一点，添加`TargetEntry`项到 `parsetree->targetList`，包含读取的额外值的表达式。 每个这样的项必须被标记`resjunk` = `true`， 并且有一个不同的`resname`在执行期间标识它。 避免使用匹配`ctid`N``、`wholerow` 或者`wholerow`N``的名称，正如核心系统可以 产生这些名字的垃圾列。

在改写过程中调用该函数，而不是规划器，因此该可用信息不同于可用的规划程序。 当`target_rte`和 `target_relation`描述目标外表时，`parsetree`是`UPDATE`或者 `DELETE`命令的解析树。

如果`AddForeignUpdateTargets`指针被设置为`NULL`， 那么没有额外目标表达式被添加。 （这将不可能实现`DELETE`操作，尽管`UPDATE`可能仍然是可行的， 如果FDW依赖于一个标识行的未改变主键）。

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

执行任何额外规划操作需要插入，更新或者删除外表。 该函数产生附属于执行更新操作的`ModifyTable`规划节点 的FDW-私有信息。这个私有信息必须有`List`形式，并且 在执行阶段将转交给`BeginForeignModify`。

`root`是关于查询的规划器的全局信息。 `plan`是`ModifyTable`规划节点， 除了`fdwPrivLists`字段外它是完整的。 `resultRelation`通过射程表索引识别目标外表。 `subplan_index`识别从零开始计算的`ModifyTable`规划节点是哪个目标； 如果你想要索引`plan->plans`或者其他`plan`节点的子结构，那么使用它。

参阅第 53.4 节获取更多额外信息。

如果`PlanForeignModify`指针被设置为`NULL`， 没有采取额外规划时间操作，并且`fdw_private`列表转交给 `BeginForeignModify`为零。

    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`包含要插入的元组；它将匹配外表rowtype定义。 `planSlot`包含通过`ModifyTable`规划节点的子计划产生的元组； 它不同于可能包含额外"junk"列的`slot`。

返回值要么是包含实际插入的数据的槽（这可能与提供的数据不同，比如作为触发器操作结果）， 如果没有行实际被插入，那么返回NULL（再次，通常作为触发器结果）。 传入的`slot`可以重新用于这个目的。

只有`INSERT`查询有`RETURNING`子句，或该外部表有一个 `AFTER ROW`触发器时，才使用返回槽中的数据。触发器请求所有的字段， 但是FDW可能选择优化返回依赖于`RETURNING`子句内容的一些或者全部列。 不管怎样，必须返回一些槽以表示成功，否则该查询报告的行数是错误的。

如果`ExecForeignInsert`指针被设置为`NULL`， 尝试插入外表将带有错误消息而失败。

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

更新外表上的元组。 `estate`是查询的全局执行状态。`rinfo`是描述目标外表的 `ResultRelInfo`结构。`slot`包含元组的新数据； 它将匹配外表rowtype定义。 `planSlot`包含通过`ModifyTable`规划节点的子计划产生的元组； 它不同于可能包含额外"junk"列的`slot`。 尤其是，通过`AddForeignUpdateTargets`请求的任何垃圾列将从该槽中提供。

返回值要么是包含实际更新的行的槽（这可能与提供的数据不同，比如作为触发器操作结果）， 如果没有行实际被更新，那么返回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分别 提供`ExecForeignInsert`, `ExecForeignUpdate`或者`ExecForeignDelete`，那么 外表被认为是可插入，可更新或者可删除的。 如果FDW支持一些可更新的和一些不可更新的表，那么需要这个函数。 （即使这样，它允许在执行程序中抛出错误而不是在这个函数中进行检查。然而，该函数 用于在`information_schema`视图中显示可更新。）

## 53.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`期间不打印任何额外信息。

## 53.2.4. `ANALYZE`的FDW程序

    bool
    AnalyzeForeignTable (Relation relation,
                         AcquireSampleRowsFunc *func,
                         BlockNumber *totalpages);

当在外表上执行ANALYZE时，调用该函数。 如果FDW可以收集外表的统计，它应该返回`真`， 并且提供一个指针给函数，该函数从`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`为零。）

上一页 | 起始页 | 下一页
外数据封装函数 | 上一级 | 外数据封装辅助函数