SQL/MED

来自 PostgreSQL 维基
跳转到导航跳转到搜索

SQL/MED 是外部数据管理,它是 SQL 标准的一部分,处理数据库管理系统如何集成存储在数据库外部的数据。SQL/MED 有两个组件:

外部表
一种对外部数据的透明访问方法
DATALINK
一种特殊的 SQL 类型,用于在数据库中存储 URL

当前状态

此规范的实现始于 PostgreSQL 8.4,并将随着时间的推移,为 PostgreSQL 引入强大的新功能。

基本功能已在 PostgreSQL 9.1 中合并。

  • 使外部数据包装器功能化
  • 支持外部表

contrib/file_fdw 可用于从服务器端文件检索外部数据。

查看所有 外部数据包装器 的列表

正在进行的活动工作

将 pgsql_fdw 添加为一个 contrib 模块

"pgsql_fdw contrib 模块" 正在 CF 2011-11 中提出。该提案的目标是将 pgsql_fdw 添加为一个 contrib 模块。

智能规划

  • 我们可能有外部数据的统计信息。ANALYZE 命令需要有钩子将行采样委派给每个 FDW。为此,"收集外部表的统计信息" 正在 CF 2011-11 中提出。该提案提供了一个处理程序函数,允许 FDW 处理针对外部表执行的 ANALYZE 命令。此外,contrib/file_fdw 增强为从实际数据文件获取样本行,并使用内核中的现有例程计算统计信息。
  • set_foreign_size_estimates() 必须增强以反映实际统计信息。

JOIN 下推

在远程端执行 JOIN(或 JOINs)将减少从外部服务器传输的数据量。

表分区

外部表应该支持继承和 表分区,以便进行横向扩展 集群。主父表被划分为多个外部表,每个外部表连接到不同的外部服务器。它可以用作 PL/Proxy 中的分区远程函数调用

连接缓存

目前,为了专注于 FDW API,尚未实现连接缓存。以下想法曾经实现过,但后来被删除了。

对外部服务器的连接在后端生命周期中被缓存并重复使用。当在 ExecInitForeignScan() 中初始化对外部表的扫描时,后端会在缓存中搜索可重复使用的连接。如果缓存中没有可重复使用的连接,则调用 FdwRoutine.ConnectServer() 创建一个新连接并将其存储在连接缓存中。

连接通过名称标识。连接的名称与连接使用的服务器的名称相同。

pg_foreign_connections 视图显示当前会话中可用的所有外部连接。

名称 类型 引用 描述
connname 文本 连接的名称
srvname 名称 pg_foreign_server.srvname 外部服务器的名称
usename 名称 pg_authid.rolname 用于映射外部用户的本地角色的名称
fdwname 名称 pg_foreign_data_wrapper.fdwname 用于连接到外部服务器的外部数据包装器的名称

已完成的工作

语法

在 SQL 标准中,'CREATE FOREIGN DATA WRAPPER' 有一个 'LIBRARY' 选项,FDW 例程直接从库中导出,但另一种类似于 'CREATE LANGUAGE' 的方法会更好,因为我们已经有 pg_proc,一个现有的函数管理器。

-- Register a function that returns FDW handler function set.
CREATE FUNCTION postgresql_fdw_handler() RETURNS fdw_handler
  AS 'MODULE_PATHNAME'
  LANGUAGE C;

-- Create a foreign data wrapper with FDW handler.
CREATE FOREIGN DATA WRAPPER postgresql
  HANDLER postgresql_fdw_handler
  VALIDATOR postgresql_fdw_validator;

CREATE FOREIGN DATA WRAPPER 现在有 HANDLER 子句,用于指定用于访问外部数据的处理程序函数。

-- Create a foreign server.
CREATE SERVER remote_postgresql_server
  FOREIGN DATA WRAPPER postgresql
  OPTIONS ( host 'somehost', port 5432, dbname 'remotedb' );

-- Create a user mapping.
CREATE USER MAPPING FOR postgres
  SERVER remote_postgresql_server
  OPTIONS ( user 'someuser', password 'secret' );

这两个语句没有改变。

-- Create a foreign table.
CREATE FOREIGN TABLE schemaname.tablename (
    column_name type_name [ OPTIONS ( ... ) ] [ NOT NULL ],
    ...
  )
  SERVER remote_postgresql_server
  OPTIONS ( ... );

外部表可以使用 OPTIONS 语法拥有通用选项。

在第一个版本中,省略了列 DEFAULT 值和列级选项,以简化补丁并使审核更容易。hackers-ML 存档

FDW 例程

版本 1

在 SQL 标准中,FDW 例程被设计为具有可移植的应用程序二进制接口。FDW 库可以在无需重新编译的情况下被多个 DBMS 使用,但这似乎不切实际。相反,PostgreSQL 特定的和 C 语言特定的例程集是可行的。

/* FDW interface routines */
typedef struct FdwRoutine
{
    FSConnection * (*ConnectServer)(ForeignServer *server, UserMapping *user);
    void           (*FreeFSConnection)(FSConnection *conn);
    void           (*EstimateCosts(ForeignPath *path, PlannerInfo *root, RelOptInfo *baserel);
    void           (*BeginScan)(ForeignScanState *scanstate);
    void           (*Open)(ForeignScanState *scanstate);
    void           (*Iterate)(ForeignScanState *scanstate);
    void           (*Close)(ForeignScanState *scanstate);
    void           (*ReOpen)(ForeignScanState *scanstate);
} FdwRoutine;

FDW 例程被设计为在执行器模块中使用。执行器似乎是用于查询优化和数据抽象的最平衡层。在其他方法(如 AM(访问方法)或存储管理器(smgr)层)中,优化复杂查询(如对同一外部服务器中的多个外部表进行 JOIN)将更加困难。

只有 FdwRoutine、FSConnection 的接口在 PostgreSQL 内核中定义,而实际内容由每个 FDW 库实现。

相比之下,ForeignServer 和 UserMapping 在内核中实现。

版本 2

根据讨论和 Heikki Linnakangas 的提案,FdwRoutine 在一些方面进行了更改。

  • 添加 FdwPlan 作为 FDW 特定规划信息的容器。
  • 添加 FdwExecutionState 作为 FD 特定执行信息的容器。
  • 连接管理留给每个 FDW,因为简单的 FDW(如文件包装器)不需要连接。
  • 添加规划器钩子,允许 FDW 从 RelOptInfo 和其他信息生成 FDW 特定的计划。该计划将传递给 BeginScan() 以执行扫描。
struct FdwPlan {
    NodeTag type;           /* FdwPlan need copyObject() support for plan
                               caching */
    char *explainInfo;      /* FDW-specific info shown in EXPLAIN VERBOSE */
    double startup_cost;    /* Optimizer needs costs for each path */
    double total_cost;
    List *private;          /* FDW can store private data as copy-able objects */
};

struct FdwExecutionState
{
    void *private;          /* FDW-private data */
};

struct FdwRoutine
{
#ifdef IN_THE_FUTURE
    FdwPlan *(*PlanNative)(Oid serverid, char *query);
    FdwPlan *(*PlanQuery)(PlannerInfo *root, Query query);
#endif
    FdwPlan *(*PlanRelScan)(Oid foreigntableid, PlannerInfo *root,
                            RelOptInfo *baserel);
    FdwExecutionState *(*BeginScan)(FdwPlan *plan, ParamListInfo params);
    void (*Iterate)(FdwExecutionState *state, TupleTableSlot *slot);
    void (*ReScan)(FdwExecutionState *state);
    void (*EndScan)(FdwExecutionState *state);
};

版本 3

最终 FDW API 已在 PostgreSQL 9.1 中定义如下。

typedef FdwPlan *(*PlanForeignScan_function) (Oid foreigntableid,
                                                          PlannerInfo *root,
                                                        RelOptInfo *baserel);

typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
                                                    struct ExplainState *es);

typedef void (*BeginForeignScan_function) (ForeignScanState *node,
                                                       int eflags);

typedef TupleTableSlot *(*IterateForeignScan_function) (ForeignScanState *node);

typedef void (*ReScanForeignScan_function) (ForeignScanState *node);

typedef void (*EndForeignScan_function) (ForeignScanState *node);

typedef struct FdwRoutine
{
    NodeTag     type;

    PlanForeignScan_function PlanForeignScan;
    ExplainForeignScan_function ExplainForeignScan;
    BeginForeignScan_function BeginForeignScan;
    IterateForeignScan_function IterateForeignScan;
    ReScanForeignScan_function ReScanForeignScan;
    EndForeignScan_function EndForeignScan;
} FdwRoutine;

将来,可能会添加更多规划器钩子以允许 FDW 优化查询。

版本 4

在 9.2 中,PlanForeignScan 发生了改变,以便 FDW 可以为每个外部表返回多个扫描路径,此更改消除了 FdwPlan。规划器从 FDW 提供的路径中选择适当的路径,并创建一个仅包含所选路径的 fdw_private 副本的 ForeignScan 节点。现在 PlanForeignScan 负责创建 ForeignScan 路径节点并将其添加到 RelOptInfo(baserel)中。您可以使用 create_foreignscan_path 来创建一个已完成的 ForeignScan 路径节点,该节点在 9.2 中也发生了改变。

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

不支持任何下推功能的 FDW 的 PlanForeignScan 将如下所示。

void
fooPlanForeignScan(Oid foreigntableid,
                   PlannerInfo *root,
                   RelOptInfo *baserel)
{
    double rows;
    Cost startup_cost, total_cost;
    List *fdw_private;

    /* Estimate # of rows returned by this scan */
    rows = ...;

    /* Estimate costs of this scan */
    startup_cost = ...;
    total_cost = ...;

    /* Store FDW-private information as copy-able objects */
    fdw_private = NIL;
    fdw_private = lappend(fdw_private, makeNode(...));
    ...

    /* Create path node and add it to baserel */
    add_path(baserel, (Path *)
             create_foreignscan_path(root, baserel,
                                     rows,          /* # of tuples in the table */
                                     startup_cost,  /* costs are required */
                                     total_costs,
                                     NIL,           /* no pathkeys */
                                     NULL,          /* no outer rel eigher */
                                     NIL,           /* no param clause */
                                     fdw_private));
}

在其他 FDW 函数中,fdw_private 通过 ForeignScanState 可用。

    List *fdw_private;

    fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;

磁盘上结构

pg_catalog.pg_foreign_data_wrapper

FDW 处理程序函数返回一个 FDW 例程集。添加了一个新的伪类型 'fdw_handler' 来表示例程集。FDW 处理程序不接受任何参数,并返回 fdw_handler 类型。

FDW 处理程序在 pg_foreign_data_wrapper 目录的 fdwhandler 列中注册。fdwhandler 为 InvalidOid 表示外部数据包装器没有 FDW 处理程序,因此它不能用于定义任何外部表。本规范支持外部数据包装器用作连接信息容器(如过去)的用法。

CREATE TABLE pg_catalog.pg_foreign_data_wrapper (
    fdwname      name      NOT NULL UNIQUE,
    fdwowner     oid       NOT NULL REFERENCES pg_authid (oid),
    fdwvalidator oid       NOT NULL REFERENCES pg_proc (oid),
    fdwhandler   oid       NOT NULL REFERENCES pg_proc (oid),
    fdwacl       aclitem[],
    fdwoptions   text[]
)
WITH OIDS;

pg_catalog.pg_foreign_table

外部表在 pg_class 中注册,relkind = 'f'(RELKIND_FOREIGN_TABLE)。它还有一个相应的 pg_foreign_table 元组,我们在其中存储外部服务器 ID 和外部表的通用选项。

CREATE TABLE pg_catalog.pg_foreign_table (
    ftrelid   oid    PRIMARY KEY REFERENCES pg_class (oid),
    ftserver  oid    NOT NULL REFERENCES pg_foreign_server (oid),
    ftoptions text[]
)
WITHOUT OIDS;

规划器和执行器更改

外部表的访问层将在规划器模块和执行器模块中实现。我们将为此目的使用新的 ForeignPath 和 ForeignScan 节点。

规划器

规划器模块负责找到最佳访问路径,因此 FDW 应该为 ForeignPath 提供成本。

在规划阶段,create_foreignscan_path() 为每个 ForeignScan 节点调用相关 FDW 的 FdwRoutine 的 PlanRelScan()。PlanRelScan() 应该为扫描提供适当的成本,这些成本已按照每个 FDW 想要使用的方式进行估算。

将来,可能会添加其他规划器钩子以实现以下功能:

  1. 直通模式(一个 ForeignScan 节点执行整个查询)
  2. 查询优化,例如将多个外部表合并为一个远程查询

为了尽可能准确地估计成本,FDW 可能希望拥有自己的统计信息。在此步骤中,我们没有提供存储统计信息的通用机制。一旦这样的机制被实现,FdwRoutine 应该具有另一个从 ANALYZE 调用函数。使用这样的函数,FDW 可以以各自的方式更新其统计信息。

在版本 1 中,计划器为查询中的每个外部表生成一个 ForeignScan 节点,并在其中存储由 PlanRelScan() 返回的 FdwPlan。

typedef struct ForeignScan
{
    Scan        scan;
    bool        fsSystemCol;
    struct FdwPlan *fdwplan;
} ForeignScan;

执行器

执行器模块通过调用 FDW 例程来执行 ForeignScan 节点。

ExecInitForeignScan()
为给定的 ForeignScan 计划节点创建 ForeignScanState。
如果执行不是为了 EXPLAIN,则使用存储在 ForeignScan 中的 FdwPlan 调用 FdwRoutine.BeginScan() 以启动外部查询,并接收 FdwExecutionState。
ExecForeignScan()
调用 FdwRoutine.Iterate() 通过 TupleTableSlot 从外部表检索元组。
如果扫描到达末尾,则在 Iterate() 调用后插槽将为空。
ExecForeignReScan()
调用 FdwRoutine.ReScan() 以重新初始化扫描。
ExecEndScan()
调用 FdwRoutine.EndScan() 以完成外部扫描。
ExecForeignMarkPos()/ExecForeignRestrPos()
目前,不支持 ForeignScan 的 MarkPos() 和 RestrPos(),因此 ExecSupportsMarkRestore() 对 ForeignScan 返回 false。不支持的原因是它们用于执行合并连接,而合并连接需要排序的结果。如果 FDW 可以将 Sort 节点正确地解析为 ORDER BY 子句,并支持 MarkPos() 和 RestrPos(),则支持外部表的合并连接。

ExecInitForeignScan() 从 ForeignScan 生成 ForeignScanState,FDW 例程使用它来管理扫描的状态。

typedef struct ForeignScanState
{
    ScanState       ss;
    struct FdwRoutine     *fdwroutine;
    void *fdw_state;
} ForeignScanState;

FdwExecutionState 具有私有区域,可用于在 FDW 例程之间传递特定于外部数据包装器的數據。每个外部数据包装器可以定义私有数据结构并将其存储到 ForeignScanState.fdw_state->private 中。

每列 FDW 选项

与其他类型的 FDW 对象类似,外部表的列可以具有 FDW 选项。这意味着 CREATE/ALTER FOREIGN TABLE 语法接受列的 OPTIONS 子句,键值对存储在 pg_attribute 的 attfdwoptions 中。

由于“DEFAULT b_expr”和“OPTIONS (...)”之间语法的模糊性,列的 OPTIONS 子句必须在任何约束或默认值之前指定。

外部数据包装器

file_fdw

file_fdw 是外部数据包装器实现,并作为 contrib 模块包含在 PostgreSQL 9.1 的分发版中。这可用于从服务器本地文件系统中的文件读取数据,类似于 `COPY FROM` 命令。目前,虽然 COPY FROM 允许 stdin,但它不受支持。

由于 FDW 从服务器端读取文件,因此应考虑一些安全问题。也许非超级用户不应该被允许创建或修改使用 file_fdw 的外部表。至少默认情况下。

使用 COPY FROM 例程

File_fdw 可以通过使用导出的 COPY FROM 例程识别 COPY 命令识别的文件格式。

通用选项

源文件的資訊,例如文件名,通过通用选项传递。COPY FROM 语句的选项是可以接受的,但是 file_fdw 不支持 `oids`,因为它是一个遗留功能。

与 COPY 不同的是,`force_not_null` 可以用布尔值(而不是列名列表)在每列通用选项中描述。

PostgreSQL

这可用于连接外部 PostgreSQL 服务器。它可能能够与 contrib/dblink 集成以共享代码和连接。dblink 将像标准 contrib 模块一样被选择性地安装。

连接选项

连接选项是从外部数据包装器、外部服务器和用户映射的 FDW 选项构建的,只选择连接选项,因为 FDW 选项可能包含非连接选项,例如 relname 和 nspname。请注意,由于安全问题,非超级用户必须在 FDW 选项中指定密码,并要求外部服务器进行密码身份验证。

在当前实现中,由于安全问题,具有 SUPERUSER 权限或相关 SERVER 上的 USAGE 权限的用户可以看到用户映射的 FDW 选项。

无事务管理

用于 PostgreSQL 的 FDW 绝不发出 BEGIN、ROLLBACK 和 COMMIT 等事务命令。因此,当 `autocommit` 设置为 `on` 时,所有 SQL 语句都在每个事务中执行。

成本估算

9.0 中不支持针对外部表的 ANALYZE,因此我们无法在本地 PG 中存储统计信息。一个解决方法是从远程服务器获取 EXPLAIN 结果,并使用其成本值进行本地规划。

SELECT 子句优化

目前 SELECT 子句被构造为“SELECT col1, col2, col3, ...”。如果原始查询中根本未使用某些列,则为了优化,它们将被替换为 NULL。例如,如果 col2 未使用,SELECT 子句将为“SELECT col1, NULL, col3, ...”。这种优化的主要目的是减少从远程服务器传输的数据量。

WHERE 子句下推

原始查询中的 WHERE 子句被 下推 到发送到外部服务器的重建查询中。

要下推条件,它必须仅包含以下节点类型。为此,我们检查 RelOptInfo.baserestrictinfo 列表中的每个元素。如果存在无法下推的条件,远程服务器将发送不包含条件的行,本地服务器将评估这些行并忽略不满足条件的行。

元素 标签名称 注意
常数值 Const
表列引用 Var
某种类型的数组 Array 表达式,例如“'{1, 2, 3}'"
外部参数 Param "外部" 表示 "Param.paramkind == PARAM_EXTERNAL"
布尔表达式 BoolExpr 表达式,例如“A AND B”、“A OR B”、“NOT A”
NULL 测试 NullTest 表达式,例如“IS [NOT] NULL”
运算符 OpExpr pg_operator.opcode 必须是 IMMUTABLE 函数
DISTINCT 运算符 DistinctExpr 表达式,例如“A IS DISTINCT FROM B”

pg_operator.opcode 必须是 IMMUTABLE 函数

标量数组运算符 ScalarArrayOpExpr 表达式,例如“ANY (...)”、“ALL (...)”

pg_operator.opcode 必须是 IMMUTABLE 函数

函数调用 FuncExpr 必须是 IMMUTABLE 函数

外部查询中不使用 ORDER BY、LIMIT、OFFSET、GROUP BY 或 HAVING。

检索结果元组

此 FDW 根据估计的结果行数切换检索结果元组的方法。

如果估计的行数小于阈值,则在 Begin() 或 ReScan() 之后,在第一次调用 Iterate() 时使用简单的 SELECT 一次检索所有结果。否则,将在该位置创建 SQL 级游标,并在需要时检索结果行。

可以使用 SERVER 和/或 FOREIGN TABLE 的 FDW 选项配置两个数字,即使用游标的最小行数和一次 FETCH 调用中提取的行数。如果在两个对象上都指定了选项,则后一个选项会覆盖前一个选项。

我们必须确保在任何情况下都显式释放 PGresult,因为 libpq 使用 malloc 而不是 palloc。将结果复制到 Tuplestorestate 是一个解决方案,它在 contrib/dblink 中使用,但它在复制过程中需要额外的内存,并且会产生一些开销。另一个解决方案是将清理函数注册到资源所有者,并在该清理函数中释放 PGresult。此方法已用于关闭 libpq 连接。

悬而未决的问题

FDW 设计和实现中仍然存在一些问题

我们应该从哪里导出外部连接管理函数?
目前 `DISCARD ALL` 会断开所有连接,但我们可能会提供 SQL 函数来管理每个外部连接。我们可以从核心导出这些函数,例如 pg_connect()/pg_disconnect(),或者如果它们是可选的,则继续使用 contrib/dblink。

已解决的问题

pg_foreign_table.ftoptions 与 pg_class.reloptions
我们可以将 ftserver 和 ftoptions 存储到 pg_class 中的某些字段中,例如 relam 和 reloptions,因为我们可能不会将这些字段用于外部表。
FdwRoutine 与 SETOF 记录函数
一些 fdw 例程类似于 SETOF 记录函数。我们可以将它们合并或共享一些内部例程。但是,似乎很难使用 SRF 来代替 FdwRoutine,因为 FDW 需要支持一些实用程序函数;连接、断开连接、处理 WHERE 条件等。
fdw_handler 与类似 pg_am 的函数表
FDW 例程需要一组函数。fdw_handler 可以将这些函数打包在类似 C++ 的接口中。但是,我们有用于索引访问方法的 pg_am,它是一种基于表的做法。请注意,我们可能需要使用 C 编写 fdw 例程,因为它访问执行器对象以提取表达式。
哪个用户标识符适合确定 USER MAPPING?
当前实现使用 OuterUserId 而不是 CurrentUserId 来确定 USER MAPPING。因为 OuterUserId 是用户使用 SET ROLE 或 SET SESSION AUTHORIZATOIN 显式指定的角色,而 CurrentUserId 在执行使用 SECURITY DEFINER 选项创建的函数时会隐式更改。用户可能不希望通过 SECURITY-DEFINER 函数访问外部表使用与函数所有者相关的 USER MAPPING。这是否是一个适当的规范?
锁定外部表
目前,外部表只能以 ACCESS SHARE 模式锁定,因为只能对外部表授予 SELECT 权限。在普通表情况下,至少需要 INSERT/UPDATE/DELETE 权限之一才能以其他模式锁定。如果目标是外部服务器,我们是否应该放松限制?我们必须考虑通过表继承进行递归锁定。
在 9.1 中,不支持锁定外部表。

支持的功能

DDL

  • ALTER FOREIGN DATA WRAPPER name {HANDLER name|NO HANDLER}
  • CREATE FOREIGN TABLE name INHERITS (parent)
    • 继承普通关系(也支持 tableoid 系统属性)
  • DROP FOREIGN TABLE
  • ALTER FOREIGN TABLE name RENAME TO newname
  • ALTER FOREIGN TABLE name RENAME COLUMN column TO newname
  • ALTER FOREIGN TABLE name {ADD|DROP} column
  • ALTER FOREIGN TABLE name {ADD|DROP} constraint
    • 仅支持 NOT NULL 和 CHECK 约束。
  • ALTER FOREIGN TABLE name OWNER TO owner
  • {GRANT|REVOKE} SELECT [(column list)] ON FOREIGN TABLE name {TO|FROM} user
    • 以下语法也有效
      • {GRANT|REVOKE} SELECT [(column list)] ON name {TO|FROM} user
      • {GRANT|REVOKE} SELECT [(column list)] ON TABLE name {TO|FROM} user
  • CREATE RULE ... TO foreign_table
  • COMMENT ON FOREIGN TABLE name IS 'table comment'
  • COMMENT ON COLUMN name.column IS 'column comment'

DML

  • 使用 SELECT 语句
    • 多个外部数据包装器
    • 多个外部服务器
    • 多个外部表(JOIN、UNION、子查询等)
    • 带有参数的 PREPARE/EXECUTE 语句
  • 拒绝执行外部表的 INSERT/UPDATE/DELETE
  • 拒绝执行外部表的 VACUUM/TRUNCATE/CLUSTER
  • 锁定外部表及其子级
支持 tableoid 系统列
要使外部表支持继承,外部表的元组应提供 tableoid 列。

pg_dump

  • 转储外部表的架构(定义)
    • 不转储外部表的内容,因为它们不是数据库的一部分
  • 转储带有 HANDLER 规范的外部数据包装器
  • 转储外部数据包装器、服务器和用户映射,不包括内置对象

未来改进

一般

智能规划
ANALYZE 命令可以通过添加 FdwRoutine Analyze(tableoid 或 tablename) 来更新外部表的 pg_statistic 和 pg_class(reltuples 和 relpages)的一部分,该函数返回外部表的 pg_statistic 记录。
即使数据定义和内容相同,访问外部数据的成本也会与访问本地数据的成本不同。GENERIC OPTION(如 `cost_factor`)允许告知计划器开销。

针对基于 SQL 的 FDW

同一个服务器中两个外部表的 JOIN 操作
它们可以合并成一个 ForeignScan,以便外部服务器可以在本地 JOIN 后返回结果。
优化 SELECT 语句
某些外部扫描只需要一部分列。在这样的扫描中,不需要的列可以从 SELECT 语句中省略。
支持内部参数
某些类型的计划,例如嵌套循环,会生成内部参数,用于将值从父节点传递到子节点。通过将内部参数应用于外部查询,可以减少从外部服务器获取的记录数量。
这在某些情况下似乎很困难,因为内部参数的值是在从关系中获取元组 **之后** 确定的。
优化参数
某些外部扫描只使用 EXECUTE 语句的一部分参数。未使用的参数可以从 PQexecParams() 的参数中省略。参数可以通过二进制格式传递,以避免文本和二进制之间的转换。
支持大结果集的游标模式
目前 libpq 不支持协议级游标,因此 PostgreSQL 的 FDW 通过 PQexecParams() 直接执行 SELECT 语句,并一次性检索所有元组。如果支持参数化游标,PostgreSQL 的 FDW 将能够一次检索一部分结果,以改善响应速度。
下推包含 CURRENT_TIMESTAMP 的 WHERE 子句
类似 pgpool 重写查询,或用表示 CURRENT_TIMESTAMP 结果的 Const 节点替换 FuncExpr 节点。

SQL 符合性

SQL 标准中的外部表功能
标识符 描述 状态
M004 外部数据支持
M005 外部模式支持
M006 GetSQLString 例程
M007 TransmitRequest
M009 GetOpts 和 GetStatistics 例程
M010 外部数据包装器支持
M018 Ada 中的外部数据包装器接口例程 (未计划)
M019 C 中的外部数据包装器接口例程
M020 COBOL 中的外部数据包装器接口例程 (未计划)
M021 Fortran 中的外部数据包装器接口例程 (未计划)
M022 MUMPS 中的外部数据包装器接口例程 (未计划)
M023 Pascal 中的外部数据包装器接口例程 (未计划)
M024 PL/I 中的外部数据包装器接口例程 (未计划)
M030 SQL 服务器外部数据支持
M031 外部数据包装器通用例程
FDW 的错误代码
代码 含义
HV000 FDW 特定条件
HV001 内存分配错误
HV002 需要动态参数值
HV004 无效数据类型
HV005 未找到列名
HV006 无效数据类型描述符
HV007 无效列名
HV008 无效列号
HV009 无效的空指针使用
HV00A 无效的字符串格式
HV00B 无效句柄
HV00C 无效选项索引
HV00D 无效选项名称
HV00J 未找到选项名称
HV00K 回复句柄
HV00L 无法创建执行
HV00M 无法创建回复
HV00N 无法建立连接
HV00P 没有模式
HV00Q 未找到模式
HV00R 未找到表
HV010 函数序列错误
HV014 句柄数量限制超过
HV021 描述符信息不一致
HV024 无效属性值
HV090 无效的字符串长度或缓冲区长度
HV091 无效的描述符字段标识符
0X000 无效的外部服务器规范
0Y000 直通特定条件
0Y001 无效的游标选项
0Y002 无效的游标分配