首页 > 其他分享 >实现一个简单的Database2(译文)

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

时间:2022-09-24 11:33:42浏览次数:83  
标签:COMMAND 译文 SUCCESS buffer 简单 STATEMENT statement Database2 input

前文回顾:实现一个简单的Database1(译文)

译注:cstsck在github维护了一个简单的、类似sqlite的数据库实现,通过这个简单的项目,可以很好的理解数据库是如何运行的。本文是第二篇,主要是实现数据库的前端组件,编译器与虚拟机部分功能

Part 2 世界上最简单的SQL编译器与虚拟机

我们正在实现一个sqlite的克隆版本。sqlite的前端是SQL编译器,编译器用来解析字符串并输出一个内部的表示,叫做字节码。

这些字节码被传到虚拟机(virtual machine),在虚拟机中,字节码将被执行。

1654132

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 [email protected])

译注: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

标签:COMMAND,译文,SUCCESS,buffer,简单,STATEMENT,statement,Database2,input
From: https://www.cnblogs.com/greatsql/p/16725217.html

相关文章

  • spring security 认证和授权简单流程了解
    1.总结:昨天主要是对WebSecurityConfigurerAdaptor的三个函数的区分以及了解了springsecurity的认证和授权流程;再就是动手使用了下thymeleaf和freeMark的模板以及使用JSON......
  • HTML|简单的个人介绍网页
    个人介绍网页效果代码实现图片可以放在本地,也可以使用托管网站,这里我是用的托管网站。<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><me......
  • 下载b站视频的简单方法
    大前提:win系统版本≥101.如何安装打开解压后的附件文件夹,双击哔哩哔哩.msixbundle等待加载完成后,点击“安装此应用”(此处配图为“重新安装”,位置差不多)等待安装......
  • wxWidgets UI 库 简单示例和 高清屏 DPI 适配
    wxWidgets是一种跨平台开发的UI库,winmacOSubuntu都有很好的本地实现。版权友好,个人商业用途都可以,静态编译也比较容易,开发的比较出名的软件有:Filezilla、Aegisub......
  • redis的简单使用(2)
    redis持久化操作RDB: 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。 Redis会单独创建(fork)一......
  • python学习之路Day03(pyhcarm基础配置/python基础语法/简单数据类型)
    今日内容概要pycharm基础设置PEP-8规范/python基础语法变量与常量定义基本数据类型(整形int,字符串str,浮点型float,字典dict,列表list)首先我们要学习Pycharm......
  • 2nd 2022/5/3-2022/5/4 简单数论学习
    Day-1/2:2022/5/3·1最大公约数枚举。。。训算法质因数分解。。。是个办法,但大材小用,浪费了算法得来的其他数据,时间较慢欧几里得算法,辗转相除,巧妙消元,时间$O(\l......
  • vue3路由简单配置
    路由目录各文件内容【router/index】import{createRouter,createWebHashHistory,createWebHistory}from"vue-router";import{scrollBehavior}from"./helpe......
  • 【Vue】vue项目搭建、ES6的简单使用(大觅)
    目录项目搭建与基本配置项目搭建安装淘宝NPM镜像cnpm安装webpack新建项目运行项目运行时出现的一些问题和解决方案框架安装安装UI框架iView引入UI框架iView引入方式1:全部......
  • 简单的继承练习——疑问:私有属性在类的方法内调用时,有必要使用get,set方法嘛?
    要求:创建一个Circle类,设置半径属性和返回面积的方法,在无参构造器中初始化半径值为1。创建一个Cylinder类,继承Circle类,设置属性高和返回体积的方法,在无参构造器中初始化......