本文作者简介:夏克,多年从事金融行业核心系统设计开发,服务于某交易所子公司,现阶段负责国产数据库调研,近期陆续获得了 OBCA、PCTA、OGCA、DCA、GDCA 等认证。
引言:最近看到一篇帖子OceanBase 源码解读(十一):表达式和函数,
作者简介:竹翁,毕业于北京大学, OceanBase 内核研发总监
。竹翁老师在最后说:“为OceanBase添加系统内建函数,是OceanBase SQL组很多新人入职第一题”。虽然暂时没有跳槽的打算,但是还是想试一下这道第一题。我本身不是DBA,平时和数据库打交道的机会也不多,只是最近由于工作原因开始了解一些国产数据库头部产品。就我的领域而言,算是“出圈”了。看了很多各大社区的帖子,参与了一些公开课,也更深刻的感受到国产数据库生态蓬勃发展的趋势,同时也非常感谢那些能把自己积累的经验分享出来的国产DBer,让我这样的门外汉能够迅速的“入圈”-。所谓“投之以木桃,报之以琼瑶”,正值OB社区的征文活动。也希望能把我一些浅薄的经验留给社区,希望更多有需要的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都有,这里就不做对比了,毕竟严格地讲,我还是个门外汉,不过从使用的角度还是想给咱们OB提几个小小的建议。
- 环境搭建相对比较复杂,毕竟是分布式部署,所以也可以理解,但是应该有一个像TiDB的playground这样一键搭建demo集群的小功能,可以方便用户快速上手;
- 很吃资源,对于设备简陋的小伙伴想上手,门槛较高,经常遇到一些资源的问题导致部署失败,所以是否可以提供低配版配置;
- 就是上文提到的关于OB如何提供用户扩展接口?可能有人会觉得这是个鸡肋,但是往往可以解决一些企业级的应用的需求,在对标Oracle功能上也会加分。
企业版中
Oracle租户驱动接口可以再丰富一些,请参考python通过JayDeBeApi使用JDBC链接OceanBase。
后记
- 社区里面的帖子大部分是面向DBA的,集中在部署、迁移、应用,性能,运维等场景,所以这篇内容未必是主流,受众可能也不会太多。以开源数据库二次开发的角度抛砖引玉吧。
- 受限于精力和水平,如有纰漏和错误请及时指正。300w行核心代码真心是撸不动!
- 近期会拿到企业版OceanBase,后续也会继续分享企业版的使用与迁移经验!
欢迎持续关注 OceanBase 技术社区,我们将不断输出技术干货内容,与千万技术人共同成长!!!
搜索🔍钉钉群(33254054),或扫描下方二维码,还可进入 OceanBase 技术答疑群,有任何技术问题在里面都能找到答案哦~