diff --git a/README.md b/README.md index 4d96a43650d8e06a385cb597467f06ff4e08a8b3..e96294b9c926d62518ef88233eda513f85f90d20 100644 --- a/README.md +++ b/README.md @@ -6,56 +6,38 @@ miniob设计的目标是让不熟悉数据库设计和实现的同学能够快 [GitHub 首页](https://github.com/oceanbase/miniob) -# How to build -直接在本地搭建开发环境,可以参考 [how_to_build](docs/how_to_build.md)。 - -# Docker - -首先要确保本地已经安装了Docker。 - -- 使用 Dockerfile 构建 +# 如何开发 +## 搭建开发环境 +有多种方式搭建开发环境,可以直接在本地安装一些三方依赖,或者使用Docker。如果使用的是Windows,我们建议使用Docker来开发。 -Dockerfile: https://github.com/oceanbase/miniob/blob/main/Dockerfile - -```bash -# build -docker build -t miniob . -docker run --privileged -d --name='miniob' miniob:latest - -# 进入容器,开发调试 -docker exec -it miniob bash -``` +### 搭建本地开发环境 +直接在本地搭建开发环境,可以参考 [how_to_build](docs/how_to_build.md)。 -- 使用docker hub 镜像运行 +### 使用Docker开发 -```bash -docker run oceanbase/miniob -``` +请参考 [如何使用Docker开发MiniOB](docs/how-to-dev-using-docker.md) -Docker环境说明: -docker基于`CentOS:7`制作。 +### Windows上开发MiniOB -镜像包含: +[如何在Windows上使用Docker开发miniob](docs/how_to_dev_miniob_by_docker_on_windows.md) -- jsoncpp -- google test -- libevent -- flex -- bison(3.7) -- gcc/g++ (version=11) -- miniob 源码(/root/source/miniob) +## 词法语法解析开发环境 -docker中在/root/source/miniob目录下载了github的源码,可以根据个人需要,下载自己仓库的源代码,也可以直接使用git pull 拉取最新代码。 -/root/source/miniob/build.sh 提供了一个编译脚本,以DEBUG模式编译miniob。 +如果已经在处理一些SQL词法语法解析相关的问题,请参考 [MiniOB 词法语法解析开发与测试](docs/miniob-sql-parser.md)。 +Docker 环境已经预安装了相关的组件。 # 数据库管理系统实现基础讲义 由华中科技大学谢美意和左琼老师联合编撰数据库管理系统实现教材。参考 [数据库管理系统实现基础讲义](docs/lectures/index.md) # miniob 介绍 -[miniob代码架构框架设计和说明](https://github.com/OceanBase-Partner/lectures-on-dbms-implementation/blob/main/miniob-introduction.md) +[miniob代码架构框架设计和说明](docs/miniob-introduction.md) + +# miniob 训练 +我们为MiniOB设计了配套的训练题目,大家可以在 [MiniOB 训练营](https://open.oceanbase.com/train?questionId=200001) 上进行提交测试。 -# miniob 题目 -[miniob 题目](docs/miniob_topics.md) +[miniob 题目描述](docs/miniob_topics.md) + +为了满足训练营或比赛测试要求,代码的输出需要满足一定要求,请参考 [MiniOB 输出约定](docs/miniob-output-convention.md)。一般情况下,不需要专门来看这篇文档,但是如果你的测试总是不正确,建议对照一下输出约定。 # miniob 实现解析 @@ -66,8 +48,3 @@ docker中在/root/source/miniob目录下载了github的源码,可以根据个 [miniob select-tables 实现解析](https://oceanbase-partner.github.io/lectures-on-dbms-implementation/miniob-select-tables-implementation.html) [miniob 调试篇](https://oceanbase-partner.github.io/lectures-on-dbms-implementation/miniob-how-to-debug.html) - -# Windows - -[如何在Windows上使用Docker开发miniob](docs/how_to_dev_miniob_by_docker_on_windows.md) - diff --git a/docs/how-to-dev-using-docker.md b/docs/how-to-dev-using-docker.md new file mode 100644 index 0000000000000000000000000000000000000000..ec935b0ce80292b1eac92fb8ea33b58494e49cc5 --- /dev/null +++ b/docs/how-to-dev-using-docker.md @@ -0,0 +1,43 @@ +本篇文章介绍如何使用Docker来开发MiniOB。 + +MiniOB 依赖的第三方组件比较多,搭建开发环境比较繁琐,建议同学们直接使用我们提供的Docker环境进行开发。 + +首先要确保本地已经安装了Docker。 +如果对Docker还不太熟悉,可以先在网上大致了解一下。 + +我们提供了原始的Dockerfile,也有已经打包好的镜像,可以选择自己喜欢的方式。 + +- 使用 Dockerfile 构建 + +Dockerfile: https://github.com/oceanbase/miniob/blob/main/Dockerfile + +```bash +# build +docker build -t miniob . +docker run --privileged -d --name='miniob' miniob:latest + +# 进入容器,开发调试 +docker exec -it miniob bash +``` + +- 使用docker hub 镜像运行 + +```bash +docker run oceanbase/miniob +``` + +Docker环境说明: +docker基于`CentOS:7`制作。 + +镜像包含: + +- jsoncpp +- google test +- libevent +- flex +- bison(3.7) +- gcc/g++ (version=11) +- miniob 源码(/root/source/miniob) + +docker中在/root/source/miniob目录下载了github的源码,可以根据个人需要,下载自己仓库的源代码,也可以直接使用git pull 拉取最新代码。 +/root/source/miniob/build.sh 提供了一个编译脚本,以DEBUG模式编译miniob。 \ No newline at end of file diff --git a/docs/images/miniob-introduction-running-the-client.png b/docs/images/miniob-introduction-running-the-client.png new file mode 100644 index 0000000000000000000000000000000000000000..b0fc2c98ed64b6b9bb9de0fb32fd151edbf8f7a0 Binary files /dev/null and b/docs/images/miniob-introduction-running-the-client.png differ diff --git a/docs/images/miniob-introduction-running-the-server.png b/docs/images/miniob-introduction-running-the-server.png new file mode 100644 index 0000000000000000000000000000000000000000..3a4b2ae1fe611d0a456b099b0ad51425b659dc4d Binary files /dev/null and b/docs/images/miniob-introduction-running-the-server.png differ diff --git a/docs/images/miniob-introduction-sql-flow.png b/docs/images/miniob-introduction-sql-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..436ab6bdec4904c4261bcdfb69c7561b8759b55e Binary files /dev/null and b/docs/images/miniob-introduction-sql-flow.png differ diff --git a/docs/miniob-introduction.md b/docs/miniob-introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..064875800d206f296e3a0ec058c2e522486b0baf --- /dev/null +++ b/docs/miniob-introduction.md @@ -0,0 +1,204 @@ +# OceanBase大赛miniob代码架构框架设计和说明 + +# miniob代码结构说明 + +## 背景 +miniob设计的目标是让不熟悉数据库设计和实现的同学能够快速的了解与深入学习数据库内核,期望通过miniob相关训练之后,能够对各个数据库内核模块的功能与它们之间的关联有所了解,并能够在使用时,设计出高效的SQL。面向的对象主要是在校学生,并且诸多模块做了简化,比如不考虑并发操作。 + +## miniob结构 +miniob作为一个具有“基本”功能的数据库,包含了需要的基本功能模块。包括 + +- 网络模块:负责与客户端交互,收发客户端请求与应答; + +- SQL解析:将用户输入的SQL语句解析成语法树; + +- 执行计划缓存:执行计划缓存模块会将该 SQL第一次生成的执行计划缓存在内存中,后续的执行可以反复执行这个计划,避免了重复查询优化的过程(未实现)。 + +- 语义解析模块:将生成的语法树,转换成数据库内部数据结构(部分实现); + +- 查询缓存:将执行的查询结果缓存在内存中,下次查询时,可以直接返回(未实现); + +- 查询优化:根据一定规则和统计数据,调整/重写语法树。当前实现为空,留作实验题目; + +- 计划执行:根据语法树描述,执行并生成结果; + +- 会话管理:管理用户连接、调整某个连接的参数; + +- 元数据管理:记录当前的数据库、表、字段和索引元数据信息; + +- 客户端:作为测试工具,接收用户请求,向服务端发起请求。 + + +SQL的执行流程可以参考 + +![SQL 请求执行流程](images/miniob-introduction-sql-flow.png)。 + +## 各模块工作原理介绍 +### seda异步事件框架 + +miniob使用了seda框架,在介绍其它模块之前有必要先了解一下seda。 +SEDA全称是:stage event driver architecture,它旨在结合事件驱动和多线程模式两者的优点,从而做到易扩展,解耦合,高并发。 +各个stage之间的通信由event来传递,event的处理由stage的线程池异步处理。线程池内部会维护一个事件队列。 +在miniob中,从接收请求开始,到SQL解析、查询优化、计划执行都使用event来传递数据,并且可以通过seda来配置线程池的个数。 + +### 服务端启动过程 + +虽然代码是模块化的,并且面向对象设计思想如此流行,但是很多同学还是喜欢从main函数看起。那么就先介绍一下服务端的启动流程。 + +main函数参考 main@src/observer/main.cpp。启动流程大致如下: + +解析命令行参数 parse_parameter@src/observer/main.cpp + +加载配置文件 Ini::load@deps/common/conf/ini.cpp + +初始化日志 init_log@src/observer/init.cpp + +初始化seda init_seda@src/observer/init.cpp + +初始化网络服务 init_server@src/observer/main.cpp + +启动网络服务 Server::serve@src/net/server.cpp + +建议把精力更多的留在核心模块上,以更快的了解数据库的工作。 + + +### 网络模块 +网络模块代码参考src/observer/net,主要是Server类。 +在这里,采用了libevent作为网络IO工具。libevent的工作原理可以参考libevent官方网站。 +网络服务启动时,会监听端口,接受到新的连接,会将新的连接描述字加入到libevent中。在有网络事件到达时(一般期望是新的消息到达),libevent会调用我们注册的回调函数(参考Server::recv@src/observer/net/server.cpp)。当连接接收到新的消息时,我们会创建一个SessionEvent(参考seda中的事件概念),然后交由seda调度。 + +### SQL解析 +SQL解析模块是接收到用户请求,开始正式处理的第一步。它将用户输入的数据转换成内部数据结构,一个语法树。 +解析模块的代码在`src/observer/sql/parser`下,其中`lex_sql.l`是词法解析代码,`yacc_sql.y`是语法解析代码,`parse_defs.h`中包含了语法树中各个数据结构。 +对于词法解析和语法解析,原理概念可以参考《编译原理》。 +其中词法解析会把输入(这里比如用户输入的SQL语句)解析成成一个个的“词”,称为token。解析的规则由自己定义,比如关键字SELECT,或者使用正则表达式,比如`"[A-Za-z_]+[A-Za-z0-9_]*"` 表示一个合法的标识符。 +对于语法分析,它根据词法分析的结果(一个个token),按照编写的规则,解析成“有意义”的“话”,并根据这些参数生成自己的内部数据结构。比如`SELECT * FROM T`,可以据此生成一个简单的查询语法树,并且知道查询的`columns`是"*",查询的`relation`是"T"。 +NOTE:在查询相关的地方,都是用关键字relation、attribute,而在元数据中,使用table、field与之对应。 + +### 计划执行 +在miniob的实现中,SQL解析之后,就直接跳到了计划执行,中间略去了很多重要的阶段,但是不影响最终结果。 +计划执行的代码在`src/observer/sql/executor/`下,主要参考`execute_stage.cpp`的实现。 + +### seda编程注意事项 +seda使用异步事件的方式,在线程池中调度。每个事件(event),再每个阶段完成处理后,都必须调用done接口。比如 + +- event->done(); // seda异步调用event的善后处理 + +- event->done_immediate(); // seda将直接在当前线程做event的删除处理 + +- event->done_timeout(); // 一般不使用 + +当前Miniob为了方便和简化,都执行`event->done_immediate`。 + +在event完成之后,seda会调用event的回调函数。通过 `event->push_callback` 放置回调函数,在event完成后,会按照`push_callback`的反向顺序调用回调函数。 +注意,如果执行某条命令后,长时间没有返回结果,通过pstack也无法找到执行那条命令的栈信息,就需要检查下,是否有event没有调用done操作。 +当前的几种event流程介绍: + +`recv@server.cpp`接收到用户请求时创建`SessionEvent`并交给`SessionStage` + +`SessionStage`处理`SessionEvent`并创建`SQLStageEvent`,流转-> + +`ParseStage` 处理 `SQLStageEvent` 流转到-> + +`ResolveStage` 流转 `SQLStageEvent` -> + +`QueryCacheStage` 流转 `SQLStageEvent` -> + +`PlanCacheStage` 流转 `SQLStageEvent` -> + +`OptimizeStage` 流转 `ExecutionPlanEvent` -> + +`ExecuteStage` 处理 `ExecutionPlanEvent` 并创建 `StorageEvent`,流转到-> + +`DefaultStorageStage` 处理 `StorageEvent` + +### 元数据管理模块 +元数据是指数据库一些核心概念,包括db、table、field、index等,记录它们的信息。比如db,记录db文件所属目录;field,记录字段的类型、长度、偏移量等。这里的代码现在没有单独拆分出来,目前位于`src/observer/storage/common`中,文件名中包含`meta`关键字。 + +### 客户端 +这里的客户端提供了一种测试miniob的方法。从标准输入接收用户输入,将请求发给服务端,并展示返回结果。这里简化了输入的处理,用户输入一行,就认为是一个命令。 + +### 通信协议 +miniob采用TCP通信,纯文本模式,使用'\0'作为每个消息的终结符。 +注意:测试程序也使用这种方法,***请不要修改协议,后台测试程序依赖这个协议***。 +注意:返回的普通数据结果中不要包含'\0',也不支持转义处理。 + +### 参赛/训练营建议 +在做miniob的题目时,不要做一个题目再看下一个题目,团队中多个同学分别做自己的题目时,也不要一直单独作战,因为完成课题时,需要修改的模块会有非常多的重叠,因此建议团队尽量统筹规划,避免代码冲突以及“越走越难”。 + +# 参考 +- 《数据库系统实现》 +- 《数据库系统概念》 +- 《flex_bison》 flex/bison手册 +- [flex开源源码](https://github.com/westes/flex) +- [bison首页](https://www.gnu.org/software/bison/) +- [cmake官方手册](https://cmake.org/) +- [libevent官网](https://libevent.org/) +- [SEDA wiki百科](https://en.wikipedia.org/wiki/Staged_event-driven_architecture) +- [OceanBase数据库文档](https://www.oceanbase.com/docs) +- [OceanBase开源网站](https://github.com/oceanbase/oceanbase) + +# 附录-编译安装测试 +## 编译环境 +miniob使用cmake管理,要求cmake版本至少3.10,编译的C++标准是C++14,所以使用的编译器需要支持C++14。 + +编译器推荐使用gcc或clang,使用Windows操作系统的同学,建议使用Linux虚拟机或docker编译,程序会最终在Linux操作系统上测试。 + +使用MacOS的同学,注意默认编译器是clang,即使使用命令gcc,实际编译器可能也是clang。 + +miniob 的开发环境依赖的组件比较多,搭建开发环境可能需要比较多的时间,同学们可以直接使用Docker来开发,具体参考 [MiniOB Docker 开发](https://hub.docker.com/r/oceanbase/miniob) + +NOTE:clang编译器有些表现与gcc不一致,官方测试后台使用gcc,如果后续测试出现编译错误,可以更换为gcc测试。 + +## 编译 +参考源码中 [如何构建MiniOB](./how_to_build.md) 文件。 + +## 运行服务端 +编译完成后,执行 +```bash +observer -s miniob.sock -f observer.ini +``` +可以运行服务端程序。 + +其中 +- -f表示参数`observer.ini`是配置文件。目前提供了样本配置文件,在源码的etc目录下 +- -s 表示使用unix socket 来进行测试。可以指定自己的文件名。 +- -p参数,可以覆盖配置文件中指定的端口号,注意修改代码时不要调整这个参数。 +训练营和测试后端会使用unix socket 来进行测试。 + +示例: + +![running-the-server](images/miniob-introduction-running-the-server.png) + +## 运行客户端 + +默认直接执行obclient即可。obclient从标准输入中接受输入,每收到一行数据,就向服务端发送请求,并等待应答。 + +可以通过命令行参数修改客户端连接的服务端信息: + +```bash +obclient [ip] [port] +``` + +或者 + +```bash +obclient -s miniob.sock +``` + +其中 -s 使用指定unix socket 文件连接observer,如果启动observer时也指定了unix socket。 + +![running-the-client](images/miniob-introduction-running-the-client.png) + +# FAQ + +- 命令没有加分号解析失败,比如 `help`命令 + +miniob 是功能非常简单的数据库,语法解析也做得非常简单。所有的命令都需要加上分号`;`。 + +- YYYY-m-d是否是正确的日期 +比如 2021-1-2 是否正确日期?是正确的。 +> 输出的时候要求一定要是 YYYY-mm-dd输出(2021-01-02),但是输入的时候,没有那么严格要求。 + +- 增加CPP/H 文件是否需要更新CMakeLists.txt +不需要。Cmake文件中使用自动探测的方式。加了新的源代码,再执行一遍 cmake .. 就可以自动探测到新加的源文件。 \ No newline at end of file diff --git a/docs/miniob-output-convention.md b/docs/miniob-output-convention.md new file mode 100644 index 0000000000000000000000000000000000000000..285c9ebc62e21c36e375d9abb0e170690be6b47b --- /dev/null +++ b/docs/miniob-output-convention.md @@ -0,0 +1,31 @@ + +对于比赛和训练营测试后台,需要做一些输出约定,才能正确的进行测试。 + +*** 注意: 后台测试环境,依赖本章节的输出要求。如果输出格式不满足要求,有些case将无法通过*** + +输出是指服务端返回给客户端的数据。为了可以做测试,需要对输出的格式做约定。 +NOTE:后台测试程序,是将预先编辑好的Case执行后,将执行结果与预期输出结果(预先编写完成)做对比,与mysql test工作原理类似,因此需要严格按照输出约束来输出。 + +1. 语法解析错误,返回 FAILURE(只返回这个字符串,不带任何多余字符)。 +2. 对于DML和DDL操作,执行成功返回SUCCESS,失败返回FAILURE。更新和删除操作时没有数据变更,只要没有错误,输出也是SUCCESS。 +3. 对于QUERY操作,如果执行失败,返回FAILURE(包括语法错误)。否则按照下面的格式要求输出:列名显示和顺序说明: + - 单表查询,没有指定列名(select * from t) ,按照建表语句的顺序列出列名,列名不需要带表明 + - 单表查询,指定了列名,按照指定的顺序输出列名,列名不需要带表名 + - 表查询,没有指定列名(select * from t,t1),列名需要带表明,使用'.'分开。每张表的列名与建表时顺序保持一致,多张表按照from后指定的顺序依次排列 + - 多表查询,指定列名,就按照指定的顺序排列 + - 多表查询,有些指定列名,有些没有指定(select t1.*, t2.id from t1,t2)。没有指定的与建表时保持一致,否则按照指定的顺序排列 + 输出格式: + 列名之间使用 ' | '分开,注意 '|'左右各有一个空格。输出列名后,第二行开始输出列值,值之间也使用' | '隔开。 + 注意:第一列和最后一列没有分隔符,也没有空格 + 如果没有数据,显示列名即可。 + - 聚合函数字段输出,保留与输入相似的格式。比如select max(age) from t; 那么输出时,列名输出max(age)。注意,圆括号内没有空格。 + +4. 所有输出不区分大小写 +5. 日期(date)输出格式使用:"YYYY-mm-DD" +6. 输出的字符串不使用单引号双引号或其它括起来 +7. 浮点数输出,不要带后面多余的0,可以参考C sprintf的%g格式输出,保留两位小数。 + +FAQ +- 某张表或者某个查询结果一行数据都没有,但是依然需要输出表头信息 +- 查询语句输入的字段名带了表名,比如select t.id from t; ,因为只有一张表,还是仅输出字段名称 +- 测试不考察大小写,所以输入输出都不区分大小写 \ No newline at end of file diff --git a/docs/miniob-sql-parser.md b/docs/miniob-sql-parser.md new file mode 100644 index 0000000000000000000000000000000000000000..1516a5f8a9b131c78f7ffb30bf631ad00cbcc648 --- /dev/null +++ b/docs/miniob-sql-parser.md @@ -0,0 +1,57 @@ +这部分内容会介绍一些如何对miniob中的词法语法分析模块进行开发与调试,以及依赖的工具。 + +# 如何编译词法分析和语法分析模块 + +词法分析代码lex_sql.l使用下面的命令生成C代码: + +```bash +flex --header-file=lex.yy.h lex_sql.l +``` + +生成 `lex.yy.c` 和`lex.yy.h`文件。 + +语法分析代码yacc_sql.y使用下面的命令生成C代码: + +```bash +bison -d -b yacc_sql yacc_sql.y +``` + +将会生成代码yacc_sql.tab.c和yacc_sql.tab.h。 + +其中-b表示生成的源码文件前缀,-d表示生成一个头文件。 + +注意:flex 使用 2.5.35 版本测试通过,bison使用**3.7**版本测试通过(请不要使用旧版本,比如macos自带的bision)。 + +注意:当前没有把lex_sql.l和yacc_sql.y加入CMakefile.txt中,所以修改这两个文件后,需要手动生成c代码,然后再执行编译。 + +# 如何调试词法分析和语法分析模块 + +对于lex_sql.l,参考代码中的YYDEBUG宏,可以在lex_sql.l中增加调试代码和开启宏定义,也可以在编译时定义这个宏,比如直接修改lex.yy.c代码,在代码前面增加#define YYDEBUG 1。注意,lex.yy.c是自动生成代码,执行flex命令后,会把之前的修改覆盖掉。示例: + +```c++ +#include "yacc_sql.tab.h" +extern int atoi(); +extern double atof(); +char * position = ""; +#define YYDEBUG 1 // 可以在这里定义 YYDEBUG宏 +#ifdef YYDEBUG +#define debug_printf printf // 也可以调整lex_sql.l代码,在定义YYDEBUG宏的时候,做更多事情 +#else +#define debug_printf(...) +#endif // YYDEBUG +``` + +对于yacc_sql.y,可以在yyerror中输出错误信息,或者直接使用调试工具设置断点跟踪。 + +# 如何手动安装bison +1. 下载一个合适版本的bison源码 [下载链接](http://ftp.gnu.org/gnu/bison/),比如 [bison-3.7.tar.gz](http://ftp.gnu.org/gnu/bison/bison-3.7.tar.gz) + +2. 在本地解压。`tar xzvf bison-3.7.tar.gz`,然后进入bison-3.7: `cd bison-3.7`; + +3. 执行 `./configure --prefix="your/bison/install/path"` + +4. 执行 make install + +5. 安装完成 + +注意: 安装后的Bison在指定的安装目录的bin下,如果不调整PATH环境变量,无法直接使用到最新编译的bison二进制文件,需要写全路径使用,比如 your/bison/install/path/bin/bison。