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.0;OBP_MAKE_VERSION(major, minor, patch)将三个字段编码为uint64_t版本号。 - 动态/静态两种形态:
OBP_DYNAMIC_PLUGIN定义下会导出OBP_DYNAMIC_PLUGIN_NAME_VAR、OBP_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 契约:
- 每种插件一个独立
.so,OB 通过dlopen + dlsym解析单入口符号拿到整张 vtable; - 宿主能力(内存 / IO / 执行器 / 日志)以回调表(host API)在运行时注入插件;
- 不向插件导出任何 OB 符号—— 这正是与 legacy
obp_*导出符号机制的本质区别。
目录结构
| 目录 | 作用 |
|---|---|
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_batch以ArrowArray/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 编码 |
|---|---|
| bool | JSON bool |
| int <= int32 | JSON number |
| int64 / bigint | JSON string(避开 2^53 的 double 上限) |
| float / double | JSON number |
| decimal | JSON string(如"1.23") |
| date | JSON number(距 epoch 的天数) |
| datetime / timestamp / time | JSON string(如"2024-01-01 00:00:00") |
| binary / varbinary | JSON string(base64) |
| string / varchar / char | JSON string |
| null / unknown | JSON 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; col的col_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_scan是plan-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=0、OB_EXT_INVALID_ARGUMENT=-4002、OB_EXT_NOT_SUPPORTED=-4007、OB_EXT_IO_ERROR=-4009、OB_EXT_ALLOCATE_MEMORY_FAILED=-4013、OB_EXT_ERR_UNEXPECTED=-4016、OB_EXT_ENTRY_NOT_EXIST=-4018、OB_EXT_FILE_NOT_EXIST=-4027、OB_EXT_DESERIALIZE_ERROR=-4034、OB_EXT_DIR_NOT_EXIST=-4066、OB_EXT_INVALID_DATA=-4070、OB_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_table | OB 对该格式使用内建 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_task的start_row/row_count是行区间细分(传 0 / task_row_count 表示整个 task)。
- worker:
- 统计(可选):
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:
- mem(
ObExtMemApi):mem_alloc(ctx, size, alignment)/mem_free/mem_realloc/mem_bytes_allocated。这是唯一允许跨边界的分配器。alignment非零时宿主必须遵守(如 arrow 的 64 字节对齐),mem_alloc(0)必须返回非 NULL 占位符;mem_free的size仅作信息用途,实现必须按指针释放、不得依赖 size。 - executor(
ObExtExecutorApi):exec_submit(ctx, fn, arg)、exec_thread_count(ctx)。exec_submit返回 0 表示任务被接受(宿主保证被接受的任务会运行——即使关闭期间也会 drain,因为插件只在任务运行时回收 per-task 资源);非零表示宿主拒绝且未接管所有权,插件需自行回收资源(可回退为内联执行)。 - io(
ObExtIoApi):只读文件系统 + 目录列举。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_FOUND、DLOPEN_FAILED、NO_SYMBOL、ABI_MISMATCH、NO_FORMAT_NAME、ALREADY_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_type→ObObjType) |
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)。
七、两套体系的角色对应关系
| 角色 | legacy | v2 |
|---|---|---|
| 插件可见头文件 | legacy/include | v2/include |
| OB 提供给插件的服务 | legacy/export(obp_*符号导出) | v2/host(回调表注入) |
| 插件加载/管理 | legacy/sys | v2/loader |
| 插件类型的 OB 侧消费方 | legacy/adaptor + legacy/external_table | v2/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),仅供参考