实现一个简单的Database2(译文)

打印 上一主题 下一主题

主题 856|帖子 856|积分 2568

前文回顾:实现一个简单的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”关键字,也就是程序退出命令。
  1. int main(int argc, char* argv[]) {
  2.   InputBuffer* input_buffer = new_input_buffer();
  3.   while (true) {
  4.     print_prompt();
  5.     read_input(input_buffer);
  6. -   if (strcmp(input_buffer->buffer, ".exit") == 0) {
  7. -     exit(EXIT_SUCCESS);
  8. -   } else {
  9. -     printf("Unrecognized command '%s'.\n", input_buffer->buffer);
  10. +     if (input_buffer->buffer[0] == '.') {
  11. +       switch (do_meta_command(input_buffer)) {
  12. +         case (META_COMMAND_SUCCESS):
  13. +           continue;
  14. +         case (META_COMMAND_UNRECOGNIZED_COMMAND):
  15. +           printf("Unrecognized command '%s'\n", input_buffer->buffer);
  16. +           continue;
  17. +       }
  18.       }
  19. +
  20. +     Statement statement;
  21. +     switch (prepare_statement(input_buffer, &statement)) {
  22. +       case (PREPARE_SUCCESS):
  23. +         break;
  24. +       case (PREPARE_UNRECOGNIZED_STATEMENT):
  25. +         printf("Unrecognized keyword at start of '%s'.\n",
  26. +               input_buffer->buffer);
  27. +         continue;
  28. +     }
  29. +
  30. +     execute_statement(&statement);
  31. +     printf("Executed.\n");
  32.    }
  33. }
复制代码
非SQL语句,像“.exit”这样的命令被称为“meta-commands”。它们都是以“.”开头,所以我们在一个独立的函数中检查并且处理它们。
译注:在上边代码中使用了单独的if+switch来处理了以“.”开头的“meta-commands”。
接下来,增加一个步骤,将输入行命令转换成内部表示的语句。这是sqlite前端的一个破解版本。
最后,我门将预编译语句传递到execute_statement()函数,这个函数将最终变成我们的虚拟机。
注意我们的两个新函数返回enum(枚举)类型的来表示成功或者失败:
  1. typedef enum {
  2.   META_COMMAND_SUCCESS,
  3.   META_COMMAND_UNRECOGNIZED_COMMAND
  4. } MetaCommandResult;
  5. typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;
复制代码
在输入命令行语句无法识别时,打印“Unrecognized statement”输出?这个看起来像是异常(exception)。我不喜欢使用exception(并且C语言甚至不支持exception),所以我在任何可行的地方都是用enum结果码做返回。如果我的switch语句没有处理enum成员,C编译器会报错,所以我们能感到小有信心,我们能处理所有函数结果。预计将来会有更多的结果代码被加入。
do_meta_command()函数只是对已有的功能的一个封装,为更多的命令留出空间:
  1. MetaCommandResult do_meta_command(InputBuffer* input_buffer) {
  2.   if (strcmp(input_buffer->buffer, ".exit") == 0) {
  3.     exit(EXIT_SUCCESS);
  4.   } else {
  5.     return META_COMMAND_UNRECOGNIZED_COMMAND;
  6.   }
  7. }
复制代码
我们的“prepared statement”现在只包含一个enum(有两个可能值)。在语句中将会包含更多的我们允许的参数数据:
  1. typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
  2. typedef struct {
  3.   StatementType type;
  4. } Statement;
复制代码
prepare_statement()函数(我们的SQL编译器)现在还不能理解SQL。事实上,它现在只能理解两个单词:
译注:下面的代码实现了对insert和select关键的解析。
  1. PrepareResult prepare_statement(InputBuffer* input_buffer,
  2.                                 Statement* statement) {
  3.   if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
  4.     statement->type = STATEMENT_INSERT;
  5.     return PREPARE_SUCCESS;
  6.   }
  7.   if (strcmp(input_buffer->buffer, "select") == 0) {
  8.     statement->type = STATEMENT_SELECT;
  9.     return PREPARE_SUCCESS;
  10.   }
  11.   return PREPARE_UNRECOGNIZED_STATEMENT;
  12. }
复制代码
注意,因为“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()函数处理结果进行了引用并处理。
  1. void execute_statement(Statement* statement) {
  2.   switch (statement->type) {
  3.     case (STATEMENT_INSERT):
  4.       printf("This is where we would do an insert.\n");
  5.       break;
  6.     case (STATEMENT_SELECT):
  7.       printf("This is where we would do a select.\n");
  8.       break;
  9.   }
  10. }
复制代码
注意这里没有返回任何错误码,这是因为在这里还不会有任何报错发生。
译注:目前为止,程序可解析“.exit”、“insert xxx”、"select xxx"命令,其余不会识别,只输出“Unrecognized command 'xxx'”,所以不会有什么报错输出。参考下面的演示。
做了这些重构后,我们的程序就能识别两个新的关键字了。
  1. ~ ./db
  2. db > insert foo bar
  3. This is where we would do an insert.
  4. Executed.
  5. db > delete foo
  6. Unrecognized keyword at start of 'delete foo'.
  7. db > select
  8. This is where we would do a select.
  9. Executed.
  10. db > .tables
  11. Unrecognized command '.tables'
  12. db > .exit
  13. ~
复制代码
我们的数据库骨架正在形成...如果它能存储数据不是很好吗?在下一部分,我们会实现insert和select,创建世界上最差劲的数据存储。
同时,下面是这部分重构的整个代码不同之处:
  1. @@ -10,6 +10,23 @@ struct InputBuffer_t {
  2. } InputBuffer;
  3. +typedef enum {
  4. +  META_COMMAND_SUCCESS,
  5. +  META_COMMAND_UNRECOGNIZED_COMMAND
  6. +} MetaCommandResult;
  7. +
  8. +typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;
  9. +
  10. +typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
  11. +
  12. +typedef struct {
  13. +  StatementType type;
  14. +} Statement;
  15. +
  16. InputBuffer* new_input_buffer() {
  17.    InputBuffer* input_buffer = malloc(sizeof(InputBuffer));
  18.    input_buffer->buffer = NULL;
  19. @@ -40,17 +57,67 @@ void close_input_buffer(InputBuffer* input_buffer) {
  20.      free(input_buffer);
  21. }
  22. +MetaCommandResult do_meta_command(InputBuffer* input_buffer) {
  23. +  if (strcmp(input_buffer->buffer, ".exit") == 0) {
  24. +    close_input_buffer(input_buffer);
  25. +    exit(EXIT_SUCCESS);
  26. +  } else {
  27. +    return META_COMMAND_UNRECOGNIZED_COMMAND;
  28. +  }
  29. +}
  30. +
  31. +PrepareResult prepare_statement(InputBuffer* input_buffer,
  32. +                                Statement* statement) {
  33. +  if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
  34. +    statement->type = STATEMENT_INSERT;
  35. +    return PREPARE_SUCCESS;
  36. +  }
  37. +  if (strcmp(input_buffer->buffer, "select") == 0) {
  38. +    statement->type = STATEMENT_SELECT;
  39. +    return PREPARE_SUCCESS;
  40. +  }
  41. +
  42. +  return PREPARE_UNRECOGNIZED_STATEMENT;
  43. +}
  44. +
  45. +void execute_statement(Statement* statement) {
  46. +  switch (statement->type) {
  47. +    case (STATEMENT_INSERT):
  48. +      printf("This is where we would do an insert.\n");
  49. +      break;
  50. +    case (STATEMENT_SELECT):
  51. +      printf("This is where we would do a select.\n");
  52. +      break;
  53. +  }
  54. +}
  55. +
  56. int main(int argc, char* argv[]) {
  57.    InputBuffer* input_buffer = new_input_buffer();
  58.    while (true) {
  59.      print_prompt();
  60.      read_input(input_buffer);
  61. -    if (strcmp(input_buffer->buffer, ".exit") == 0) {
  62. -      close_input_buffer(input_buffer);
  63. -      exit(EXIT_SUCCESS);
  64. -    } else {
  65. -      printf("Unrecognized command '%s'.\n", input_buffer->buffer);
  66. +    if (input_buffer->buffer[0] == '.') {
  67. +      switch (do_meta_command(input_buffer)) {
  68. +        case (META_COMMAND_SUCCESS):
  69. +          continue;
  70. +        case (META_COMMAND_UNRECOGNIZED_COMMAND):
  71. +          printf("Unrecognized command '%s'\n", input_buffer->buffer);
  72. +          continue;
  73. +      }
  74.      }
  75. +
  76. +    Statement statement;
  77. +    switch (prepare_statement(input_buffer, &statement)) {
  78. +      case (PREPARE_SUCCESS):
  79. +        break;
  80. +      case (PREPARE_UNRECOGNIZED_STATEMENT):
  81. +        printf("Unrecognized keyword at start of '%s'.\n",
  82. +               input_buffer->buffer);
  83. +        continue;
  84. +    }
  85. +
  86. +    execute_statement(&statement);
  87. +    printf("Executed.\n");
  88.    }
  89. }
复制代码
Enjoy GreatSQL
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表