如何教你从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");
   }
 }

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
  屏蔽数据库间的差异,以统一的界面和操作方式来对数据进行处理,将程序员或数据管理员从繁琐的操作模式中解脱出来,使其更加专注的进行Sql语句的编写。   将查询分析器和企业管理器功能合为一,并融入众多实用功能,并完美支持 sqlserver、oracle、mysql、access。是您编写sql语句和数据分析的绝佳帮手。   功能列表:   1、代码高亮:根据操作数据库的不同区分相应关键字,并高亮显示   2、自动完成:输入不同的表名等信息,将自动提示相关的字段信息等。   3、智能提示:输入相应关键字将出现类似VS中一样的说明提示。   4、跨库操作:可同时跨多个数据库间操作,互不影响。随时切换,随时运行。   5、随意运行:运行选择的代码、运行多个Sql操作代码。如果运行多个Select语句则显示多个结果集,用来对比查看。(快捷键F5)   6、数据库树中查找对象。   7、在Sql语句编辑器中快速查找功能。   8、查看数据库属*   9、查看表结构   10、自动生成Sql语句模板   11、删除表、视图等   12、查看数据库属*,并快速定到至物理文件。   13、生成脚本信息功能   14、结果集导出功能。   15、在结构集中查找   16、删除指定的行(快捷键 ‘delete’)   17、修改制定的数据   18、添加新数据   19、复制选择内容(快捷键‘ctrl+C’)   20、将外部数据导入到结果集中(支持txt:以 '|'或tab符号为分割符 和execl: 指定Sheet页名称 和 默认Sheet页 )   21、支持将导入的数据更新至数据库。   22、编辑操作时自动错提示功能(如:自动递增字段自动屏蔽编辑功能。必填字段没有填写内   容则提示,数据类型不正确自动提示等。。。)   23、自动标识主键(主键字段标识为-*-字段名-*-)   24、冻结指定行、列功能。使查看操作更加方便   25、数据集更改后,在提交前可选查看所有更改的部分数据。并用颜**分。   26、自定义我的收藏功能。   27、详细数据单窗体查看功能(支持图片字段)   28、*在没有安装SqlServer 客户端及任何组件的情况下仍然可以连接至SqlServer*   29、*判断Oracle的常见错误,并尝试更改或给出提示*   30、增加历史记录功能。   31、增加自动保存用户状态功能。再次打开软件将会保留上次已连接的数据库信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值