OceanBase 插件体系全解析:从 legacy `obp_*` 导出符号到 v2 纯 C vtable 契约框架
2026/9/16 14:03:30 网站建设 项目流程

OceanBase 插件体系全解析:从 legacyobp_*导出符号到 v2 纯 C vtable 契约框架

【免费下载链接】oceanbaseOceanBase is the unified distributed database for the AI era — open-source, multi-model, one engine for your most demanding workloads.项目地址: https://gitcode.com/GitHub_Trending/oc/oceanbase

导读:本文围绕 src/plugin/README.md 及其两份子 README,完整解析 OceanBase(OB)当前开源的两套插件体系:最早期的通用插件框架legacy/与新一代插件框架v2/。你将掌握两套体系的目录结构、插件契约与加载机制、二者在"宿主能力注入方式、插件类型扩展、ABI 演进"上的本质差异,以及驱动外表插件做表扫描的执行器 row iter 在 SQL 执行层中的位置。适合需要为 OB 编写外部数据源插件(如外表格式插件)或阅读 OB 插件相关源码的开发者。

一、总览:OB 的两套插件体系

OB 的插件能力集中在src/plugin/目录下,分为legacy/v2/两个子目录,各自维护独立的 README(legacy/README.md、v2/README.md)。两套体系的核心定位如下:

目录说明
legacy/最早的通用插件框架(兼容维护中):插件只依赖include/oceanbase/SDK,回调 OB 靠obp_*导出符号
v2/新一代插件框架:纯 C vtable 契约,宿主能力经回调表注入,无导出符号;外表格式插件是首个插件类型

一个容易踩坑的关键注记

主 README 特别强调:驱动外表插件做表扫描的执行器 row iter 属于 SQL 执行层,统一放在src/sql/engine/table/(与其他格式 row iter 并列),不在src/plugin/目录内。对应两个文件:

  • ob_ext_table_plugin_row_iter.*—— v2 外表契约的执行器(见 ob_ext_table_plugin_row_iter.h);
  • ob_ext_table_java_plugin_row_iter.*—— legacy 内建插件(JNI 数据源)的执行器。

从源码看,v2 的ObExtTablePluginRowIterator继承自ObExternalTableRowIterator,在init()时若对应格式的插件.so未加载,会直接返回OB_NOT_SUPPORTED;运行时通过plan_create / reader_create / reader_open_scan / reader_open_task / reader_next_batch / reader_close_*驱动插件 vtable,并用ObArrowDataLoader将每个 Arrow batch 导入 OB 输出向量。也就是说:插件只负责"产出数据批",真正把数据搬进 OB 执行引擎的 row iter 是 SQL 层代码

二、legacy/ —— 兼容维护中的通用插件框架

legacy/是 OB 最早的插件体系:插件以.so形式独立编译,只依赖include/oceanbase/下的 SDK 头文件;运行时由 observer 通过dlopen加载。插件对obp_*符号的未定义引用,由 observer 主程序通过链接选项-Wl,--export-dynamic-symbol=obp_*导出的动态符号表来解析。该链接选项可以在 src/observer/CMakeLists.txt 中直接看到(第 774 行附近);legacy/export/下的obp_*实现也依赖这个机制保留、不被链接器裁剪。

目录结构

目录作用
include/oceanbase/对外公开的 C 插件 SDK,外部插件只应依赖这里的头文件
interface/各类插件的内部接口描述(ftparser / kms / 外部表等插件类型的内部调用模型)
sys/插件管理:内置插件初始化、动态库加载(dl handle / entry handle)、插件查找
adaptor/适配器:把对外的 C 插件接口适配到 OB 内部接口(plugin / ftparser / kms)
export/插件 API 的 OB 侧实现(obp_*符号:allocator / charset / log / ftparser / kms)。内部不使用,靠 export-dynamic-symbol 保留
share/共享工具(ObProperties等)
external_table/内建外部表插件(java / odps 等 JNI 数据源,OB 管 schema)

插件描述与声明宏:OBP_DECLARE_PLUGIN

legacy插件的"身份证"是 ob_plugin.h 中定义的ObPlugin结构与OBP_DECLARE_PLUGIN宏:

OBP_DECLARE_PLUGIN(example_plugin) { "OceanBase Corporation", // author OBP_MAKE_VERSION(1, 0, 0), // version OBP_LICENSE_APACHE_V2, // license plugin_init, // init routine plugin_deinit, // deinit routine (optional) } OBP_DECLARE_PLUGIN_END;

要点:

  • 插件类型枚举ObPluginType定义了OBP_PLUGIN_TYPE_FT_PARSER(全文检索 parser 插件)、OBP_PLUGIN_TYPE_EXTERNAL(外部表数据资源)、OBP_PLUGIN_TYPE_KMS(KMS 插件)。
  • 版本宏OBP_PLUGIN_API_VERSION_CURRENT目前为0.3.0OBP_MAKE_VERSION(major, minor, patch)将三个字段编码为uint64_t版本号。
  • 动态/静态两种形态OBP_DYNAMIC_PLUGIN定义下会导出OBP_DYNAMIC_PLUGIN_NAME_VAROBP_DYNAMIC_PLUGIN_API_VERSION_VAR等符号;否则作为静态插件直接编进 observer。
  • 许可字符串:提供OBP_LICENSE_GPL / BSD / MIT / APACHE_V2等常量,也允许自定义字符串。
  • 参数交互ObPluginParamPtr(即ObPluginDatum)配合obp_param_plugin()obp_param_plugin_user_data()obp_param_set_plugin_user_data()使用,插件可在 init/deinit 中读写"插件实例特定数据"。

sys/目录负责插件的装载与生命周期管理:ob_plugin_builtin.*(内置插件初始化)、ob_plugin_dl_handle.*/ob_plugin_entry_handle.*(动态库句柄与入口句柄)、ob_plugin_mgr.*(插件查找与管理)、ob_plugin_load_param.*(加载参数)。adaptor/下的ob_plugin_ftparser_adaptor.*ob_plugin_kms_adaptor.*则把 C 插件接口适配到 OB 内部调用模型,ob_plugin_kms_client.*是 KMS 的客户端封装。

三、v2/ —— 新一代插件框架:纯 C vtable 契约

v2/是 legacy 的继替者,核心设计是纯 C vtable 契约

  1. 每种插件一个独立.so,OB 通过dlopen + dlsym解析单入口符号拿到整张 vtable;
  2. 宿主能力(内存 / IO / 执行器 / 日志)以回调表(host API)在运行时注入插件
  3. 不向插件导出任何 OB 符号—— 这正是与 legacyobp_*导出符号机制的本质区别。

目录结构

目录作用
include/插件契约头,插件唯一需要包含的头
host/宿主能力的 OB 侧实现
loader/通用 dlopen 加载器
external_table/外表插件类型的 OB 侧消费方

1. 契约头(include/)—— 插件唯一需要包含的东西

  • ob_external_table_plugin.h:插件要实现的 vtable + ABI 版本 + 入口符号;
  • ob_ext_host_api.h:OB 提供给插件的宿主 API(mem / executor / io / log 回调表);
  • ob_external_table_protocol.h:控制面协议词汇表(契约 errno + JSON key)。

注意一个工程约定:这些头与ob-deps/patch/paimon/ob_plugin/下的同名文件内容必须保持一致,以本仓库为源

2. 数据面与控制面分离:Arrow C Data + JSON 控制面

ob_external_table_plugin.h的开头注释完整定义了 v2 外表契约的"两个平面":

  • 数据面 = Arrow C Data:行批通过reader_next_batchArrowArray/ArrowSchema形式流动——二进制、零拷贝、高吞吐,绝不使用 JSON
  • 控制面 = JSON 文本:schema、谓词、统计信息和扫描任务描述符都以 UTF-8 JSON 字符串(const char*+ length)传递。

ABI 表面因此极小:vtable 函数签名 +char*缓冲区 + host API + 不透明 reader 状态引用。演进一个控制结构(新增列属性、谓词操作符、统计项、扫描任务字段)只是 JSON schema 变更,不是 ABI 破坏——旧插件忽略未知 key,新 OB 容忍缺失的可选 key,仅有文档标注为 required 的字段保持强制。这就是"独立编译、独立发布的.so能跨版本共存"的根本原因。

JSON 值编码规则(避免精度丢失)在契约头中有明确表格:

OB 类型JSON 编码
boolJSON bool
int <= int32JSON number
int64 / bigintJSON string(避开 2^53 的 double 上限)
float / doubleJSON number
decimalJSON string(如"1.23"
dateJSON number(距 epoch 的天数)
datetime / timestamp / timeJSON string(如"2024-01-01 00:00:00"
binary / varbinaryJSON string(base64)
string / varchar / charJSON string
null / unknownJSON null

3. 协议类型词汇表:ob_ext_obj_type

v2 契约定义了"协议化类型"枚举ob_ext_obj_type(ob_external_table_plugin.h),列类型由插件决定,OB 负责映射到ObObjType。枚举值保持稳定,因为 OB 的ob_ext_obj_type -> ObObjTypemapper 依赖它们;它保留了 Arrow 会丢失的区分(varchar vs string vs char、decimal 精度/刻度、datetime vs timestamp、嵌套 array/map)。控制面 JSON 中类型以枚举的名字字符串(如"BIGINT")出现,OB 侧通过ext_type_from_name解析。

4. 内存所有权(严格约定)

契约的内存所有权规则非常严格:

  • 插件输出(schema / scan-tasks / stats JSON)由插件释放,OB 从不释放。因为缓冲区可能来自静态常量、host->mem.mem_alloc或插件自有分配器,只有插件知道怎么释放。因此每个输出都有配对的 destroy 回调(schema_destroy/tasks_destroy/stats_destroy):OB 拷贝/解析完缓冲区后调用,插件内部决定是 no-op(静态)还是host->mem.mem_free(host 分配)。host参数会传给每个 destroy,保证用 host 分配器的插件仍能触达host->mem.mem_free
  • OB 输入给插件(options / predicate / projection JSON)由 OB 拥有,只在调用期间有效;插件必须解析成自己的原生形态,绝不能保留指针超过调用期
  • 跨边界唯一的非 JSON 句柄是不透明、插件所有的 reader 状态引用,通过reader_close一起释放。
  • host->mem.mem_alloc / mem_free供插件自己的内部缓冲区使用(如 arrow 内存池),不是OB 释放插件输出的方式。

5. 控制面 JSON 协议

契约头给出了完整协议示例。options(OB→插件):

{"location":"..","access_info":"..", "ext_options":{<format-private knobs>}}

ext_options是不透明嵌套对象,OB 原样透传(OB 不认识其中任何内部 key),由插件自行解包与校验,缺省即默认。

表 schema(插件→OB),ext_type使用枚举名字:

{"columns":[{"name":"..","field_id":N,"ext_type":"BIGINT", "precision":P,"scale":S,"length":L,"nullable":true}]}

ARRAY 列携带单个 child(递归、同构):

{"columns":[{"name":"tags","field_id":N,"ext_type":"ARRAY","nullable":true, "children":[{"name":"element","field_id":M,"ext_type":"STRING", "nullable":true}]}]}

(ARRAY of ARRAY 就是 child 的 ext_type 也是 ARRAY。)MAP 列携带 key/value 两个 child:

{"columns":[{"name":"attrs","field_id":N,"ext_type":"MAP","nullable":true, "children":[{"name":"key","field_id":M,"ext_type":"STRING", "nullable":false}, {"name":"value","field_id":K,"ext_type":"INT", "nullable":true}]}]}

分区列作为顶层名字列表携带,partition_keys形如["ds","hr"]

谓词 / 分区过滤(OB→插件)是表达式树:

{"kind":"cmp","op":"eq", "children":[{"kind":"col","col_idx":N,"name":".."}, {"kind":"lit","value":".."}]}
  • kinds:lit / col / cmp / in / not_in / is_null / is_not_null / and / or / not
  • colcol_idx是列的 field_id(信息性),name由插件解析到自己的 schema 字段索引/类型;
  • lit的 value 是字符串形式的字面量,插件按兄弟 col(按名字解析)的字段类型转换;
  • cmp是二元节点,children=[col, lit],op 属于eq/ne/lt/le/gt/ge
  • in/not_in的 children=[col, lit, lit, ...];is_null/is_not_null的 children=[col];and/or的 children=[pred, ...],not的 children=[pred];
  • 根节点为 NULL 或""表示不下推。

scan tasks(插件→OB)携带插件所有的 split 与可选的"plan-atomic" OB 文件读取器指令:

{"partition_filter_applied":true, "tasks":[{"row_count":N,"byte_size":N, "partition_values":[{"field_id":N,"value":"2025-01-15"}, {"field_id":M,"value":null}], "ob_file_scan":{"version":1,"file_format":"parquet", "files":[{"path":"..","byte_size":N, "row_count":N}]}, "plugin_split":".."}]}
  • partition_values:每个 task 都要产出。分区表要求每个分区 key 恰好一个按 schema 顺序的条目,task 代表的每个文件必须属于该元组;缺失或非法元数据是硬性计划错误。JSON null 表示配置的默认分区。非分区表缺失或[]表示无分区,非空元组则非法。
  • ob_file_scanplan-atomic的:缺省时整批数据留在插件 reader 上;合法描述符则选择 OB 文件读取器,执行期间不会回退到plugin_split。版本 1 支持 parquet/orc,要求非空 files 数组,每个条目 path 非空、size 非负、row_count 精确非负。出现非法描述符、混合出现、混合格式都是硬性计划错误。

statistics(插件→OB):

{"table":{"row_count":N,"byte_size":N}, "columns":[{"col_idx":N,"row_count":N,"null_count":N,"ndv":N, "min":"..","max":".."}]}

6. 错误模型:直接返回 OB errno

每个返回错误的函数直接返回 OB errno(0 =OB_EXT_SUCCESS,负数 = 错误),使用契约头中定义的OB_EXT_*枚举(插件编译时不需要 OB 的ob_errno.h)。出错时插件必须先用host->log(...)(插件侧的OBEXT_LOG_*宏,会捕获__FILE__/__LINE__/__func__,让 observer.log 里能看到插件侧的源码位置)记录诊断,再返回 errno。跨边界没有错误对象——没有ObExtError,没有error_message/error_destroy槽位,OB 只消费 int 返回值,消息活在日志行里。成功调用返回OB_EXT_SUCCESS且不记日志。errno 值是 OB errno 原样(如OB_EXT_NOT_SUPPORTED == OB_NOT_SUPPORTED == -4007),OB 直接把返回值赋给ret,无翻译层。

协议词汇表在 ob_external_table_protocol.h 中定义,包含两类词汇:

  • 契约 errno(OB_EXT_*):允许跨界面的唯一错误值。值就是 OB errno 原样(OB_EXT_SUCCESS=0OB_EXT_INVALID_ARGUMENT=-4002OB_EXT_NOT_SUPPORTED=-4007OB_EXT_IO_ERROR=-4009OB_EXT_ALLOCATE_MEMORY_FAILED=-4013OB_EXT_ERR_UNEXPECTED=-4016OB_EXT_ENTRY_NOT_EXIST=-4018OB_EXT_FILE_NOT_EXIST=-4027OB_EXT_DESERIALIZE_ERROR=-4034OB_EXT_DIR_NOT_EXIST=-4066OB_EXT_INVALID_DATA=-4070OB_EXT_OLD_SCHEMA_VERSION=-4177)。OB 在 ob_ext_plugin_loader.cpp 中通过 static_assert 编译期校验相等性。
  • JSON 字段名(OB_EXT_K_*):控制面文档的 key。OB 与每个插件必须引用这些常量而不是裸字符串字面量,这样 typo 或改名在单一定义处就是编译错误,而不是静默的运行时错配。未知 key 不会被静默忽略——OB 会打一条响亮的 WARN;required key 缺失或类型不匹配是硬错误。

7. 可选槽位(优雅降级)

契约头明确列出了哪些槽位可以为 NULL,以及对应的降级行为:

槽位为 NULL降级行为
init/deinit不做进程级初始化
fetch_statistics无统计,OB 用默认基数
schema_destroy/tasks_destroy/stats_destroy仅当插件保证对应输出永远是静态常量(无需 free)时安全;OB 的 helper 会做空指针检查。插件只要曾经分配过输出缓冲区,就必须实现对应 destroy,否则泄漏
predicate/partition_filter/read_projection不下推 / 读全部列
recognize_tableOB 对该格式使用内建 fallback 规则

只实现{load_schema, plan_create, reader_create, reader_open_scan, reader_open_task, reader_next_batch, reader_close_task, reader_close_scan, reader_close}的插件就是完整可用的。

8. vtable 全貌与三段式 reader 状态

vtable 结构ObExtTablePluginApi(见 ob_external_table_plugin.h)由以下部分组成:

  • 身份plugin_name()plugin_version()format_name()(OB 动态注册到格式表的规范格式字符串)、可选的init/deinit
  • 格式识别recognize_table(table_uri, recognize_json, host)—— 判断本插件是否拥有该表,返回OB_SUCCESS即"是我的",其他 errno 即"不是我的";
  • schema(按表缓存)load_schema产出 schema JSON,schema_destroy释放;
  • 计划/扫描任务plan_create消费 partition_filter / predicate,返回全部 scan tasks 的 JSON;tasks_destroy释放。limit == -1表示不限制;desired_task_count是 PX 并行度提示(可忽略)。catalog/plan schema 漂移时可返回OB_OLD_SCHEMA_VERSION
  • reader(每个 row iterator 三个持久状态对象)
    • worker:reader_create/reader_close,持有迭代器 init 时已知的读配置(table_uri / options / projection);
    • scan:reader_open_scan/reader_close_scan,持有由 worker 常量 + 本次 scan 谓词构建的读管道;
    • task:reader_open_task/reader_next_batch/reader_close_task,持有 split + batch reader。
    • 外层状态是内层 open 调用的只读输入(open_scan 只能改 scan,open_task 只能改 task)。reader_next_batch返回 0 = 有 batch(调用者拥有并须释放 arr/sch)、1 = EOF、<0 = OB errno。
    • reader_open_taskstart_row/row_count是行区间细分(传 0 / task_row_count 表示整个 task)。
  • 统计(可选)fetch_statistics(table_uri, options_json, partition_filter_json, ...)stats_destroy释放。

入口符号是唯一的:

const struct ObExtTablePluginApi* ob_ext_table_plugin_get_api(unsigned int abi_version);

当前OB_EXT_TABLE_PLUGIN_ABI_VERSION = 2。OB 通过 dlsym 解析这个符号;ABI 版本不匹配时插件返回 NULL。.so缺失(dlsym 失败)时 OB 报告错误——一个格式只有在它的.so存在时才可选

9. host API:注入的宿主能力

ob_ext_host_api.h 定义ObExtTableHostApi,把 OB 能力按三张函数表 + 日志回调暴露给插件。所有回调都收到顶层host->ctx

  • memObExtMemApi):mem_alloc(ctx, size, alignment)/mem_free/mem_realloc/mem_bytes_allocated。这是唯一允许跨边界的分配器。alignment非零时宿主必须遵守(如 arrow 的 64 字节对齐),mem_alloc(0)必须返回非 NULL 占位符;mem_freesize仅作信息用途,实现必须按指针释放、不得依赖 size。
  • executorObExtExecutorApi):exec_submit(ctx, fn, arg)exec_thread_count(ctx)exec_submit返回 0 表示任务被接受(宿主保证被接受的任务会运行——即使关闭期间也会 drain,因为插件只在任务运行时回收 per-task 资源);非零表示宿主拒绝且未接管所有权,插件需自行回收资源(可回退为内联执行)。
  • ioObExtIoApi):只读文件系统 + 目录列举。file_open / file_read / file_read_at / file_tell / file_seek / file_length / file_close / file_exists / file_status / list_dir。约定:file_read/file_read_at返回字节数(>0),0 = EOF,-1 = 错误;file_seek返回 0/-1(origin 是OB_EXT_SEEK_SET/CUR/END);file_open返回流句柄或 NULL;file_exists返回 1/0/-1;list_dir非递归遍历子项,is_dir为 1 表示目录。
  • log:把已格式化好的插件消息路由进 OB 的 logger,file/line/func传插件侧调用点(__FILE__/__LINE__/__func__),新插件应该用它替代 stdout/stderr,让诊断落进 observer.log。日志级别OB_EXT_LOG_TRACE/INFO/WARN/ERROR由宿主映射到 OB 日志级别。

OB 侧的 host 实现位于v2/host/ob_ext_mem_pool.*(内存池)、ob_ext_file_system.*(只读文件系统)、ob_ext_malloc_guard.h(malloc 标签守卫)、ob_ext_host_provider.*(HostApi 组装,把通用能力装成契约的ObExtTableHostApi)。

四、v2 loader:懒加载、SONAME 约定与常驻句柄

ob_ext_plugin_loader.h 描述通用 dlopen 加载器:

  • 懒加载:从集群参数ext_plugin_config懒加载——第一次注册表查找(ObExtFormatRegistry::get_plugin_by_slot/recognize_format)时读取配置并 dlopen 所有命名的插件。未加载的格式只是"不在注册表中",访问其表会报"plugin not loaded"。
  • 定位.so的两种方式
    • 显式绝对路径:配置项的path字段;
    • SONAME 约定:lib_ob_<name>.so(小写)在LD_LIBRARY_PATH下搜索。搜索前先把_ob_additional_lib_path(支持:分隔的多目录)ensure 进LD_LIBRARY_PATH,再搜索统一环境——与 libhdfs.so / libjvm.so 走同一路径(ObLdLibraryPathUtil)。
  • 常驻:通过版本检查的句柄进程生命周期内常驻,从不 dlclose(函数指针与插件初始化的全局状态都在使用中)。加载失败(dlopen / dlsym / ABI 不匹配 / 找不到)则不留任何常驻物:损坏句柄由load_ext_plugindlclose 掉(版本检查通过前其符号未被使用,安全),运维人员修复/替换.so下次重启生效
  • 加载结果分类ObExtPluginLoadStatus枚举细分了失败原因(EXT_PLUGIN_NOT_FOUNDDLOPEN_FAILEDNO_SYMBOLABI_MISMATCHNO_FORMAT_NAMEALREADY_LOADED等),供注册表打诊断日志。

五、注册表与配置:ext_plugin_config 与稳定槽位

ob_ext_format_registry.h 是进程级的外表格式插件注册表,几个关键设计:

  • 声明式配置ext_plugin_config集群参数是一个 JSON 数组(元素为{"name":"...","path":"..."}对象),是进程加载哪些插件的唯一声明性来源没有LOAD PLUGIN命令、没有启动扫描、没有 per-attempt 状态表。配置每个进程最多读一次:之后ALTER SYSTEM SET ext_plugin_config只改持久化值,下次重启生效,已常驻的集合不受影响。
  • 单插件失败不影响整体:单个插件加载失败(dlopen/dlsym/ABI 不匹配/找不到)只记日志并跳过,不中止其余条目、不使查找失败——缺失插件只是返回"not loaded"。
  • 内存模型:加载路径在std::call_once下每进程恰好执行一次,读路径无锁(call_once 的 synchronizes-with 把唯一写阶段与所有读阶段隔开),读方看到一致的plugins_[]快照;api_==nullptr的洞就是加载失败的配置条目。
  • 稳定槽位(STABLE SLOT):插件身份在运行时流程(plan struct / CG / iter / DAS)中携带的是 int16 稳定槽位[0, MAX_PLUGINS)不是格式名字符串。槽位 = 插件在plugins_[]中的数组下标,在加载时按ext_plugin_config的 JSON 数组顺序分配——它不是运行时计数器,因此只要配置相同,所有服务器的同一插件槽位一致,与每个条目的加载成败无关。失败的配置条目留下一个洞(plugins_[i]保持api_==nullptr),后续条目仍用自己的下标、不会前移。格式名只活在注册表内部(配置解析 +recognize_format的逐插件探测)。
  • 可观测性ObExtPluginStatus结构对应__all_virtual_plugin_info虚拟表的一行——某个配置声明插件的加载结果(成功或失败)的只读快照,包含plugin_name_lib_path_(实际 dlopen 的绝对路径或失败时尝试的 soname/路径)、plugin_version_error_msg_status_load_time_us_,通过get_plugin_statuses()拷贝出去。

native 格式(如 ICEBERG、HIVE)继续走各自的专用代码路径,不碰槽位轴

六、v2 外表插件的 OB 侧消费方

v2/external_table/是外表插件类型的 OB 侧消费方:

文件职责
ob_ext_format_registry.*插件注册与槽位管理(上文所述)
ob_ext_json_protocol.*+ob_ext_json_internal.h控制面 JSON 编解码
ob_ext_schema_parser.*schema JSON 解析
ob_ext_type_mapper.*协议类型 → OB 类型映射(ob_ext_obj_typeObObjType
ob_ext_table_metadata.*表元数据
ob_ext_plugin_util.*契约 destroy 包装

scan tasks 由优化器侧的插件 pruner 产出,通过现有 PX 路径分发;执行时迭代器把实例化的存储过滤转成可选的 predicate JSON 传给reader_open_scan。CPP_PLUGIN 不会把树复制进spec.filters_,因此该迭代器总是通过calc_filters计算pd_storage_filters_作为正确性兜底(见 ob_ext_table_plugin_row_iter.h)。

七、两套体系的角色对应关系

角色legacyv2
插件可见头文件legacy/includev2/include
OB 提供给插件的服务legacy/export(obp_*符号导出)v2/host(回调表注入)
插件加载/管理legacy/sysv2/loader
插件类型的 OB 侧消费方legacy/adaptor + legacy/external_tablev2/external_table

新插件类型一律进v2/(纯 C vtable + 回调表注入,无导出符号);legacy 仅兼容维护,不再扩展。

八、小结与源码导航

两套插件体系的本质差异可以用一句话概括:legacy 用"导出符号"把 OB 能力交给插件,v2 用"回调表注入"把 OB 能力送进插件——前者要求 observer 通过-Wl,--export-dynamic-symbol=obp_*暴露符号(见 src/observer/CMakeLists.txt),后者通过ObExtTableHostApi在运行时注入 mem / executor / io / log 四类回调(见 ob_ext_host_api.h)。v2 的 JSON 控制面让"新增列属性、谓词算子、统计项"都变成 JSON schema 演进而非 ABI 变更,从而支撑独立编译、独立发布的插件.so跨版本共存。

继续深入时推荐按以下路径阅读:

  • src/plugin/README.md —— 两套体系总览;
  • src/plugin/legacy/README.md 与 src/plugin/v2/README.md —— 各目录职责;
  • ob_external_table_plugin.h —— vtable、ABI 版本、内存所有权、JSON 协议(本文主体依据);
  • ob_external_table_protocol.h —— 契约 errno 与 JSON key 单一来源;
  • ob_ext_host_api.h —— 宿主回调表;
  • ob_ext_plugin_loader.h 与 ob_ext_format_registry.h —— 懒加载与稳定槽位机制;
  • ob_ext_table_plugin_row_iter.h —— SQL 执行层如何驱动插件 vtable 并导入 Arrow batch。

【免费下载链接】oceanbaseOceanBase is the unified distributed database for the AI era — open-source, multi-model, one engine for your most demanding workloads.项目地址: https://gitcode.com/GitHub_Trending/oc/oceanbase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询