分享|Hello OceanBase!开启 OceanBase 二次开发
引子:
最近看到 OB 内核研发总监竹翁老师的一篇关于源码解读(《表达式和函数》)的帖子很有感触。竹翁老师在最后说:“为 OceanBase 添加系统内建函数,是 OceanBase SQL 组很多新人入职第一题”。虽然暂时没有跳槽的打算,但是还是想试一下这道第一题。我本身不是 DBA,平时和数据库打交道的机会也不多,只是最近由于工作原因开始了解一些国产数据库头部产品。就我的领域而言,算是“出圈”了。看了各大社区很多帖子,参与了一些公开课,也更深刻的感受到国产数据库生态蓬勃发展的趋势,同时也非常感谢那些能把自己积累的经验分享出来的国产 DBer,让我这样的门外汉能够迅速的“入圈”。所谓“投之以木桃,报之以琼瑶”,希望借此机会能把我一些浅薄的经验留给社区,希望更多有需要的 DBer 能看到。
作者简介:
夏克,OceanBase 社区文档贡献者,多年从事金融行业核心系统设计开发工作,服务于某交易所子公司,现阶段负责国产数据库调研,近期考取了OBCA 、PCTA 认证。
Hello OceanBase,看到这标题,你可能会联想到“ Hello World ”,是的!没错!本文就是针对 OceanBase 二次开发的“ Hello World ”。
概要
通过一个 demo 了解如何添加或修改 OceanBase 内建函数,或者说如何基于 OceanBase 社区版进行二次开发。做这个尝试除了上面提到的竹翁老师文章的启发外,还源于一个需求,即数据库的外部函数扩展。在 Oracle 中提供了可以调用 C 或者 JAVA 函数的功能——外部函数,调用外部函数方式就像是调用内建函数一样的,从用户的角度没有任何区别。
从某种意义上讲,这个功能提升了数据库的扩展性,也可以应对一些业务场景,比如,一些比较复杂的数学算法,通过 SQL 或者 Oracle 内置函数实现比较麻烦,可以作为外部函数通过 C 或者 JAVA 来实现,当然这些算法可以上提到业务层来实现。
之前调研了几款国产数据库也写了一些类似使用外部函数的操作流程,在我看来,达梦外部函数的基本用法与 Oracle 类似,细节和实现上略有差异;像 OpenGauss/MogDB、TDSQL-PG、人大金仓等,是基于 PostgreSQL 内核的产品,所以天然的继承了postgres 的扩展方式。但完全自主研发的 OceanBase 目前还没有这样的扩展方式,所以我另辟蹊径—— 不能外部扩展,那么就尝试内部扩展,让我们从“ Hello OceanBase ”开始。
环境准备
OceanBase 集群
社区关于 OceanBase 部署的帖子应该很多,单机的、多机的、单节点的,3节点的。针对这个 demo 推荐大家使用离线使用 OBD 本地部署单节点 OceanBase ,因为就开发环境而言,搭建和使用都比较简单。
OceanBase 源码
获取最新源码可参考:
git clone https://github.com/oceanbase/oceanbase
代码结构
社区有一系列源码解读的帖子供参考,所以在这里简单介绍一下sql/resolver/expr
相关的部分代码结构。
内建函数注册流程
#define REG_OP(OpClass) \do { \
OpClass op(alloc); \if (OB_UNLIKELY(i >= EXPR_OP_NUM)) { \
LOG_ERROR("out of the max expr"); \
} else { \
NAME_TYPES[i].name_ = op.get_name(); \
NAME_TYPES[i].type_ = op.get_type(); \
OP_ALLOC[op.get_type()] = ObExprOperatorFactory::alloc<OpClass>; \
i++; \
} \
} while (0)
expr 类图
内置函数主要实现 ObExprOperator 接口类,其中
calc_result_type0、calc_result0 ,提供函数注册是内存分配,类型定义。cg_expr 将函数指针注册给 eval_func_,rt_expr.eval_func_ = ObExprHello::eval_hello ;内建函数被调用时,通过这个函数指针被调用。而 eval_hello 是真正实现功能的函数。
开发 Hello OceanBase
整个工程需要修改以下几个文件
创建 ObExprHello 类
sql/resolver/expr 目录下有大量的实现例子供参考,可以根据实际业务需求选择参考对象。
ob_expr_hello.h
#ifndef _OB_EXPR_HELLO_H_
#define _OB_EXPR_HELLO_H_
#include "sql/engine/expr/ob_expr_operator.h"
namespace oceanbase {
namespace sql {
class ObExprHello : public ObStringExprOperator {
public:
explicit ObExprHello(common::ObIAllocator& alloc);
virtual ~ObExprHello();
virtual int calc_result_type0(ObExprResType& type, common::ObExprTypeCtx& type_ctx) const;
virtual int calc_result0(common::ObObj& result, common::ObExprCtx& expr_ctx) const;
static int eval_hello(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum);
virtual int cg_expr(ObExprCGCtx& op_cg_ctx, const ObRawExpr& raw_expr, ObExpr& rt_expr) const override;
private:
DISALLOW_COPY_AND_ASSIGN(ObExprHello);
};
} /* namespace sql */
} /* namespace oceanbase */
#endif
ob_expr_hello.cpp
#define USING_LOG_PREFIX SQL_ENG
#include "sql/engine/expr/ob_expr_hello.h"
static const char* SAY_HELLO = "Hello OceanBase!";
namespace oceanbase {
using namespace common;
namespace sql {
ObExprHello::ObExprHello(ObIAllocator& alloc) : ObStringExprOperator(alloc, T_FUN_SYS_HELLO, N_HELLO, 0)
{}
ObExprHello::~ObExprHello()
{}
int ObExprHello::calc_result_type0(ObExprResType& type, ObExprTypeCtx& type_ctx) const
{
UNUSED(type_ctx);
type.set_varchar();
type.set_length(static_cast<common::ObLength>(strlen(SAY_HELLO)));
type.set_default_collation_type();
type.set_collation_level(CS_LEVEL_SYSCONST);
return OB_SUCCESS;
}
int ObExprHello::calc_result0(ObObj& result, ObExprCtx& expr_ctx) const
{
UNUSED(expr_ctx);
result.set_varchar(common::ObString(SAY_HELLO));
result.set_collation(result_type_);
return OB_SUCCESS;
}
int ObExprHello::eval_hello(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
UNUSED(expr);
UNUSED(ctx);
expr_datum.set_string(common::ObString(SAY_HELLO));
return OB_SUCCESS;
}
int ObExprHello::cg_expr(ObExprCGCtx& op_cg_ctx, const ObRawExpr& raw_expr, ObExpr& rt_expr) const
{
UNUSED(raw_expr);
UNUSED(op_cg_ctx);
rt_expr.eval_func_ = ObExprHello::eval_hello;
return OB_SUCCESS;
}
} // namespace sql
} // namespace oceanbase
修改添加函数名定义
ob_name_def.h
这里注册了函数名,在语法解析时应该会用到,这部分没有继续撸。
修改工厂类
ob_expr_operator_factory.cpp
主要是注册函数指针,运行时 runtime 会通过函数指针回调具体内建函数。
注册内建函数
添加ID
ob_item_type.h
可以理解为 KEY,只想函数指正。
修改工程文件
CMakeLists.txt
将新建的 ObExprHello 添加到工程中进行编译。
ob_expr_hello.cpp
ob_expr_eval_functions.cpp
一些建议
近2~3个月一直在研究众多国产数据库,包括 OceanBase、TiDB 、OpenGauss/MogDB 、达梦等,OLTP 、OLAP 、HTAP 都有,从使用的角度还是想给 OceanBase 提几个小的建议。 (OB 已经在认真听取意见了
环境搭建相对比较复杂。希望有一个一键搭建 demo 集群的小功能,方便用户快速上手;
提供低配版配置。很吃资源,对于设备简陋的小伙伴想上手,门槛较高,经常遇到一些资源的问题导致部署失败。
关于 OceanBase 如何提供用户扩展接口?虽然听起来有点鸡肋,但是往往可以解决一些企业级的应用的需求,在功能上也会加分。
在企业版中,Oracle 租户驱动接口可以再丰富一些,请参考 python 通过 JayDeBeApi 使用 JDBC 链接 OceanBase 。(https://blog.csdn.net/xk_xx/article/details/123158031)
社区里面的帖子大部分是面向 DBA 的,集中在部署、迁移、应用、性能、运维等场景,这篇内容未必是主流,受众可能也不会太多,但我希望以开源数据库二次开发的角度抛砖引玉。
受限于精力和水平,如有纰漏和错误请及时指正,300w 行核心代码尚需探索的地方还很多!在拿到企业版 OceanBase 后,也会继续分享企业版的使用与迁移经验!
2023届校园招聘正式开启!OceanBase 想和你在这个春天约一场面试
新 Slogan 新征程|OceanBase 海量记录 笔笔算数