Hello OceanBase!开启 OceanBase 二次开发
作者简介:夏克,OceanBase 社区文档贡献者,多年从事金融行业核心系统设计开发工作,服务于某交易所子公司,现阶段负责国产数据库调研,近期考取了OBCA 、PCTA 认证。
引子
最近看到一篇帖子 OceanBase 源码解读(十一):表达式和函数,作者简介:竹翁,毕业于北京大学, OceanBase 内核研发总监。竹翁老师在最后说:“为 OceanBase 添加系统内建函数,是 OceanBase SQL 组很多新人入职第一题”。虽然暂时没有跳槽的打算,但是还是想试一下这道第一题。我本身不是 DBA,平时和数据库打交道的机会也不多,只是最近由于工作原因开始了解一些国产数据库头部产品。就我的领域而言,算是“出圈”了。看了很多各大社区的帖子,参与了一些公开课,也更深刻的感受到国产数据库生态蓬勃发展的趋势,同时也非常感谢那些能把自己积累的经验分享出来的国产 DBer,让我这样的门外汉能够迅速的“入圈”-。所谓“投之以木桃,报之以琼瑶”,正值 OceanBase 社区的征文活动。也希望能把我一些浅薄的经验留给社区,希望更多有需要的 DBer 能看到
概要
Hello OceanBase,看到这标题,你可能会联想到“ Hello World ”,是的!没错!这篇帖子就是 OceanBase 二次开发的“ Hello World ”。通过一个 demo 了解如何添加或修改 OceanBase 内建函数,或者说如何基于 OceanBase 社区版进行二次开发。做这个尝试除了上面提到的竹翁老师的文章外,还源于一个需求,即数据库的外部函数扩展。在 Oracle 中提供了可以调用 C 或者 JAVA 函数的功能——外部函数,调用外部函数方式就像是调用内建函数一样的,从用户的角度没有任何区别。从某种意义上讲,这个功能提升了数据库的扩展性,也可以应对一些业务场景,比如,一些比较复杂的数学算法,通过 SQL 或者 Oracle 内置函数实现比较麻烦,可以作为外部函数通过 C 或者 JAVA 来实现,当然这些算法可以上提到业务层来实现(不抬杠,只讨论这个功能)。之前调研了几款国产数据库也写了一些类似使用外部函数的操作流程达梦DM8数据库实现 Oracle 中的外部函数、postgresql 自定义函数实现,通过 contrib 模块进行扩展 供大家参考。达梦外部函数的基本用法与 Oracle 类似,细节和实现上略有差异;像 OpenGauss/MogDB、TDSQL-PG、人大金仓等,是基于 PostgreSQL 内核的产品,所以天然的继承了postgres 的扩展方式。但 OceanBase 目前应该是没有这样的扩展方式。然而我想一定会有另辟蹊径的方法——不能外部扩展,那么就内部扩展_。让我们从“ Hello OceanBase ”开始。
环境准备
OceanBase 集群
最近看到一篇帖子 OceanBase 源码解读(十一):表达式和函数,作者简介:竹翁,毕业于北京大学, OceanBase 内核研发总监。竹翁老师在最后说:“为 OceanBase 添加系统内建函数,是 OceanBase SQL 组很多新人入职第一题”。虽然暂时没有跳槽的打算,但是还是想试一下这道第一题。我本身不是 DBA,平时和数据库打交道的机会也不多,只是最近由于工作原因开始了解一些国产数据库头部产品。就我的领域而言,算是“出圈”了。看了很多各大社区的帖子,参与了一些公开课,也更深刻的感受到国产数据库生态蓬勃发展的趋势,同时也非常感谢那些能把自己积累的经验分享出来的国产 DBer,让我这样的门外汉能够迅速的“入圈”-。所谓“投之以木桃,报之以琼瑶”,正值 OceanBase 社区的征文活动。也希望能把我一些浅薄的经验留给社区,希望更多有需要的 DBer 能看到
Hello OceanBase,看到这标题,你可能会联想到“ Hello World ”,是的!没错!这篇帖子就是 OceanBase 二次开发的“ Hello World ”。通过一个 demo 了解如何添加或修改 OceanBase 内建函数,或者说如何基于 OceanBase 社区版进行二次开发。做这个尝试除了上面提到的竹翁老师的文章外,还源于一个需求,即数据库的外部函数扩展。在 Oracle 中提供了可以调用 C 或者 JAVA 函数的功能——外部函数,调用外部函数方式就像是调用内建函数一样的,从用户的角度没有任何区别。从某种意义上讲,这个功能提升了数据库的扩展性,也可以应对一些业务场景,比如,一些比较复杂的数学算法,通过 SQL 或者 Oracle 内置函数实现比较麻烦,可以作为外部函数通过 C 或者 JAVA 来实现,当然这些算法可以上提到业务层来实现(不抬杠,只讨论这个功能)。之前调研了几款国产数据库也写了一些类似使用外部函数的操作流程达梦DM8数据库实现 Oracle 中的外部函数、postgresql 自定义函数实现,通过 contrib 模块进行扩展 供大家参考。达梦外部函数的基本用法与 Oracle 类似,细节和实现上略有差异;像 OpenGauss/MogDB、TDSQL-PG、人大金仓等,是基于 PostgreSQL 内核的产品,所以天然的继承了postgres 的扩展方式。但 OceanBase 目前应该是没有这样的扩展方式。然而我想一定会有另辟蹊径的方法——不能外部扩展,那么就内部扩展_。让我们从“ Hello OceanBase ”开始。
环境准备
OceanBase 集群
这块偷个懒吧,社区关于 OceanBase 部署的帖子应该很多,单机的、多机的、单节点的,3节点的。到处可见-_-,这里推荐我之前写的几个方法 OceanBase Docker 部署&使用、手动部署 OceanBase 单节点、离线使用 OBD 本地部署单节点 OceanBase。针对这个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
整个工程需要修改以下几个文件

1.创建 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
2.修改添加函数名定义
- ob_name_def.h
这里注册了函数名,在语法解析时应该会用到,这部分没有继续撸。


3.修改工厂类
ob_expr_operator_factory.cpp
主要是注册函数指针,运行时 runtime 会通过函数指针回调具体内建函数。

- 注册内建函数

4.添加ID
- ob_item_type.h
可以理解为 KEY,只想函数指正。

5.修改工程文件
- CMakeLists.txt
将新建的 ObExprHello 添加到工程中进行编译。

6. ob_expr_hello.cpp
7.ob_expr_eval_functions.cpp

编译
再偷个懒吧,参考之前写的
编译 OceanBase 源代码。
PS.目前最新版的代码有编译错误,即将修改 push request 到 GitHub 上,并已经接受但还没有 merge。
验证
1.替换 observer
/root/observer/bin 目录下的 observer 通过软连接指向 /root/.obd/repository/oceanbase-ce/3.1.2/7fafba0fac1e90cbd1b5b7ae5fa129b64dc63aed/bin 目录,编译出来的 observer提供该目录下的 observer 即可。

2.启动

细心的同学可能会发现 version 是 3.1.3,当前这个版本应该还没发布,是因为我们使用的是社区最新的代码
测试

一些建议
近2~3个月一直在折腾国产数据库包括 OceanBase、TiDB 、OpenGauss/MogDB 、达梦等,OLTP 、OLAP 、HTAP 都有,这里就不做对比了,毕竟严格地讲,我还是个门外汉,不过从使用的角度还是想给咱们 OceanBase 提几个小小的建议。
环境搭建相对比较复杂,毕竟是分布式部署,所以也可以理解,但是应该有一个像 TiDB 的 playground 这样一键搭建 demo 集群的小功能,可以方便用户快速上手;
很吃资源,对于设备简陋的小伙伴想上手,门槛较高,经常遇到一些资源的问题导致部署失败,所以是否可以提供低配版配置;
就是上文提到的关于 OceanBase 如何提供用户扩展接口?可能有人会觉得这是个鸡肋,但是往往可以解决一些企业级的应用的需求,在对标 Oracle 功能上也会加分。
企业版中
Oracle 租户驱动接口可以再丰富一些,请参考 python 通过 JayDeBeApi 使用 JDBC 链接 OceanBase。
后记
社区里面的帖子大部分是面向 DBA 的,集中在部署、迁移、应用,性能,运维等场景,所以这篇内容未必是主流,受众可能也不会太多。以开源数据库二次开发的角度抛砖引玉吧。
受限于精力和水平,如有纰漏和错误请及时指正。300w行核心代码真心是撸不动!
近期会拿到企业版 OceanBase,后续也会继续分享企业版的使用与迁移经验!
————————————————
附录:
练习题:
实践练习一(必选):OceanBase Docker 体验
实践练习二(必选):手动部署 OceanBase 集群
实践练习三(可选):使用OBD 部署一个 三副本OceanBase 集群
实践练习四(必选):迁移 MySQL 数据到 OceanBase 集群
实践练习五(可选):对 OceanBase 做性能测试
实践练习六(必选):查看 OceanBase 执行计划
还没交作业的小伙伴要抓紧啦!
可以免费带走 OBCP 考试券喔~~
方法一:完成四道必选练习
方法二:任意一道练习题 ➕ 结业考试超过80分
已经有很多同学抢先答题了,
加入钉钉群(群号3582 5151),和大家一起学习、交流~~
进群二维码:

