写一个外部数据的包装器
写一个外部数据的包装器
本章概述了如何写一个新的外部数据包装器。
外部表上的所有操作都是通过其外部数据包装器(FDW)处理的,FDW是一个包含了Greenplum数据库服务器核心调用的一组函数组成的库。 外部数据包装器负责从远程数据存储中获取数据并将其递交给Greenplum数据库执行器。 如果外部数据支持更新,则包装器也必须能处理。
当你准备自己写一个包装器的时候,包含在Greenplum数据库在github上的开源仓库里的外部数据包装器是一个很好的参考。 你需要查阅一下contrib/目录下的 file_fdw 和postgres_fdw 模块。 CREATE FOREIGN DATA WRAPPER 的参考页面也提供了一些有用的信息。
该文章包含如下章节:
需要准备的东西
已知问题和限制
Greenplum 6 Beta版的外部数据包装器实现包括如下已知问题和限制:
- Greenplum 6 Beta版不会安装任何外部数据包装器。
- Greenplum数据库仅对外部表扫描使用mpp_execute选项值。 当插入或更新一个外部表的时候,Greenplum不遵循mpp_execute设置;所有的写操作都从master进行初始化。
头文件
开发外部数据包装器过程中可能使用到的头文件在greenplum-db/src/include/目录里 (针对Greenplum数据库github开源仓库做开发时),或安装在$GPHOME/include/postgresql/server/目录里(针对Greenplum安装版做开发时):
- foreign/fdwapi.h - FDW API 相关结构体和回调函数
- foreign/foreign.h - FDW帮助相关的结构体和函数
- catalog/pg_foreign_table.h - 外部表定义
- catalog/pg_foreign_server.h - 外部服务定义
您的FDW代码也可能依赖于访问远程数据存储所需的头文件和库。
外部数据包装器函数
开发者必须实现一个SQL可调用的处理函数,和一个可选的验证函数。这两个函数必须使用像C一样的编译语言,都用version-1接口。
处理函数会返回一个被Greenplum数据库优化器,执行器和各个维护命令调用的函数指针结构体。 处理函数必须在Greenplum数据库中注册为不带参数并且返回特殊的伪类型fdw_handler。 例如:
CREATE FUNCTION NEW_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
写一个外部数据包装器的大部分工作就是实现这些回调函数。 对于在SQL级别不可见或不可调用的FDW API回调函数和普通的C函数在 Foreign Data Wrapper Callback Functions里有详细描述。
验证器函数负责验证CREATE和ALTER命令中为其外部数据包装器提供的选项, 以及使用包装器的外部服务器, 用户映射和外部表。 验证器函数必须注册为带两个参数,一个包含要验证的选项的文本数组,以及一个表示与选项关联的对象类型的OID。例如:
CREATE FUNCTION NEW_fdw_validator( text[], oid ) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
OID参数反映了对象存储在系统catalog中的类型,其中包括 ForeignDataWrapperRelationId, ForeignServerRelationId, UserMappingRelationId, 或 ForeignTableRelationId. 如果外部数据包装器未提供验证器函数,则Greenplum数据库不会在对象创建时或对象更改时检查选项有效性。
外部数据包装器回调函数
外部数据包装器API定义了Greenplum数据库在扫描和更新外部表时调用的回调函数。API还包括了对外部表做explain和analyze操作的回调函数。
外部数据包装器处理函数会返回一个由palloc申请的包含下面描述的回调函数指针的FdwRoutine结构体。 FdwRoutine结构体存储在头文件foreign/fdwapi.h中,下面是定义:
/* * FdwRoutine is the struct returned by a foreign-data wrapper's handler * function. It provides pointers to the callback functions needed by the * planner and executor. * * More function pointers are likely to be added in the future. Therefore * it's recommended that the handler initialize the struct with * makeNode(FdwRoutine) so that all fields are set to NULL. This will * ensure that no fields are accidentally left undefined. */ typedef struct FdwRoutine { NodeTag type; /* Functions for scanning foreign tables */ GetForeignRelSize_function GetForeignRelSize; GetForeignPaths_function GetForeignPaths; GetForeignPlan_function GetForeignPlan; BeginForeignScan_function BeginForeignScan; IterateForeignScan_function IterateForeignScan; ReScanForeignScan_function ReScanForeignScan; EndForeignScan_function EndForeignScan; /* * Remaining functions are optional. Set the pointer to NULL for any that * are not provided. */ /* Functions for updating foreign tables */ AddForeignUpdateTargets_function AddForeignUpdateTargets; PlanForeignModify_function PlanForeignModify; BeginForeignModify_function BeginForeignModify; ExecForeignInsert_function ExecForeignInsert; ExecForeignUpdate_function ExecForeignUpdate; ExecForeignDelete_function ExecForeignDelete; EndForeignModify_function EndForeignModify; IsForeignRelUpdatable_function IsForeignRelUpdatable; /* Support functions for EXPLAIN */ ExplainForeignScan_function ExplainForeignScan; ExplainForeignModify_function ExplainForeignModify; /* Support functions for ANALYZE */ AnalyzeForeignTable_function AnalyzeForeignTable; } FdwRoutine;
必须在外部数据包装器中实现和扫描相关的函数,其他回调函数是可选的。
扫描相关的函数包括:
回调签名 | 描述 |
---|---|
void GetForeignRelSize (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) |
获取一个外部表大小的估算值。在对一个外部表做查询计划的开始时调用。 |
void GetForeignPaths (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) |
为一个外部表的扫描操作创建可能的访问路径。在查询计划过程中调用。
Note: 注意:一个与Greenplum数据库相兼容的FDW必须在它的
GetForeignPaths()回调函数中调用
create_foreignscan_path()。
|
ForeignScan * GetForeignPlan (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses) |
从已选择的外部访问路径中创建一个ForeignScan计划节点。 在查询计划结束时调用。 |
void BeginForeignScan (ForeignScanState *node, int eflags) |
开始执行一个外部扫描。在执行器启动时调用。 |
TupleTableSlot * IterateForeignScan (ForeignScanState *node) |
从外部源获取一行,并在一个元组表槽中返回;如果没有可用的行则返回NULL。 |
void ReScanForeignScan (ForeignScanState *node) |
从头开始重新扫描。 |
void EndForeignScan (ForeignScanState *node) |
结束扫描并释放资源。 |
关于FDW回调函数的输入输出更详细的信息,请参考PostgreSQL文档 Foreign Data Wrapper Callback Routines。
外部数据包装器帮助函数
FDW API从Greenplum数据库核心服务输出了几个帮助函数, 所以外部数据包装器的作者可以轻松的访问FDW相关对象的属性, 比如当用户创建或修改外部数据包装器、服务或外部表时提供的选项。 要使用这些帮助函数,必须在源文件里包含foreign.h头文件:
#include "foreign/foreign.h"
FDW API包括了下面表格列出的这些帮助函数。 想了解更多关于这些函数的信息请参考PostgreSQL文档 Foreign Data Wrapper Helper Functions。
帮着签名 | 描述 |
---|---|
ForeignDataWrapper * GetForeignDataWrapper(Oid fdwid); |
通过给定的OID,返回外部数据包装器ForeignDataWrapper对象。 |
ForeignDataWrapper * GetForeignDataWrapperByName(const char *name, bool missing_ok); |
通过给定的名称,返回外部数据包装器ForeignDataWrapper对象。 |
ForeignServer * GetForeignServer(Oid serverid); |
通过给定的OID,返回外部服务的ForeignServer对象。 |
ForeignServer * GetForeignServerByName(const char *name, bool missing_ok); |
通过给定的名称,返回外部服务的ForeignServer对象。 |
UserMapping * GetUserMapping(Oid userid, Oid serverid); |
在给定的服务上为给定的用户返回用户映射UserMapping对象。 |
ForeignTable * GetForeignTable(Oid relid); |
根据OID返回对应外部表的ForeignTable对象。 |
List * GetForeignColumnOptions(Oid relid, AttrNumber attnum); |
根据OID对应的外部表和属性编号,返回列的FDW选项。 |
Greenplum数据库注意事项
一个Greenplum数据库用户可以在创建和修改一个外部表、外部服务或外部数据包装器时指定mpp_execute选项。 一个Greenplum数据库兼容的外部数据包装器会在扫描时检查mpp_execute属性值并根据它决定向哪发送数据, 包括:向master(默认值),任何节点(master或任何一个节点),或所有节点。
下面的扫描代码片段探测与外部表关联的mpp_execute值。
ForeignTable *table = GetForeignTable(foreigntableid); if (table->exec_location == FTEXECLOCATION_ALL_SEGMENTS) { ... } else if (table->exec_location == FTEXECLOCATION_ANY) { ... } else if (table->exec_location == FTEXECLOCATION_MASTER) { ... }
如果外部表创建和外部服务的时候没有设置mpp_execute, 外部数据包装器会探测并决定。 如果没有任何一个外部数据相关的对象设置mpp_execute, 则使用默认的master。
如果外部数据包装器支持mpp_execute设置成'all', 它会实现一个Greenplum节点与数据匹配的策略。 为了不重复的从远处取数据,每个节点上的FDW必须能决定哪部分数据归它负责。 一个FDW会使用节点标识和节点数量去帮助做这个决定。 这面这段代码演示了一个外部数据包装器是怎样获取节点编号和节点数量的:
int segmentNumber = GpIdentity.segindex; int totalNumberOfSegments = getgpsegmentCount();
使用PGXS建立一个外部数据包装器扩展
将你用FDW API写的外部数据包装器函数编译成Greenplum数据库按需加载的一个或多个共享库。
你可以使用PostgreSQL扩展基础库(PGXS)为Greenplum数据库安装构建外部数据包装器的源代码。该框架自动化了简单模块的常用构建规则。如果你需要构建一个更复杂的用例,你必须写自己的构建系统。
使用PGXS基础库为你的FDW生成一个共享库,创建一个设置好PGXS相关变量的简单的Makefile。
例如:下面Makefile根据base_fdw_1.c和base_fdw_2.c这两个C的源文件在当前工作目录生成了一个名字叫base_fdw.so的共享库:
MODULE_big = base_fdw OBJS = base_fdw_1.o base_fdw_2.o PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) PG_CPPFLAGS = -I$(shell $(PG_CONFIG) --includedir) SHLIB_LINK = -L$(shell $(PG_CONFIG) --libdir) include $(PGXS)
下面是Makefile中用到的指令的描述:
- MODULE_big - 标识Makefile生成的共享库的基本名称。
- PG_CPPFLAGS - 将Greenplum数据库安装中的include/目录加到到编译器头文件的查找路径里。
- SHLIB_LINK 将Greenplum数据库安装中的库目录($GPHOME/lib/)加到链接查找路径里。
- PG_CONFIG和PGXS变量设置和include语句是必需的, 通常位于Makefile的最后三行。
要将外部数据包装器打包为Greenplum数据库扩展,可以创建脚本 (newfdw - version.sql)和控制 (newfdw.control)文件,这些文件注册FDW句柄和验证器函数,创建外部数据包装器并识别FDW共享库文件的特征。
示例名为base_fdw--1.0.sql的外部数据包装器扩展脚本样例:
CREATE FUNCTION base_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FUNCTION base_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FOREIGN DATA WRAPPER base_fdw HANDLER base_fdw_handler VALIDATOR base_fdw_validator;
示例名为base_fdw.control的FDW控制文件:
# base_fdw FDW extension comment = 'base foreign-data wrapper implementation; does not do much' default_version = '1.0' module_pathname = '$libdir/base_fdw' relocatable = true
把下面这些指令加到Makefile里, 可以指定FDW扩展控制文件的基本名称(EXTENSION) 和SQL脚本(DATA):
EXTENSION = base_fdw DATA = base_fdw--1.0.sql
在包含有这些命令的Makefile文件目录里运行make install 会将共享库、FDW SQL和控制文件拷贝到Greenplum数据库安装目录中($GPHOME)特定的或默认的位置。
部署注意事项
您必须以适合在Greenplum数据库群集中部署的形式打包FDW共享库和扩展文件。当你创建和部署安装包时,考虑一下因素:
- FDW共享库必须安装到集群中master节点和每个segment节点的相同位置。 在.control文件里指定这个路径。这个路径通常是$GPHOME/lib/postgresql/。
- FDW的.sql和.control文件必须安装到集群中 master节点和每个segment节点的$GPHOME/share/postgresql/extension/路径。
- gpadmin用户必须拥有访问整个FDW共享库和扩展文件的权限。