Skip to content

Commit cf39099

Browse files
committed
01-03: refine docs
Signed-off-by: Runji Wang <[email protected]>
1 parent acb2c1f commit cf39099

File tree

3 files changed

+266
-11
lines changed

3 files changed

+266
-11
lines changed

docs/src/01-03-create-table.md

Lines changed: 255 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,274 @@
11
# 创建表(`CREATE TABLE`
22

3-
在实现了 Catalog 之后,我们就可以创建第一个数据表。
3+
在实现了 Catalog 之后,我们就可以使用 `CREATE TABLE` 语句创建数据表:
44

5-
在 SQL 语言中,创建表用 `CREATE TABLE` 语句实现。这里就需要引入 Binder 的概念。……
5+
```sql
6+
CREATE TABLE student (
7+
id INTEGER PRIMARY KEY,
8+
name VARCHAR NOT NULL,
9+
age INTEGER
10+
);
11+
```
12+
13+
这一语句除了解析和执行两个步骤以外,还需要做名称的检查,并将它们与数据库内部对象绑定起来。
14+
这些工作一般是由一个叫做 Binder 的模块完成。
15+
16+
在此任务中,我们将实现一个基础的 Binder,同时拓展 Executor 实现相应的执行逻辑,最终支持 `CREATE TABLE` 语句。
617

718
<!-- toc -->
819

920
## 背景知识
1021

1122
### Binder
1223

13-
TODO
24+
Binder 是整个数据库系统中一个不太起眼但又十分重要的模块。
25+
26+
它的作用是将解析后生成的 AST 和 Schema 信息绑定起来,具体包括:
27+
28+
- 检查输入的名称是否合法、是否有重复、有歧义
29+
- 推断表达式的返回值类型,并检查是否合法
30+
- 将输入的名称转换成内部 ID
31+
32+
比如,对于一个简单的创建表的命令:
33+
34+
```sql
35+
CREATE TABLE student (
36+
id INTEGER PRIMARY KEY,
37+
name VARCHAR NOT NULL,
38+
age INTEGER
39+
);
40+
```
41+
42+
Binder 会依次完成以下工作:
43+
44+
1. 由于 SQL 对大小写不敏感,Binder 会首先将所有名称统一成小写。
45+
2. 对于表名 `student`,自动补全省略的 schema 名。假如当前 schema 是 `school`,那么会将其补全成 `school.student`
46+
3. 检查 schema 是否存在,并获取它的 Schema ID。
47+
4. 检查 schema 中是否已经存在名为 `student` 的表。
48+
5. 检查列名 `id` `name` `age` 是否合法、是否有重复。
49+
6. 检查列的属性是否合法,例如不能出现两个 `PRIMARY KEY`
50+
7. 向 AST 中填入绑定后的信息:Schema ID。
51+
52+
对于插入数据的 `INSERT` 语句,例如:
53+
54+
```sql
55+
INSERT INTO student VALUES (1, 'Alice', 18)
56+
```
57+
58+
Binder 需要查询表中每一列的信息,推断表达式的类型,并检查它们是否相符。
59+
换言之,Binder 应该能够识别出以下不合法的插入语句:
60+
61+
```sql
62+
INSERT INTO student VALUES (1) -- 存在未指定的 NOT NULL 值
63+
INSERT INTO student VALUES (1, 'Alice', 'old') -- 类型不匹配
64+
INSERT INTO student VALUES (1, 'Alice', 18+'g') -- 表达式类型不匹配
65+
```
66+
67+
对于更复杂的嵌套查询语句,Binder 还需要根据当前语境,推断出每个名称具体指代哪个对象:
68+
69+
```sql
70+
SELECT name FROM student WHERE sid IN (
71+
-- ^-----------^ student.sid
72+
SELECT sid FROM enrolled WHERE class = 'database'
73+
-- ^----------^ enrolled.sid
74+
)
75+
```
76+
77+
可以看出,Binder 干的都是一些比较繁琐的脏活累活。因此后面我们写的 Binder 代码也会比较冗长并且细节琐碎。
1478

1579
## 任务目标
1680

17-
能够创建数据表,支持以下 SQL:
81+
能够创建数据表,支持以下 SQL 语句
1882

1983
```sql
20-
CREATE TABLE t (a INT)
84+
CREATE TABLE student (
85+
id INTEGER PRIMARY KEY,
86+
name VARCHAR NOT NULL,
87+
age INTEGER
88+
);
89+
```
90+
91+
【练习】支持 `DROP TABLE` 语句,删除数据表:
92+
93+
```sql
94+
DROP TABLE student;
95+
```
96+
97+
【练习】支持 `CREATE SCHEMA` 语句,创建 schema:
98+
99+
```sql
100+
CREATE SCHEMA school;
21101
```
22102

23103
## 整体设计
24104

25-
TODO
105+
在加入 Binder 之后,RisingLight 的整个数据处理流程扩展成了这个样子:
106+
107+
![](img/01-03-mod.svg)
108+
109+
其中 Binder 插在了 Parser 和 Executor 之间。
110+
它会将 Parser 生成的 AST 进行处理后,生成一个新的 AST 交给 Executor,在此过程中需要从 Catalog 读取 Schema 信息。
111+
Executor 拿到绑定后的 AST 去执行,在此过程中可能也会再次修改 Catalog(比如创建一个表)。
112+
113+
在代码结构上,我们可能会新增以下文件:
114+
115+
```
116+
src
117+
├── binder
118+
│ ├── mod.rs
119+
│ └── statement
120+
│ ├── mod.rs
121+
│ ├── create.rs
122+
│ └── select.rs
123+
├── executor
124+
│ ├── mod.rs
125+
│ ├── create.rs
126+
│ └── select.rs
127+
...
128+
```
129+
130+
此外还需要对数据库顶层结构进行修改。
131+
132+
### Bound AST
133+
134+
Binder 模块的主要任务是给 Parser 生成的 AST 绑定必要的信息。
135+
136+
由于我们的 Parser 使用了第三方库,不能在它的 AST 结构上扩展新的属性,所以只能定义新的结构来存放这些信息。
137+
138+
例如对于 `CREATE TABLE` 语句来说,绑定后的 AST 应该具有以下信息:
139+
140+
```rust,no_run
141+
// binder/statement/create.rs
142+
143+
/// A bound `CREATE TABLE` statement.
144+
#[derive(Debug, PartialEq, Clone)]
145+
pub struct BoundCreateTable {
146+
pub schema_id: SchemaId, // schema name 经过向 catalog 查询转换成了 ID
147+
pub table_name: String,
148+
pub columns: Vec<(String, ColumnDesc)>,
149+
}
150+
```
151+
152+
类似地,对于 1.1 中的 `SELECT 1` 语句而言,我们可以只提取出必要的值来保存:
153+
154+
```rust,no_run
155+
// binder/statement/select.rs
156+
157+
use crate::parser::Value;
158+
159+
/// A bound `SELECT` statement.
160+
#[derive(Debug, PartialEq, Clone)]
161+
pub struct BoundSelect {
162+
pub values: Vec<Value>,
163+
}
164+
```
165+
166+
最后,我们需要定义一个 enum 将各种不同类型的语句聚合起来:
167+
168+
```rust,no_run
169+
// binder/mod.rs
170+
171+
/// A bound SQL statement.
172+
#[derive(Debug, PartialEq, Clone)]
173+
pub enum BoundStatement {
174+
CreateTable(BoundCreateTable),
175+
Select(BoundSelect),
176+
}
177+
```
178+
179+
这样,一个 `BoundStatement` 变量就可以表示 Binder 生成的整个 AST 了。
180+
181+
### Binder
182+
183+
接下来,我们实现真正的 `Binder` 对象。它会将 Parser 生成的 AST 转换成一个新的 AST。
184+
由于在绑定过程中会访问 Catalog 的数据,`Binder` 中需要存放一个 Catalog 对象的指针:
185+
186+
```rust,no_run
187+
pub struct Binder {
188+
catalog: Arc<Catalog>,
189+
}
190+
```
191+
192+
我们在 `Binder` 对象上实现各种 `bind` 方法来完成对不同 AST 节点的处理:
193+
194+
```rust,no_run
195+
use crate::parser::{Query, Statement};
196+
197+
impl Binder {
198+
pub fn bind(&mut self, stmt: &Statement) -> Result<BoundStatement, BindError> {
199+
use Statement::*;
200+
match stmt {
201+
CreateTable { .. } => Ok(BoundStatement::CreateTable(self.bind_create_table(stmt)?)),
202+
Query(query) => Ok(BoundStatement::Select(self.bind_select(query)?)),
203+
_ => todo!("bind statement: {:#?}", stmt),
204+
}
205+
}
206+
207+
fn bind_create_table(&mut self, stmt: &Statement) -> Result<BoundCreateTable, BindError> {
208+
// YOUR CODE HERE
209+
}
210+
211+
fn bind_select(&mut self, query: &Query) -> Result<BoundSelect, BindError> {
212+
// YOUR CODE HERE
213+
}
214+
}
215+
```
216+
217+
注意到这些方法都使用了 `&mut self` 签名,这是因为 `Binder` 未来会有内部状态,并且在 bind 过程中还会修改这些状态。
218+
<!-- 例如对于 `SELECT id FROM student` 语句来说,需要首先扫描 FROM 子句记录下表名 `student`,然后访问 `id` 时才能知道它是否是某个表的列。 -->
219+
220+
另外在 bind 过程中还可能产生各种各样的错误,比如名称不存在或者重复等等。
221+
我们将所有可能发生的错误定义在一个 `BindError` 错误类型中(参考 [1.1 错误处理](../01-01-hello-sql.md#错误处理)):
222+
223+
```rust,no_run
224+
/// The error type of bind operations.
225+
#[derive(thiserror::Error, Debug, PartialEq)]
226+
pub enum BindError {
227+
#[error("schema not found: {0}")]
228+
SchemaNotFound(String),
229+
// ...
230+
}
231+
```
232+
233+
至于具体的 bind 逻辑,大家可以参考背景知识中描述的过程尝试自己实现。
234+
235+
### Executor
236+
237+
在 1.1 中我们实现过一个最简单的执行器,它只是一个函数,拿到 AST 后做具体的执行。
238+
现在我们有了更多类型的语句,并且在执行它们的过程中还需要访问 Catalog。
239+
因此和 Binder 类似,我们现在需要将 Executor 也扩展为一个对象:
240+
241+
```rust,no_run
242+
pub struct Executor {
243+
catalog: Arc<Catalog>,
244+
}
245+
```
246+
247+
然后在 `Executor` 上实现各种 `execute` 方法来对不同类型的 AST 节点做执行:
248+
249+
```rust,no_run
250+
/// The error type of execution.
251+
#[derive(thiserror::Error, Debug)]
252+
pub enum ExecuteError {...}
253+
254+
impl Executor {
255+
pub fn execute(&self, stmt: BoundStatement) -> Result<String, ExecuteError> {
256+
match stmt {
257+
BoundStatement::CreateTable(stmt) => self.execute_create_table(stmt),
258+
BoundStatement::Select(stmt) => self.execute_select(stmt),
259+
}
260+
}
261+
262+
fn execute_create_table(&self, stmt: BoundCreateTable) -> Result<String, ExecuteError> {
263+
// YOUR CODE HERE
264+
}
265+
266+
fn execute_select(&self, query: BoundSelect) -> Result<String, BindError> {
267+
// YOUR CODE HERE
268+
}
269+
}
270+
```
271+
272+
我们暂时将 Executor 的返回值设定为 `String` 类型,表示语句的执行结果。
273+
在下一个任务中,我们会实现更具体的内存数据类型 `Array``DataChunk`
274+
到那时,Executor 的输出就是一段真正的数据了。

docs/src/01-intro.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
欢迎来到新手村!
44

55
在这里我们将白手起家,构建出一个能够运行简单 SQL 语句的数据库框架。
6-
在此过程中,我们会从一个 Parser 开始,逐步引入查询引擎所需的 Binder,Planner,Executor 等模块
6+
在此过程中,我们会从一个 SQL 解析器开始,逐步引入查询引擎所需的各个模块
77
最终实现数据库的 3 条基本命令:创建 `CREATE`,插入 `INSERT` 和查找 `SELECT`
88

99
## 世界地图
1010

1111
![](img/world1.svg)
1212

13-
1. HelloSQL:实现最简单的 SQL 解释器。
13+
1. Hello SQL:实现最简单的 SQL 解释器。
1414

1515
2. Catalog:定义 Catalog 相关数据结构。
1616

@@ -20,14 +20,16 @@
2020

2121
5. 插入数据:向表中插入数据,支持 `INSERT VALUES` 语句。
2222

23-
6. 执行计划:实现 Plan Node,支持 `EXPLAIN` 语句。
23+
6. 执行计划:实现执行计划树,支持 `EXPLAIN` 语句。
2424

2525
7. 查询数据:从表中读取数据,支持 `SELECT v FROM t` 语句。
2626

27+
8. 执行引擎:实现向量化模型的执行引擎。
28+
2729
除此之外,还有以下小练习:
2830

29-
8. 删除表:支持 `DROP TABLE` 语句。
31+
1. 删除表:支持 `DROP TABLE` 语句。
3032

31-
9. 创建 Schema:支持 `CREATE SCHEMA` 语句。
33+
2. 创建 Schema:支持 `CREATE SCHEMA` 语句。
3234

3335
从新手村成功毕业以后,我们就有了探索其他世界所需的代码基础。

0 commit comments

Comments
 (0)