内核执行 Babelfish 上下文需要考虑的问题


难度 中等

概述

SQL Server 和 PG 本身在数据库架构层面 schema 的含义就不一致:

维度 SQL Server PostgreSQL
命名空间 三级:Database → Schema → Object 两级:Schema → Object
逻辑数据库 有(SQL Server 数据库) 无(PG 直接用 schema 组织)
端口区分 Babelfish 用端口区分模式
连接类型 TDS 协议 PG 协议 + TDS 协议

Babelfish 使用端口来和 PG 模式进行区分,因而 Babelfish 里面的很多逻辑都是和端口强绑定,即 MyProcPort

同时根据客户端连接方式的不同:

  • PG-TSQL:psql 连接 Babelfish 端口(5432),走 PG 协议但设置 sql_dialect = 'tsql'
  • TDS:sqlcmd/SSMS 连接 Babelfish 端口(1433),走 TDS 协议

两者的内部执行路径不完全一致,这对 DDL 同步有重要影响。


Schema 映射问题

Multi-DB 和 Single-DB

Babelfish 支持两种部署模式:

Multi-DB 模式(默认)

┌─────────────────────────────────────────────┐
│ PostgreSQL Cluster                          │
│                                             │
│  ┌─────────────┐  ┌─────────────┐          │
│  │ Database:   │  │ Database:   │          │
│  │ myapp       │  │ sales       │          │
│  │             │  │             │          │
│  │  Schema:    │  │  Schema:    │          │
│  │  dbo        │  │  dbo        │          │
│  │  sys        │  │  sys        │          │
│  └─────────────┘  └─────────────┘          │
│         │                │                 │
│         └────────────────┴──────► Babelfish│
│         映射到不同的物理 PG database        │
└─────────────────────────────────────────────┘

Single-DB 模式

┌─────────────────────────────────────────────┐
│ PostgreSQL Cluster (single database)        │
│                                             │
│  ┌─────────────────────────────────────┐   │
│  │ Database: postgres                   │   │
│  │                                     │   │
│  │  Schema (logical):      Physical:   │   │
│  │  dbo                →   public      │   │
│  │  sys                →   sys         │   │
│  │  INFORMATION_SCHEMA → information_  │   │
│  └─────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

关键差异

特性 Multi-DB Single-DB
数据库隔离 强(不同物理 DB) 弱(共用一个 DB)
CREATE DATABASE 支持 不支持
跨库查询 不支持 支持(通过 schema 区分)
迁移复杂度
DDL 捕获时的 current db 必需 可忽略

逻辑数据库和物理数据库

Babelfish 维护一套逻辑数据库名到物理数据库名的映射:

// babelfishpg_tsql/src/multidb.c

typedef struct {
    const char *logical_db_name;     // 逻辑名:myapp, sales
    const char *physical_db_name;     // 物理名:myapp, sales (multi-db) 或 postgres (single-db)
    Oid         db_id;               // 数据库 OID
    bool        is_plpgsql Trusted;   // 是否有 plpgsql 扩展
} LogicalDBEntry;

// 查询逻辑数据库
LogicalDBEntry *
get_logical_db_entry(const char *logical_name)
{
    // 在系统表中查找映射
}

// 获取当前逻辑数据库
const char *
get_current_logical_database_name(void)
{
    // 返回当前连接的逻辑数据库名
}

DDL 同步时的问题

-- 发布端执行 (连接到 logical DB: myapp)
CREATE TABLE myapp.dbo.t1 (id INT);

-- 捕获的 DDL SQL 可能是:
-- 1. 包含数据库名:CREATE TABLE myapp.dbo.t1 ...
-- 2. 不包含数据库名:CREATE TABLE dbo.t1 ...
-- 3. 使用物理名:CREATE TABLE public.t1 ...

Current DB Name

在 DDL 执行时,Babelfish 需要知道”当前是哪个逻辑数据库”:

// 获取当前数据库上下文
static char *
get_current_db_name(void)
{
    // 优先从 session 变量获取
    const char *val = get_config_option("babelfishpg_tsql.database_name", false, true);
    if (val)
        return pstrdup(val);

    // 回退到 catalog 查询
    Oid db_oid = MyDatabaseId;
    return get_logical_database_name(db_oid);
}

对于 apply worker 的挑战

apply worker 连接的是物理 PG 数据库,但 DDL SQL 可能是逻辑数据库名:

-- 发布端在 myapp.dbo 创建表
CREATE TABLE dbo.t1 (id INT);

-- 订阅端 apply worker 连接的是物理 DB
-- 需要知道这个 dbo 对应哪个物理 schema

Schema Mapping

Babelfish 维护逻辑 schema 到物理 schema 的映射:

Multi-DB 模式

逻辑 Schema 物理 Schema
dbo public
sys sys
INFORMATION_SCHEMA information_schema

Single-DB 模式

逻辑 Schema 物理 Schema
dbo dbo
sys sys
INFORMATION_SCHEMA INFORMATION_SCHEMA

映射查询函数

// babelfishpg_tsql/src/multidb.c

// 将逻辑 schema 名转换为物理 schema 名
char *
get_physical_schema_name(const char *logical_schema)
{
    // 查询 sys.babelfish_namespace 表
    // 返回物理 schema 名
}

// 将物理 schema 名转换为逻辑 schema 名
char *
get_logical_schema_name(const char *physical_schema)
{
    // 反向查询
}

// 完全解析表引用(database.schema.table)
void
rewrite_object_name(ObjectName *name)
{
    // 如果没有指定 database,使用当前逻辑数据库
    // 如果没有指定 schema,使用 dbo
    // 然后查找物理映射
}

DDL 同步时的 schema 处理

-- 发布端 DDL
CREATE TABLE dbo.t1 (
    id INT PRIMARY KEY,
    name NVARCHAR(100)
);

-- 捕获时需要保存的信息:
{
    "ddl_sql": "CREATE TABLE dbo.t1 ...",
    "search_path": "dbo,pg_catalog",  -- 发布端的 search_path
    "current_db": "myapp",            -- 当前逻辑数据库
    "target_schema": "public"          -- 物理 schema(multi-db)
}

PG-TSQL 和 TDS

PG-TSQL

PG-TSQL 是通过 PostgreSQL 协议连接 Babelfish,但设置 sql_dialect = 'tsql'

客户端 (psql)
     │
     │ PG 协议
     ▼
Babelfish 端口 (5432)
     │
     ├──► 解析 PG 协议数据
     │
     ├──► 设置 sql_dialect = 'tsql'(session 变量)
     │
     ▼
PG-TSQL 执行路径
     │
     ├──► PG parser (使用 T-SQL 语法规则)
     ├──► bbf_ProcessUtility() hook
     └──► T-SQL 兼容函数

PG-TSQL 的特点

  • 仍然是 PG 连接,MyProcPort->proc 是 PG 的
  • Is_TSQL_CLIENT() 可能返回 false(取决于实现)
  • 适合:已存在 PG 工具/脚本,渐进式迁移

代码判断

// babelfishpg_tsql/src/pl_handler.c

bool
IsTDSSprotocol(void)
{
    // 检查是否是 TDS 协议连接
    // 实际上是检查 MyProcPort->hints 中是否有 TDS 相关标记
}

bool
isTDSConnection(void)
{
    // 更直接的检查方式
    // 检查连接是否是 Babelfish TDS 端口 (1433)
}

TDS

TDS (Tabular Data Stream) 是 SQL Server 的原生协议:

客户端 (sqlcmd, SSMS)
     │
     │ TDS 协议
     ▼
Babelfish TDS 端口 (1433)
     │
     ├──► TDS 协议解析
     ├──► 设置 sql_dialect = 'tsql'
     └──► 完全 T-SQL 兼容层

TDS 的特点

  • 完全 T-SQL 兼容
  • 支持 SQL Server 特有语法
  • Is_TSQL_CLIENT() 返回 true
  • 支持 sp_ 存储过程

PG-TSQL 和 TDS 的执行差异

操作 PG-TSQL TDS
ALTER VIEW ❌ 报错 ✅ 支持
CREATE TRIGGER ⚠️ 有限支持 ✅ 支持
SELECT INTO ⚠️ 有限支持 ✅ 支持
IDENTITY ✅ 支持 ✅ 支持
NVARCHAR(MAX) ✅ 支持 ✅ 支持
SET NOCOUNT ON ❌ 不识别 ✅ 支持

Apply Worker Replay DDL

发布端转换 TSQL 为 PG SQL 发送

发布端捕获 DDL 时,DDL SQL 是用户输入的 T-SQL 语法。方案一是将 T-SQL DDL 转换为 PG DDL 再发送:

转换类型

T-SQL 类型 转换规则 PG 结果
INT IDENTITY(1,1) SERIAL 自增序列
NVARCHAR(MAX) TEXT 文本类型
DATETIME2 TIMESTAMP 时间戳
BIT BOOLEAN 布尔类型
[dbo].[t1] public.t1 Schema 映射
DROP TABLE t1 DROP TABLE IF EXISTS t1 安全删除

转换代码结构

// postgresql-3/src/backend/tcop/utility.c

typedef struct {
    const char *tsql_type;      // T-SQL 类型名
    const char *pg_type;         // PG 等价类型
    bool        need_rewrite;    // 是否需要 rewrite
} TSQLtoPGMapping;

// 核心转换函数
static char *
rewrite_tsql_ddl(const char *tsql_ddl, const RewriteContext *ctx)
{
    // 1. 解析 T-SQL DDL AST
    List       *stmts = babelfishpg_tsql_raw_parser(tsql_ddl);

    // 2. 遍历节点,转换类型引用
    for (each node in stmts) {
        rewrite_node(node, ctx);
    }

    // 3. 生成 PG SQL
    return deparse_stmt(stmts);
}

Apply Worker 获取 Babelfish 上下文

Apply worker 是 PG 连接,要执行 Babelfish DDL 需要获取正确的上下文:

需要的上下文信息

typedef struct {
    char       *current_database;      // 当前逻辑数据库名
    char       *current_schema;       // 当前 schema (通常是 dbo)
    char       *search_path;          // 发布端捕获时的 search_path
    Oid         current_db_oid;       // 物理数据库 OID
    Oid         target_schema_oid;     // 目标物理 schema OID
} BabelfishContext;

获取方式

// 1. 从捕获的 DDL 消息中获取
static BabelfishContext *
extract_bbf_context(const DDLMessage *msg)
{
    BabelfishContext *ctx = palloc0(sizeof(BabelfishContext));

    // 从 pg_publication_sync 消息中解析
    ctx->current_database = msg->database_name;  // 捕获时保存
    ctx->search_path = msg->search_path;         // 捕获时保存

    // 查询物理映射
    ctx->target_schema_oid =
        get_physical_schema_oid(ctx->current_database, "dbo");

    return ctx;
}

// 2. 在 apply worker 中设置上下文
static void
setup_bbf_context(BabelfishContext *ctx)
{
    // 设置当前逻辑数据库
    set_config_option("babelfishpg_tsql.database_name",
                     ctx->current_database,
                     PGC_SUSET, PGC_S_SESSION);

    // 设置 search_path
    set_config_option("search_path",
                     ctx->search_path,
                     PGC_SUSET, PGC_S_SESSION);

    // 设置 dialect
    set_config_option("babelfishpg_tsql.sql_dialect", "tsql",
                     PGC_SUSET, PGC_S_SESSION);
}

Schema Mapping

Apply worker 需要正确解析 schema 引用:

// 解析 DDL SQL 中的 schema 引用
static void
resolve_schema_references(BabelfishContext *ctx, Node *stmt)
{
    if (IsA(stmt, CreateStmt)) {
        CreateStmt *create = (CreateStmt *)stmt;

        // 处理表名
        resolve_rangevar(create->relation, ctx);

        // 处理列类型
        foreach(col, create->tableElts) {
            if (IsA(l, ColumnDef)) {
                ColumnDef *col = (ColumnDef *)l;
                col->typeName = rewrite_type_name(col->typeName, ctx);
            }
        }
    }
}

PG-TSQL Parse

使用 PG 解析器配合 T-SQL dialect:

// 在 PG-TSQL 模式下解析 DDL
static List *
parse_ddl_as_pgtsql(const char *ddl_sql)
{
    // 设置 dialect
    set_config_option("babelfishpg_tsql.sql_dialect", "tsql", ...);

    // 使用 PG parser 解析 T-SQL 语法
    List *stmts = pg_parse_query(ddl_sql);

    // 恢复
    set_config_option("babelfishpg_tsql.sql_dialect", "postgres", ...);

    return stmts;
}

问题:PG parser 只能解析 PG 语法,不能解析纯 T-SQL 语法。对于复杂的 T-SQL DDL 可能会解析失败。

TDS Parse

使用 Babelfish 的 T-SQL 解析器:

// 使用 T-SQL 原生解析器
static List *
parse_ddl_as_tsql(const char *ddl_sql)
{
    // T-SQL parser
    List *stmts = babelfishpg_tsql_raw_parser(ddl_sql, RAW_PARSE_DEFAULT);
    return stmts;
}

问题:T-SQL 解析器输出的 AST 是 PG 节点,但某些 T-SQL 特有的语义信息可能丢失。

Worker 创建 TDS 连接来执行 TSQL

这是方案三的核心,通过 libpq 建立 TDS 连接执行 DDL:

核心实现

// worker.c 中添加 TDS 连接执行路径

static void
execute_ddl_via_tsql_connection(const char *ddl_sql, BabelfishContext *ctx)
{
    PGconn *tsql_conn;

    // 1. 建立 TDS 连接
    tsql_conn = establish_tsql_connection(ctx->current_database);
    if (!tsql_conn) {
        ereport(ERROR, "Failed to establish TDS connection");
    }

    // 2. 设置 session 选项(与发布端一致)
    set_session_config(tsql_conn, ctx);

    // 3. 执行 DDL
    PGresult *res = PQexec(tsql_conn, ddl_sql);

    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
        // 记录错误
        ereport(ERROR,
            errmsg("DDL execution failed on subscriber"),
            errdetail("%s", PQerrorMessage(tsql_conn)));
    }

    PQclear(res);
    PQfinish(tsql_conn);
}

static PGconn *
establish_tsql_connection(const char *database)
{
    char        conninfo[256];
    PGconn     *conn;

    // 连接到本地 Babelfish TDS 端口
    snprintf(conninfo, sizeof(conninfo),
             "host=localhost port=1433 dbname=%s "
             "user=%s password=%s",
             database,
             babelfish_user,    // 订阅端配置的 Babelfish 用户
             babelfish_pass);

    conn = PQconnectdb(conninfo);

    if (PQstatus(conn) != CONNECTION_OK) {
        PQfinish(conn);
        return NULL;
    }

    return conn;
}

数据一致性

两阶段提交问题

Apply Worker 执行时序:

┌─────────────────────────────────────────────────────────────┐
│ PG 连接 (执行 DML)                                          │
│ BEGIN;                                                     │
│   INSERT INTO t1 VALUES (1);  ───► 已提交                  │
│ COMMIT;                                                     │
│                                                             │
│                        ┌───────────────────────────────────┤
│                        │ T-SQL 连接 (执行 DDL)              │
│                        │ BEGIN;                             │
│                        │   ALTER TABLE t1 ADD col2 INT;   │
│                        │                                    │
│                        │   ⚠️ 如果失败?                    │
│                        │   ROLLBACK;                        │
│                        └───────────────────────────────────┘
│                                                             │
│ 问题:INSERT 已提交,ALTER TABLE 回滚 → 数据不一致            │
└─────────────────────────────────────────────────────────────┘

解决方案:同步屏障

// DDL 执行前的同步机制
static void
execute_ddl_with_barrier(const char *ddl_sql, BabelfishContext *ctx)
{
    // 1. 暂停 DML 处理
    pause_dml_processing();

    // 2. 等待所有 pending DML 完成
    wait_for_pending_dml();

    // 3. 确保没有活跃的事务在引用目标表
    wait_for_no_active_txns_on_table(ctx->target_relation);

    // 4. 执行 DDL
    begin_synchronized_transaction();
    execute_ddl_via_tsql_connection(ddl_sql, ctx);
    commit_synchronized_transaction();

    // 5. 恢复 DML 处理
    resume_dml_processing();
}

连接池优化

// T-SQL 连接池
typedef struct {
    PGconn         *conn;
    char           *database;
    pthread_mutex_t mutex;
    time_t          last_used;
    int             use_count;
} TSQLConnectionPool;

// 全局连接池
static TSQLConnectionPool tsql_pool = {
    .conn = NULL,
    .mutex = PTHREAD_MUTEX_INITIALIZER,
    .last_used = 0,
    .use_count = 0
};

static PGconn *
get_tsql_connection(const char *database)
{
    pthread_mutex_lock(&tsql_pool.mutex);

    // 检查现有连接
    if (tsql_pool.conn != NULL) {
        if (strcmp(tsql_pool.database, database) == 0 &&
            PQstatus(tsql_pool.conn) == CONNECTION_OK) {

            // 检查连接是否健康
            if (PQping(tsql_pool.conn) == PQPING_OK) {
                tsql_pool.last_used = time(NULL);
                tsql_pool.use_count++;
                pthread_mutex_unlock(&tsql_pool.mutex);
                return tsql_pool.conn;
            }
        }

        // 连接不健康,关闭
        PQfinish(tsql_pool.conn);
        tsql_pool.conn = NULL;
    }

    // 建立新连接
    tsql_pool.conn = establish_tsql_connection(database);
    tsql_pool.database = pstrdup(database);
    tsql_pool.last_used = time(NULL);
    tsql_pool.use_count = 1;

    pthread_mutex_unlock(&tsql_pool.mutex);

    return tsql_pool.conn;
}

// 定期清理连接池
static void
cleanup_tsql_connection_pool(void)
{
    pthread_mutex_lock(&tsql_pool.mutex);

    if (tsql_pool.conn != NULL) {
        time_t idle_time = time(NULL) - tsql_pool.last_used;

        // 超过 5 分钟空闲或使用次数过多,关闭连接
        if (idle_time > 300 || tsql_pool.use_count > 100) {
            PQfinish(tsql_pool.conn);
            tsql_pool.conn = NULL;
            pfree(tsql_pool.database);
        }
    }

    pthread_mutex_unlock(&tsql_pool.mutex);
}

PostgreSQL Hook 机制与内核-插件关系

PostgreSQL 的 Hook 架构原则

PostgreSQL 的核心设计原则之一是内核不依赖任何插件,而是通过 hook 机制让插件扩展内核功能:

┌─────────────────────────────────────────────────────────────────┐
│                    PostgreSQL 内核                                │
│                                                                 │
│  src/backend/tcop/utility.c                                     │
│       │                                                         │
│       ▼                                                         │
│  ProcessUtility()                                               │
│       │                                                         │
│       ├──► if (ProcessUtility_hook)                            │
│       │         ProcessUtility_hook(pstate, pstmt, ...)        │
│       │              │                                          │
│       │              ▼                                          │
│       │         [插件注册的 hook 函数]                           │
│       │              │                                          │
│       │              ├──► Babelfish: bbf_ProcessUtility()      │
│       │              └──► 其他插件: their_custom_hook()        │
│       │                                                         │
│       ▼                                                         │
│  standard_ProcessUtility()  ◄── 内核默认实现                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

关键点:
1. 内核定义 hook 类型和调用点
2. 插件注册 hook 函数到内核
3. 内核调用 hook 时不知道具体是哪个插件
4. 插件不能直接调用内核私有函数

PostgreSQL 主要 Hook 类型

Hook 类型 定义位置 用途
ProcessUtility_hook tcop/utility.h DDL/DML 语句执行
ExecutorStart_hook executor.h 执行器启动
ExecutorRun_hook executor.h 执行器运行
planner_hook optimizer/planner.h 查询规划
object_access_hook catalog/objectaccess.h 对象访问控制
shmem_request_hook miscadmin.h 共享内存请求
post_parse_analyze_hook parser/analyze.h 解析后分析

Babelfish 注册的 Hook 一览

InstallExtendedHooks() 中注册

// babelfishpg_tsql/src/hooks.c

static void
InstallExtendedHooks(void)
{
    // Executor hooks
    ExecutorStart_hook = pltsql_ExecutorStart;
    ExecutorRun_hook = pltsql_ExecutorRun;
    ExecutorFinish_hook = pltsql_ExecutorFinish;
    ExecutorEnd_hook = pltsql_ExecutorEnd;

    // Planner hooks
    planner_hook = pltsql_planner_hook;

    // Parser hooks
    core_yylex_hook = pgtsql_core_yylex;
    pre_transform_returning_hook = handle_returning_qualifiers;
    // ... 更多 parser hooks

    // Object access hook
    object_access_hook = bbf_object_access_hook;

    // Replication hook
    logicalrep_modify_slot_hook = logicalrep_modify_slot;

    // 其他
    GetNewObjectId_hook = pltsql_GetNewObjectId;
}

pl_handler.c 中注册的 Hook

// babelfishpg_tsql/src/pl_handler.c

ProcessUtility_hook = bbf_ProcessUtility;
relname_lookup_hook = bbf_table_var_lookup;
pre_parse_analyze_hook = ...;
post_parse_analyze_hook = ...;
// TSQL 方言特定的 hooks

Babelfish 自定义 Hook 类型

// babelfishpg_tsql/src/hooks.h

// Babelfish 特有的 hook 类型
typedef bool (*bbfCustomProcessUtility_hook_type)(ParseState *pstate,
    PlannedStmt *pstmt, const char *queryString,
    ProcessUtilityContext context, ParamListInfo params,
    QueryCompletion *qc);

// table variable 相关
typedef bool (*table_variable_satisfies_visibility_hook_type)(...);
typedef TM_Result (*table_variable_satisfies_update_hook_type)(...);

// ...

内核与插件的依赖关系图

正确的关系:内核定义 hook,插件注册

┌────────────────────────────────────────────────────────────────┐
│                     PostgreSQL 内核                             │
│                                                                │
│  定义 hook 类型 (例如 ProcessUtility_hook_type)                 │
│  定义 hook 调用点 (例如 ProcessUtility() 中的 if (hook) 调用)  │
│  提供公共 API (set_config_option, get_config_option 等)        │
│                                                                │
└────────────────────────┬───────────────────────────────────────┘
                       │ 定义 + 调用
                       ▼
┌────────────────────────────────────────────────────────────────┐
│                     Babelfish 插件                              │
│                                                                │
│  实现 hook 函数 (bbf_ProcessUtility, bbf_custom_process_...)   │
│  注册到内核 (ExecutorStart_hook = pltsql_ExecutorStart)        │
│  使用内核公共 API (不能调用内核内部函数)                         │
│                                                                │
└────────────────────────────────────────────────────────────────┘

错误的关系:内核直接调用插件函数

┌────────────────────────────────────────────────────────────────┐
│                     PostgreSQL 内核                             │
│  ✗ 直接调用 babelfishpg_tsql 的某个 extern 函数                │
│  ✗ #include "babelfishpg_tsql.h"                              │
│  ✗ 依赖 Babelfish 特定的数据结构                               │
└────────────────────────────────────────────────────────────────┘

方案二为何违反了这个原则

方案二试图在 apply worker 中直接调用 Babelfish 的接口:

// 方案二的问题代码 - worker.c

// 问题 1:直接调用 Babelfish 的 extern 函数
parsetree_list = babelfishpg_tsql_raw_parser(ddl_sql, RAW_PARSE_DEFAULT);

// 问题 2:假设 Babelfish 存在,内核编译时需要 Babelfish
// 这意味着:如果没有 Babelfish,PG 内核无法编译

// 问题 3:假设特定的 hook 函数存在并可用
set_config_option("babelfishpg_tsql.sql_dialect", "tsql", ...);

这违反了以下原则

原则 违反原因
内核不依赖插件 内核代码中直接调用 babelfishpg_tsql_raw_parser()
插件化架构 apply worker 逻辑中硬编码了 Babelfish 判断
可插拔 PG 编译需要 Babelfish 代码存在

内核能否调用 Babelfish 暴露的接口?

Babelfish 暴露的公共接口(在 hooks.h 中用 extern 声明):

// babelfishpg_tsql/src/hooks.h

// 安装/卸载 hooks
extern void InstallExtendedHooks(void);
extern void UninstallExtendedHooks(void);

// 函数参数列表
extern char *gen_func_arg_list(Oid objectId);

// 类型转换
extern Datum pltsql_exec_tsql_cast_value(Datum value, bool *isnull, ...);

// 视图绑定
extern bool handle_bbf_view_binding_on_object_drop(...);

问题是:即使这些是 extern 函数,内核代码也不应该直接调用它们,因为:

  1. 运行时绑定:hook 函数通过函数指针调用,内核不需要知道具体函数
  2. 可选插件:如果没有安装 Babelfish,这些函数不存在
  3. 版本兼容:不同版本的 Babelfish 可能提供不同的接口

正确的做法:通过 Hook 机制

正确的架构应该是

Apply Worker 执行 DDL
         │
         ▼
┌─────────────────────────┐
│  ProcessUtility()        │
│  (内核公共入口)           │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  ProcessUtility_hook    │
│  (如果有插件注册)        │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  bbf_ProcessUtility()   │
│  (Babelfish hook 函数)   │
└─────────────────────────┘

对于 apply worker,需要考虑的是

  1. apply worker 执行 DDL 时,ProcessUtility_hook 会被调用吗?

    • 答案:。apply worker 执行 DDL 就是调用 ProcessUtility()
    • 如果 Babelfish 安装并注册了 ProcessUtility_hook,hook 会被触发
  2. apply worker 能否获得正确的 Babelfish 上下文?

    • 答案:可以,但需要正确设置 session 变量
    • 设置 sql_dialect = 'tsql'
    • 设置 babelfishpg_tsql.database_name
    • 设置 search_path
  3. Babelfish hook 函数能否正确处理 DDL?

    • 答案:部分可以,但 Is_TSQL_CLIENT() 等检查可能失败
    • 这是方案二的根本问题

关键发现:Babelfish 是否有用于 Replication 的 Hook?

// Babelfish 注册的唯一专门用于 replication 的 hook
logicalrep_modify_slot_hook = logicalrep_modify_slot;

这个 hook 的用途:在 logical replication 时修改 tuple slot 中的数据类型(例如 T-SQL 类型到 PG 类型的转换)。

但这个 hook 不能用于 DDL 同步,因为:

  • 它只处理 DML 数据的类型转换
  • 不处理 DDL 语句的执行

新增 Hook 的可能性

如果需要在内核和 Babelfish 之间建立更规范的接口,有两个选择:

选择 1:新增 PG 内核 Hook

// 在 PG 内核中定义新的 hook
typedef void (*DDLPreExecute_hook_type)(const char *ddl_sql, void *context);
static PGDLLIMPORT DDLPreExecute_hook_type DDLPreExecute_hook;

// 在 apply worker 中调用
if (DDLPreExecute_hook)
    DDLPreExecute_hook(ddl_sql, context);

选择 2:通过 object_access_hook 扩展

// object_access_hook 已经可以拦截对象创建/删除
// 可以扩展它来处理 DDL 同步场景

typedef void (*object_access_hook_type)(
    ObjectAccessType access,
    Oid classId,
    Oid objectId,
    int subId,
    void *arg);

// 拦截 CREATE, DROP, ALTER 操作

选择 3:Babelfish 主动注册处理函数

// 通过 common_utility_plugin_ptr 暴露接口
// 这是 Babelfish 已经使用的机制

typedef struct Common_Utility_Plugin {
    const char *(*translate_pg_type_to_tsql)(const char *pg_type);
    // ... 其他接口
} Common_Utility_Plugin;

总结:内核与插件的正确关系

问题 答案
PG 内核能否直接调用 Babelfish 函数? 不能,违反插件化原则
apply worker 执行 DDL 时 Babelfish hook 会触发吗? ,如果 ProcessUtility_hook 已注册
方案二的问题在哪里? 假设 sql_dialect = 'tsql' 足以让 Babelfish 正确处理 DDL
方案三为何可行? 建立独立的 TDS 连接,绕过了 hook 调用的上下文问题
能否新增 Hook 让方案二可行? 可以,但需要修改 PG 内核定义新 hook

总结

各方案上下文获取能力

上下文需求 方案一 (发布端转换) 方案二 (PG-TSQL) 方案三 (TDS 连接)
Schema 映射 发布端处理 ✅ PG 连接上下文 ⚠️ 完整 TDS 上下文 ✅
逻辑数据库 发布端处理 ✅ 需要额外获取 ⚠️ 通过连接指定 ✅
T-SQL 兼容 无需 部分支持 ❌ 完全支持 ✅
Search path 发布端保存 ✅ 可能不一致 ⚠️ 显式设置 ✅
事务隔离 PG 事务 ✅ PG 事务 ✅ 独立 T-SQL 事务 ⚠️

方案选择建议

对于 Multi-DB 模式

  • 优先选择方案三(TDS 连接)
  • 每个逻辑数据库有独立的物理 DB,需要正确的数据库上下文

对于 Single-DB 模式

  • 方案一(发布端转换)可行,schema 映射简单
  • 方案三也可选,但连接管理复杂度增加

对于需要完整 T-SQL 兼容(如 ALTER VIEW):

  • 只能选择方案三(TDS 连接)

文章作者: growdu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 growdu !
  目录
分类导航
随笔2 AI27 算法1 计算机基础13 博客搭建7 ChatGPT2 集群63 计算机通信1 数据库34 数据库深入80 DPDK26 Docker11 Elasticsearch4 编辑工具4 FAQ1 Go Web1 hometown2 编程语言16 网络9 OPC1 Linux38 openGauss4 页面12 PostgreSQL54 程序员自我修养1 协议11 成长之路1 stock1 存储5 工具20 VPP18 视频作品1 Vue13 Web1 代码示例11 数据库15 BenchmarkSQL1 PostgreSQL 源码修炼之路14
最热文章
1
13 逻辑复制深入
数据库深入🔥 1570
2
0 Postgresql存储、索引及系统优化、主备切换
PostgreSQL🔥 1495
3
一文读懂openguass dcf网络模块
集群🔥 1420
4
逻辑复制源码分析
数据库深入🔥 1327
5
PostgreSQL 分区表:从一行 `PARTITION BY` 到路由热路径的全链路拆解
数据库🔥 1094
6
applyparallelworker.c 之 LA 端源码深度解析:Leader Apply Worker 的指挥中枢
数据库深入🔥 1082
7
PostgreSQL Background Worker 全解:从 `RegisterBackgroundWorker` 到逻辑复制 4 类 worker 的全生命周期
数据库🔥 1078
8
PostgreSQL的后台进程walsender分析 - 关系型数据库 - 亿速云
PostgreSQL🔥 1033
9
PostgreSQL 逻辑复制的监控:六张视图 + 一组可执行 SQL,把 publisher/subscriber 的速率与健康度彻底看透
数据库🔥 1032
10
PostgreSQL 逻辑复制支持 DDL 之后:DDL 与 DML 的时序难题(重点:分区表)
数据库🔥 999
11
reorderbuffer.c 源码深度解析:PostgreSQL 逻辑复制的"事务重组引擎
数据库深入🔥 953
12
PostgreSQL 内核开发:读取一张表的 9 步标准流程与缓存全景
数据库🔥 938
13
从 `postgres` 二进制到生产级守护 —— PostgreSQL 最外层模块与启动全流程拆解
数据库🔥 936
14
支持逻辑复制同步 DDL 适配 SQL Server 方案
数据库深入🔥 934
15
PostgreSQL 逻辑复制的 ReorderBuffer 与事务机制:从一行 WAL 到一致性变更流的全链路绑定
数据库🔥 913
16
DDL同步架构(美化版)
数据库深入🔥 908
17
PostgreSQL Latch 机制详解:从一行 SetLatch 到 epoll 的内核之旅
数据库🔥 871
18
pgbench 源码全解:一个 C 文件如何撑起 PostgreSQL 官方压测工具
数据库🔥 860
19
PostgreSQL libpq 机制与缓冲区详解
数据库🔥 850
20
PostgreSQL 逻辑复制 spill 文件深度剖析:从 `xid-*.spill` 到 TPC-C 的增长方程
数据库🔥 845