本系列文章一共13篇,本文为第2篇,请关注公众号,后续文章会陆续发布。

系列文章第1篇:《手把手教你从零开始实现一个数据库系统
我们正在实现SQLlite的克隆。SQLite的前端是SQL编译器,它接收字符串并输出我们称之为字节码的内部表达式。
字节码被传输到虚拟机并执行。

一起做个简单的数据库(二):世上最简单的SQL编译器和虚拟机_虚拟机

SQLite架构
上述将事情分成两步处理有两点好处:
  • 降低每个部分的复杂性(例如虚拟机不用考虑语法错误)

  • 通过对查询语句的批量编译和字节码的缓存来提高性能


在这样的设计思路下,让我们重构主函数并在此过程中支持两个新关键字:
  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. }


Non-SQL中的声明语句如.exit被称作为“元命令”,它们都以“.”开始,我们用不同的函数来处理。
接下来,我们增加一步将输入行转化成内部表达式。这就是我们自定义的SQLite前端。
最后我们将prepared statement传递给execute_statement。这个函数会最终成为我们的虚拟机。
我们通过两个新函数的enums返回值来判断执行结果:

  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”看起来像个异常,抛出异常不是什么好现象(C语言甚至不支持异常),所以我们在任何可用的地方使用enum result code来做判断。我的switch statement支持多enum的处理,因此我们可以更有信心处理函数的每个结果。预计更多的result code会产生出来。
dometacommand函数只是为现有函数做个打包,让程序更精简:

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目前应该只包含两个值,如果我们设置参数,enum中将会有更多的值。

  1. typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;


  2. typedef struct {

  3.  StatementType type;

  4. } Statement;


prepare_statement(我们的SQL编译器)目前还不能编译SQL,实际上它目前只认识两个单词:

  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. }


因为“插入”关键字后面要紧跟数据,我们用strncmp实现“插入”。(例如,insert cstack foo@bar.com)
最终,execute_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;  }}


这个函数目前并不会返回任何报错信息。
通过代码重构,程序可以识别两个新关键词了!

~ ./dbdb > insert foo barThis is where we would do an insert.Executed.db > delete fooUnrecognized keyword at start of 'delete foo'.db > selectThis is where we would do a select.Executed.db > .tablesUnrecognized command '.tables'db > .exit~


我们的数据库越来越健壮了,在下一篇,我们会执行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. }


原文链接:https://cstack.github.io/db_tutorial/parts/part2.html