前文回顾:实现一个简单的Database1(译文)
译注:cstsck在github维护了一个简单的、类似sqlite的数据库实现,通过这个简单的项目,可以很好的理解数据库是如何运行的。本文是第二篇,主要是实现数据库的前端组件,编译器与虚拟机部分功能
Part 2 世界上最简单的SQL编译器与虚拟机
我们正在实现一个sqlite的克隆版本。sqlite的前端是SQL编译器,编译器用来解析字符串并输出一个内部的表示,叫做字节码。
这些字节码被传到虚拟机(virtual machine),在虚拟机中,字节码将被执行。
SQLite Architecture (https://www.sqlite.org/arch.html)
像这样把事情分成两个步骤(SQL编译和虚拟机)有以下两个优点:
- 减少各个部分的复杂性(例如:虚拟机不用关心输入语句语法错误)
- 允许只编译通用查询一次,然后对生成的字节码进行缓存,以此来提升性能
有了这些想法,让我们来重构主函数,在程序中支持了两个新的关键字:
译注:下面代码中行开头加减号是相对与第一部分(part 1)的实现,增加或者删除的代码。代码对main()重构以适合识别新关键字,在第一部分中,main()函数只能识别“.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) {
- - exit(EXIT_SUCCESS);
- - } else {
- - printf("Unrecognized command '%s'.\n", input_buffer->buffer);
- + if (input_buffer->buffer[0] == '.') {
- + switch (do_meta_command(input_buffer)) {
- + case (META_COMMAND_SUCCESS):
- + continue;
- + case (META_COMMAND_UNRECOGNIZED_COMMAND):
- + printf("Unrecognized command '%s'\n", input_buffer->buffer);
- + continue;
- + }
- }
- +
- + Statement statement;
- + switch (prepare_statement(input_buffer, &statement)) {
- + case (PREPARE_SUCCESS):
- + break;
- + case (PREPARE_UNRECOGNIZED_STATEMENT):
- + printf("Unrecognized keyword at start of '%s'.\n",
- + input_buffer->buffer);
- + continue;
- + }
- +
- + execute_statement(&statement);
- + printf("Executed.\n");
- }
- }
复制代码 非SQL语句,像“.exit”这样的命令被称为“meta-commands”。它们都是以“.”开头,所以我们在一个独立的函数中检查并且处理它们。
译注:在上边代码中使用了单独的if+switch来处理了以“.”开头的“meta-commands”。
接下来,增加一个步骤,将输入行命令转换成内部表示的语句。这是sqlite前端的一个破解版本。
最后,我门将预编译语句传递到execute_statement()函数,这个函数将最终变成我们的虚拟机。
注意我们的两个新函数返回enum(枚举)类型的来表示成功或者失败:- typedef enum {
- META_COMMAND_SUCCESS,
- META_COMMAND_UNRECOGNIZED_COMMAND
- } MetaCommandResult;
- typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;
复制代码 在输入命令行语句无法识别时,打印“Unrecognized statement”输出?这个看起来像是异常(exception)。我不喜欢使用exception(并且C语言甚至不支持exception),所以我在任何可行的地方都是用enum结果码做返回。如果我的switch语句没有处理enum成员,C编译器会报错,所以我们能感到小有信心,我们能处理所有函数结果。预计将来会有更多的结果代码被加入。
do_meta_command()函数只是对已有的功能的一个封装,为更多的命令留出空间:- MetaCommandResult do_meta_command(InputBuffer* input_buffer) {
- if (strcmp(input_buffer->buffer, ".exit") == 0) {
- exit(EXIT_SUCCESS);
- } else {
- return META_COMMAND_UNRECOGNIZED_COMMAND;
- }
- }
复制代码 我们的“prepared statement”现在只包含一个enum(有两个可能值)。在语句中将会包含更多的我们允许的参数数据:- typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
- typedef struct {
- StatementType type;
- } Statement;
复制代码 prepare_statement()函数(我们的SQL编译器)现在还不能理解SQL。事实上,它现在只能理解两个单词:
译注:下面的代码实现了对insert和select关键的解析。- PrepareResult prepare_statement(InputBuffer* input_buffer,
- Statement* statement) {
- if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
- statement->type = STATEMENT_INSERT;
- return PREPARE_SUCCESS;
- }
- if (strcmp(input_buffer->buffer, "select") == 0) {
- statement->type = STATEMENT_SELECT;
- return PREPARE_SUCCESS;
- }
- return PREPARE_UNRECOGNIZED_STATEMENT;
- }
复制代码 注意,因为“insert”关键字后面有跟随数据,所以为“insert”使用了strncmp()库函数来比对输入值。(例如输入语句为:insert 1 cstack foo@bar.com)
译注:C 库函数 int strncmp(const char *str1, const char *str2, size_t n) 是把输入参数 str1 和 str2 进行比较,最多比较入参的前 n 个字节。
最后,execute_statement()函数中包含了一些桩(stubs):
译注:stubs(一小块代码),是为了实现测试代码进行,会硬编码一些输入和输出,即在execute_statement()函数中对prepare_statement()函数处理结果进行了引用并处理。- void execute_statement(Statement* statement) {
- switch (statement->type) {
- case (STATEMENT_INSERT):
- printf("This is where we would do an insert.\n");
- break;
- case (STATEMENT_SELECT):
- printf("This is where we would do a select.\n");
- break;
- }
- }
复制代码 注意这里没有返回任何错误码,这是因为在这里还不会有任何报错发生。
译注:目前为止,程序可解析“.exit”、“insert xxx”、"select xxx"命令,其余不会识别,只输出“Unrecognized command 'xxx'”,所以不会有什么报错输出。参考下面的演示。
做了这些重构后,我们的程序就能识别两个新的关键字了。- ~ ./db
- db > insert foo bar
- This is where we would do an insert.
- Executed.
- db > delete foo
- Unrecognized keyword at start of 'delete foo'.
- db > select
- This is where we would do a select.
- Executed.
- db > .tables
- Unrecognized command '.tables'
- db > .exit
- ~
复制代码 我们的数据库骨架正在形成...如果它能存储数据不是很好吗?在下一部分,我们会实现insert和select,创建世界上最差劲的数据存储。
同时,下面是这部分重构的整个代码不同之处:- @@ -10,6 +10,23 @@ struct InputBuffer_t {
- } InputBuffer;
- +typedef enum {
- + META_COMMAND_SUCCESS,
- + META_COMMAND_UNRECOGNIZED_COMMAND
- +} MetaCommandResult;
- +
- +typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;
- +
- +typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
- +
- +typedef struct {
- + StatementType type;
- +} Statement;
- +
- InputBuffer* new_input_buffer() {
- InputBuffer* input_buffer = malloc(sizeof(InputBuffer));
- input_buffer->buffer = NULL;
- @@ -40,17 +57,67 @@ void close_input_buffer(InputBuffer* input_buffer) {
- free(input_buffer);
- }
- +MetaCommandResult do_meta_command(InputBuffer* input_buffer) {
- + if (strcmp(input_buffer->buffer, ".exit") == 0) {
- + close_input_buffer(input_buffer);
- + exit(EXIT_SUCCESS);
- + } else {
- + return META_COMMAND_UNRECOGNIZED_COMMAND;
- + }
- +}
- +
- +PrepareResult prepare_statement(InputBuffer* input_buffer,
- + Statement* statement) {
- + if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
- + statement->type = STATEMENT_INSERT;
- + return PREPARE_SUCCESS;
- + }
- + if (strcmp(input_buffer->buffer, "select") == 0) {
- + statement->type = STATEMENT_SELECT;
- + return PREPARE_SUCCESS;
- + }
- +
- + return PREPARE_UNRECOGNIZED_STATEMENT;
- +}
- +
- +void execute_statement(Statement* statement) {
- + switch (statement->type) {
- + case (STATEMENT_INSERT):
- + printf("This is where we would do an insert.\n");
- + break;
- + case (STATEMENT_SELECT):
- + printf("This is where we would do a select.\n");
- + break;
- + }
- +}
- +
- 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);
- + if (input_buffer->buffer[0] == '.') {
- + switch (do_meta_command(input_buffer)) {
- + case (META_COMMAND_SUCCESS):
- + continue;
- + case (META_COMMAND_UNRECOGNIZED_COMMAND):
- + printf("Unrecognized command '%s'\n", input_buffer->buffer);
- + continue;
- + }
- }
- +
- + Statement statement;
- + switch (prepare_statement(input_buffer, &statement)) {
- + case (PREPARE_SUCCESS):
- + break;
- + case (PREPARE_UNRECOGNIZED_STATEMENT):
- + printf("Unrecognized keyword at start of '%s'.\n",
- + input_buffer->buffer);
- + continue;
- + }
- +
- + execute_statement(&statement);
- + printf("Executed.\n");
- }
- }
复制代码 Enjoy GreatSQL
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |