学习PostgreSQL的FDW(#4)-其他函数说明 II

继上篇学习PostgreSQL的FDW(#3)-其他函数说明 I,继续说说其余回调函数

用于行锁定的FDW回调函数

如果一个FDW 希望支持后期行锁定,必须提供以下回调函数:

  • GetForeignRowMarkType
1
2
3
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,因此也不必提供它)。

  • RefetchForeignRow
1
2
3
4
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,重新取得行的尝试将会失败并伴随有一个错误消息。

  • RecheckForeignScan
1
2
3
bool
RecheckForeignScan(ForeignScanState *node,
TupleTableSlot *slot);

重新检查之前返回的元组是否仍然匹配相关的扫描和连接条件,并且可能提供该元组的一个修改版本。对于不执行连接下推的外部数据包装器,通常把这设置为NULL并且恰当地设置fdw_recheck_quals会更方便。不过当外部连接被下推时,把与所有基表相关的检查重新应用在结果元组上是不够的,即便所有需要的属性都存在也是如此,因为匹配某个条件失败可能会导致某些属性变成 NULL,而不是没有元组被返回。RecheckForeignScan能够重新检查条件,并且在它们仍然满足时返回真,否则返回假,但是它也能够在提供的槽中存储一个替换元组。

要实现连接下推,外部数据包装器通常将构造一个可替代的本地连接计划,它只被用来做重新检查。这将变成ForeignScan的外子计划。在需要一次重新检查时,这个子计划可以被执行并且结果元组可以被存储在槽中。这个计划不需要效率很高,因为不会有基表返回超过一行。例如,它可以把所有的连接实现为嵌套循环。函数GetExistingLocalJoinPath可以被用来在已有的路径中搜索合适的本地连接路径,它可以被用作替换的本地连接计划。GetExistingLocalJoinPath会在指定连接关系的路径列表中搜索一个非参数化路径(如果没有找到这样的路径,它会返回 NULL,这种情况下外部数据包装器可以自行构造本地路径或者可以选择不为这个连接创建访问路径)。

EXPLAIN 相关FDW回调函数

  • ExplainForeignScan
1
2
3
void
ExplainForeignScan(ForeignScanState *node,
ExplainState *es);

为一个外部表扫描打印额外的EXPLAIN输出。这个函数可以调用ExplainPropertyText和相关函数来向EXPLAIN输出中增加域。es中的标志域可以被用来决定什么将被打印,并且ForeignScanState节点的状态可以被检查来为EXPLAIN ANALYZE提供运行时统计数据。

如果ExplainForeignScan指针被设置为NULL,在EXPLAIN期间不会打印任何额外的信息。

  • ExplainForeignModify
1
2
3
4
5
6
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期间不会打印任何额外的信息。

  • ExplainDirectModify
1
2
3
void
ExplainDirectModify(ForeignScanState *node,
ExplainState *es);

为远程服务器上的直接修改打印额外的EXPLAIN输出。这个函数可以调用ExplainPropertyText和相关函数来为EXPLAIN输出增加域。es中的标志域可以被用来判断要打印什么,并且在EXPLAIN ANALYZE情况中可以观察ForeignScanState节点的状态来提供运行时统计信息。

如果ExplainDirectModify指针被设置为NULL,EXPLAIN期间不会打印出额外的信息。

ANALYZE 相关FDW回调函数

  • AnalyzeForeignTable
1
2
3
4
bool
AnalyzeForeignTable (Relation relation,
AcquireSampleRowsFunc *func,
BlockNumber *totalpages);

当ANALYZE被执行在一个外部表上时会调用这个函数。如果FDW可以为这个外部表收集统计信息,它会返回true并提供一个函数指针,该函数将将从func中的表上收集采样行,外加totalpages中页面中的表尺寸估计值。否则,返回false。

如果FDW不支持为任何表收集统计信息,AnalyzeForeignTable指针可以被设置为NULL。

如果提供,采样收集函数必须具有签名

  • AcquireSampleRowsFunc
1
2
3
4
5
6
7
int
AcquireSampleRowsFunc(Relation relation,
int elevel,
HeapTuple *rows,
int targrows,
double *totalrows,
double *totaldeadrows);

应该从该表上收集最多targrows行的一个随机采样并将它存放到调用者提供的rows数组中。实际被收集的行的数量必须被返回。另外,将表中有效行和死亡行的总数存储到输出参数totalrows和totaldeadrows中(如果FDW没有死亡行的概念,将totaldeadrows设置为 0 )。

IMPORT FOREIGN SCHEMA 回调函数

1
2
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。

并行执行 回调函数

ForeignScan节点可以选择支持并行执行。一个并行的 ForeignScan将在多个进程中执行, 并且在相互合作的进程中每个行必须只被返回一次。要做到这样, 进程可以通过动态共享内存的固定尺寸块来协作。并不保证在每一个进程中这部份共享内存都被映射到相同的地址, 因此必须不能包含指针。下面的函数都是可选的,但是如果要支持并行执行需要提供大多数函数。

  • IsForeignScanParallelSafe
    1
    2
    3
    bool
    IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
    RangeTblEntry *rte);

测试一个扫描是否可以在一个并行工作者中被执行。只有当规划器相信可以使用并行计划时才会调用这个函数,如果该扫描在并行工作者中可以安全运行这个函数应该返回真。如果远程数据源具有事务语义,情况通常都不是这样,除非工作者到数据的连接能够以某种方式共享与领导者相同的事务环境。

如果没有定义这个函数,则假定该扫描必须被放置在并行领导者中。注意返回真并不意味着该扫描本身可以被并行完成,只是说明该扫描可以在一个并行工作者中执行。因此,即便当不支持并行执行时,定义这个方法也是有用的。

  • EstimateDSMForeignScan
1
2
Size
EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);

估算并行操作所需的动态共享内存的数量。这可能比实际要用的数量更大,但是绝不能更小。返回值的单位是字节。该函数是可选的,如果不需要可以省略;但是如果省略它, 接下来的三个函数也必须省略,因为没有分配共享内存将给FDW使用。

  • InitializeDSMForeignScan
1
2
3
void
InitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
void *coordinate);

初始化并行操作所需的动态共享内存。coordinate指向共享内存区域, 其大小等于EstimateDSMForeignScan的返回值。 该函数是可选的,如果不需要可以省略。

  • ReInitializeDSMForeignScan
1
2
3
void
ReInitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
void *coordinate);

当外部扫描计划节点即将被重新扫描时,重新初始化并行操作所需的动态共享内存。该函数是可选的,如果不需要可以省略。推荐的做法是此函数仅重置共享状态, 而ReScanForeignScan函数仅重置本地状态。目前, 这个函数会在ReScanForeignScan之前调用,但最好不要依赖这个顺序。

  • InitializeWorkerForeignScan
1
2
3
void
InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc,
void *coordinate);

基于InitializeDSMForeignScan期间领导者设置的共享状态初始化并行工作者的本地状态。这个函数是可选的, 如果不需要可以省略。

  • ShutdownForeignScan
1
2
void
ShutdownForeignScan(ForeignScanState *node);

预计节点将不会执行完成时释放资源。这在所有情况下都不被调用;有时, EndForeignScan可能会在没有先调用此函数的情况下调用。由于并行查询使用的DSM段在调用此回调后即被销毁, 因此希望在DSM段消失之前采取某些操作的外部数据包装器应实现此方法。

用于路径重新参数化 的回调函数

  • ReparameterizeForeignPathByChild
1
2
3
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。