OceanBase源码解读:英文基础分词解析器 ob_beng_ft_parser

在 OceanBase 4.3 的全文检索(FTS)体系里,每一种分词器都由「插件描述符 + 分词执行器」两个角色配合完成。本文要看的 ob_beng_ft_parser.cpp 实现的是内置的英文基础分词解析器(parser_type = beng),全部代码只有 195 行,却是理解 FTS 插件框架最干净的样本:它把 ObEnglishAnalyzer(SQL 层的英文分词器)包装成全文索引可用的词项迭代器,完整展示了分词请求从创建到销毁的生命周期管理。与 ngram、ik、whitespace 等解析器一样,它通过 ob_fts_builtin_parser_register 注册为内置解析器,供建 FULLTEXT INDEX(WITH PARSER beng)与全文查询时调用。

核心数据结构

文件包含两个类,职责分工非常清晰:

ObBEngFTParser —— 分词执行器。它同时实现了 ObITokenIterator 迭代器接口,成员包括英文分词器 english_analyzer_、分词上下文 analysis_ctx_、文档对象 doc_ 和切分产出的 token_stream_。设计上采用「init 时一次性切分,get_next_token 逐词吐出」的流式模型。

ObBasicEnglishFTParserDesc —— 插件描述符,相当于解析器对外的「门面 + 工厂」。它本身无状态,核心工作是:每次分词请求 new 一个 ObBEngFTParser,把请求级内存池(param->allocator_)交给它使用,分词结束后由 free_token_iter 显式回收。

关键流程

1. 初始化与一次性切分(init)

init 做四件事:防重复初始化校验、文档长度校验(不超过 UINT32_MAX)、绑定原文并配置上下文、调用英文分词器完成切分:

doc_.set_string(param->fulltext_, param->ft_length_);
analysis_ctx_.cs_ = param->cs_;
analysis_ctx_.filter_stopword_ = false;
analysis_ctx_.need_grouping_ = false;
if (OB_FAIL(english_analyzer_.init(analysis_ctx_, *param->allocator_))) {
  // ...
} else if (OB_FAIL(segment(doc_, token_stream_))) {
  // ...
}

一个值得注意的设计细节:filter_stopword_ 和 need_grouping_ 都被显式关闭。停用词过滤和同词聚合并不是解析器自己的活——它通过 get_add_word_flag 向上层声明了 stop_word、groupby_word 能力标志,由索引构建阶段统一处理。这种「解析器只切词、语义处理上移」的职责划分,让 beng 解析器保持极简。

2. 逐词迭代(get_next_token)

这是 ObITokenIterator 接口的实现,每调用一次返回一个词项。关键点在内存管理:token 的内存属于 token_stream_ 的内部缓冲,迭代过程中可能被复用覆盖,所以必须拷贝到自身 allocator 的 buf 里再返回:

buf = static_cast(allocator_.alloc(token.len_));
MEMCPY(buf, token.ptr_, token.len_);
word = buf;
word_len = token.len_;
char_len = token.len_;   // 英文场景字节 == 字符
word_freq = token_freq;  // 分词阶段已完成同词合并计数

word_freq 来自 token_stream_->get_next(token, token_freq),说明 ObEnglishAnalyzer 在 analyze 阶段已经完成了词频统计,索引构建层拿到的是聚合后的结果。

3. 工厂方法与配对回收(segment / free_token_iter)

描述符的 segment 是核心工厂方法:用 OB_NEWx 从请求级内存池分配一个新的 ObBEngFTParser,init 成功后把 parser 自身作为迭代器返回(它既是执行器又是迭代器,一个对象两种身份);任何一步失败则立刻用 OB_DELETEx 回收,避免泄漏:

parser = OB_NEWx(ObBEngFTParser, param->allocator_, *(param->allocator_));
// ... parser->init(param) ...
iter = parser;
if (OB_FAIL(ret)) {
  OB_DELETEx(ObBEngFTParser, param->allocator_, parser);
}

对应的 free_token_iter 则展示了与 ObIAllocator 严格配对的释放方式:不走 delete,而是显式调用析构函数再 free——因为对象可能并非 new 分配,只有「placement 析构 + allocator free」才能保证与分配方式一致:

iter->~ObITokenIterator();
param->allocator_->free(iter);

4. 能力声明(get_add_word_flag)

描述符通过这个接口告诉索引构建层自己支持什么:min_max_word(记录最短/最长词边界)、stop_word(停用词过滤)、groupby_word(同词聚合词频)。上层据此决定索引写入时的附加处理。

小结

ob_beng_ft_parser.cpp 虽然短小,却浓缩了 OceanBase FTS 插件框架的三个关键设计:一是双角色结构——无状态的 Desc 做工厂和生命周期管理,有状态的 Parser 做实际切分;二是流式迭代模型——init 一次性切分、get_next_token 逐词消费,天然适配索引构建的批量写入;三是内存纪律——词项必须拷贝脱离上游缓冲、迭代器用 placement 析构配对释放,全程不依赖全局 new/delete。看完这个文件,再看 ngram、ik 这些更复杂的分词器,框架部分就是完全一样的套路了。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注