创建一个简单的数据库:介绍并设置REPL(read-excute-print loop)
这里,只有一个搬运工。如果翻译或是理解有问题,欢迎指正。
作为一个web开发者,在每天的日常工作中,都在使用关系型数据库,但是这些数据库对于使用者来说是一个黑盒子。
为了解决这些问题,才有了从头开始写一个数据库的目标。这里选择以sqlite为模板,相较于mysql或者postgresql,sqlit要更小而具有部分它们的部分功能,这样可以更好的理解数据库。整个数据库存储在一个文件中!
sqlite
在他们的网站上,有很多sqlite内部结构文档,此外还有一份《SQLite Database System: Design and Implementation》(sqlite 数据库系统:设计及实现)
sqlite官方文档网站:https://www.sqlite.org/arch.html

一个查询通过一系列的组件来获取或修改数据。前端组成部分:
- 标记器
- 解析器
- 代码生成器
在前端输入一个sql查询。输出的是sqlite虚拟机字节码(可在数据库运行的已编译程序)。
后端组成:
- 虚拟机
- B树
- pager(寻呼机)
- OS接口
虚拟机把前端生成的字节码当做指令。它可以对一个或者多个表,或索引执行操作,而它们分别存储在一个名为B树的数据结构里。
虚拟机本质上是对字节码指令的一个转换。
每一个B树是由许多的节点组成。每个节点是一页的长度。B树可以通过向pager发送命令来从硬盘上获取一页或是将一页写回硬盘。
pager收到命令,写或是读数据页。它负责在数据库文件里读取或写入合适的偏移量。同时,它还将近期访问的页缓存在内存中,并决定这些页什么时候写回硬盘。
OS接口,随着操作系统不同而不同,它取决于sqlite为哪种操作系统编译。在这个教程中,并不会支持跨平台。
千里之行,始于足下。现在我们从简单的开始:REPL
制作一个简单的REPL
当从命令行启动sqlite时,sqlite会开始一个read-execute-print循环:
~ sqlite3 SQLite version 3.16.0 2016-11-04 19:09:39 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite> create table users (id int, username varchar(255), email varchar(255)); sqlite> .tables users sqlite> .exit ~
要实现这个,主函数需要有一个无限循环:打印提示,获取输入行,并对输入行内容进行处理:
int main(int argc, char* argv[]) { InputBuffer* input_buffer = new_input_buffer(); while (true) { print_prompt(); read_input(input_buffer); if (strcmp(input_buffer->buffer, ".exit") == 0) { close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("Unrecognized command '%s'.\n", input_buffer->buffer); } } }
这里需要定义InputBuffer作为一个小容器,用于存放和getline()交互时,用到的声明(状态)。(更多稍后说明)
typedef struct { char* buffer; size_t buffer_length; ssize_t input_length; } InputBuffer; InputBuffer* new_input_buffer() { InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; input_buffer->buffer_length = 0; input_buffer->input_length = 0; return input_buffer; }
接着,print_prompt()打印一个提示给用户。这个实在读取输入前执行。
void print_prompt() { printf("db > "); }
为了读取输入,使用getline()
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
lineptr:这是一个指针,这个指针指向包含读取内容的buffer。如果它被设置为NULL,那么就由getline对其进行分配,这样也需要由用户来释放,即使命令失败。
n: 指针,指向分配buffer的大小
stream: 被读取的输入流。从标准输入读取。
返回值
读取的字节数,可能比buffer的size要小。
通过getline,实现:将读取内容存放到input_buffer->buffer,同时将buffer的长度存放到input_buffer->buffer_length。而返回值会被存放到input_buffer->input_length。
buffer一开始是NULL,所以geline分配足够的内存来存放输入,并让buffer指向它。
void read_input(InputBuffer* input_buffer) { ssize_t bytes_read = getline(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin); if (bytes_read <= 0) { printf("Error reading input\n"); exit(EXIT_FAILURE); } // Ignore trailing newline input_buffer->input_length = bytes_read - 1; input_buffer->buffer[bytes_read - 1] = 0; }
至此,可以定义一个函数用于释放Inputbuffer *实例占用的内存,以及各个结构的缓存元素(geline通过read_input, 分配input_buffer->buffer内存).
void close_input_buffer(InputBuffer* input_buffer) { free(input_buffer->buffer); free(input_buffer); }
最后,需要解析并执行命令。目前只有一个可识别的命令:.exit, 用于结束程序。此外需要打一个错误信息以及让循环继续。
if (strcmp(input_buffer->buffer, ".exit") == 0) { close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("Unrecognized command '%s'.\n", input_buffer->buffer); }
现在可以进行测试!
~ ./db db > .tables Unrecognized command '.tables'. db > .exit ~
好的,现在已经得到了一个可执行的REPL。下一节,开始开发命令。同时,下面是这个小节的全部代码:
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char* buffer; size_t buffer_length; ssize_t input_length; } InputBuffer; InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; input_buffer->buffer_length = 0; input_buffer->input_length = 0; return input_buffer; } void print_prompt() { printf("db > "); } void read_input(InputBuffer* input_buffer) { ssize_t bytes_read = getline(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin); if (bytes_read <= 0) { printf("Error reading input\n"); exit(EXIT_FAILURE); } // Ignore trailing newline input_buffer->input_length = bytes_read - 1; input_buffer->buffer[bytes_read - 1] = 0; } void close_input_buffer(InputBuffer* input_buffer) { free(input_buffer->buffer); free(input_buffer); } int main(int argc, char* argv[]) { InputBuffer* input_buffer = new_input_buffer(); while (true) { print_prompt(); read_input(input_buffer); if (strcmp(input_buffer->buffer, ".exit") == 0) { close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("Unrecognized command '%s'.\n", input_buffer->buffer); } } }

浙公网安备 33010602011771号