如何教你从0到1实现一个简单的数据库系统(二)--世界上最简单的SQL编译器和虚拟机

我们做一个sqlite的克隆。sqlite的前端是一个SQL编译器,它是用来解析一个字符串并且输出一个称为字节码的内部表示。字节码通过虚拟机来执行它。

将事物分成两个步骤具有两个优点:

  • 减少每一部分的复杂性(例如:虚拟机不需要担心语法错误)
  • 允许编译一次共同查询并缓存字节码以提高性能。

在这个想法中,让我们重构我们的main()函数并支持处理两个新的关键字:

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");
   }
 }

.exit这种非non-sql我们就称之为meta-commands,也叫做元命令,也就是不能被分割。它们通常以一个圆点开始,因此我们在一个分离函数中检查并处理它们。

接下来,我们需要把输入的一行转换成我们语法的内部表示(也就是能被我们虚拟机识别处理的语句),这是sqlite前端的黑客部分。

最后,我们将准备好的语句传给execute_statement,这个函数最终变成我们的虚拟机。

要注意的是,这两个新的函数返回的都是枚举用来表示成功或者失败:

typedef enum {
  META_COMMAND_SUCCESS,
  META_COMMAND_UNRECOGNIZED_COMMAND
} MetaCommandResult;

typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;

“无法识别的语句?”,这似乎和异常有一点像。但异常是坏的(并且c语言甚至不支持),所以我尽可能的使用枚举结果代码。如果switch语句不能处理枚举的一个成员,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现在只包括两个可能值的枚举。将来会包含更多的数据,因为我们允许在语句中使用参数。

typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;

typedef struct {
  StatementType type;
} Statement;

请注意,我们使用strncmp作为“insert”,因为“insert”关键字后面将跟有数据(insert 1 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;
  }
}

注意!它不返回任何错误码,因为它没有任何能出错的。

对于这次重构,我们现在需要识别两个新的关键字:

~ ./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
~

到了这里我们的数据库才有一个大概的脉络,这太好了,但是如果我们存放数据了?在下一章我们将会实现insertselect,创建这世界上最差的数据存储。同时,我们把本章不同的代码贴出来

 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;
 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");
   }
 }

关注我每周获取最新最简单的数据库实现的系列文章,您也可以扫码关注我的公众号:码码呀
在这里插入图片描述


版权声明:本文为qq_27632921原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。