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

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

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

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

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

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

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

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 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()函数处理结果进行了引用并处理。

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 😃

关于 GreatSQL

GreatSQL是由万里数据库维护的MySQL分支,专注于提升MGR可靠性及性能,支持InnoDB并行查询特性,是适用于金融级应用的MySQL分支版本。

相关链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

欢迎来GreatSQL社区发帖提问
https://greatsql.cn/

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

技术交流群:

微信:扫码添加 GreatSQL社区助手微信好友,发送验证信息 加群

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

Original: https://www.cnblogs.com/greatsql/p/16725217.html
Author: GreatSQL
Title: 实现一个简单的Database2(译文)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/598634/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 一篇文章带你掌握主流数据库框架——MyBatis

    一篇文章带你掌握主流数据库框架——MyBatis MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。 在之前的文章中我们学习了MYSQL和JDBC…

    数据库 2023年6月14日
    098
  • MySQL事务、隔离级别

    一、事务简介 事务是操作的集合,它是一个不可分割的工作单元。事务将向整个系统提交或取消操作请求,即这些操作要么同时成功,要么同时失败。 [En] A transaction is …

    数据库 2023年5月24日
    080
  • 一个Tomcat 如何部署多个项目?附多种解决方案及详细步骤!

    ; 此文源自一次多年前面试的面试题,民工哥将它总结出来分享给大家,希望对大家有所帮助,或者今后的面试中说不定会用的上。 首先,我们了解一下常见的Java Web服务器。 Tomca…

    数据库 2023年6月9日
    0169
  • 【黄啊码】关于微信支付走过的坑

    错误:未定义数组索引:openid 。经过检查发现是 :微信支付授权获取 openId {“errcode”:40163,”errmsg&#82…

    数据库 2023年6月16日
    090
  • Python–序列化与反序列化

    序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态(存在内存中)写入到临时或持久性存储区(硬盘)。以后,可以通过从存储区中读取或反序列化对象的…

    数据库 2023年6月9日
    0106
  • 容器化 | 一文搞定镜像构建方式选型

    作者:安树博 青云科技 PaaS 中间件开发工程师从事 PaaS 中间件服务(Redis/Memcached 等)开发工作,热衷对 NoSQL 数据库领域内技术的学习与研究 官方镜…

    数据库 2023年5月24日
    079
  • Linux_hadoop2.2.0伪分布式搭建安装

    1.1 开启网络,ifconfig指令查看ip 1.2 修改主机名为自己名字(hadoop)centos 7 连接:https://zhuanlan.zhihu.com/p/375…

    数据库 2023年6月11日
    0109
  • 在Ubuntu系统上安装StoneDB数据库

    今天我会进行StoneDB数据库在Ubuntu 22.04系统下的安装。 严格按照官方文档的步骤操作,看看安装能否成功。 [En] Strictly follow the step…

    数据库 2023年5月24日
    0103
  • 从学校到公司,2022新的起点!!!

    步入新的阶段 目前仍然是大学生的身份,但也算是打工人了。2021秋招时来到了天津的一个公司做实习生,并签订了三方协议。已经来公司将近一个月了,我在这段时间想了很多关于我的未来发展方…

    数据库 2023年6月6日
    073
  • [LeetCode]20. 有效的括号

    给定一个只包括 ‘(‘,’)’,'{‘,’}’,'[‘,’]&#8217…

    数据库 2023年6月9日
    0124
  • Linux进程管理

    进程管理 基本概念介绍 进程和线程 进程: 一个在内存中运行的应用程序,每一个进程都有自己独立的一块内存空间,并被分配一个ID号(PID),在Windows下可以打开任务管理器查看…

    数据库 2023年6月16日
    0111
  • Java 面试题及答案整理(2021最新版)持续更新中~~~

    Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用…

    数据库 2023年6月9日
    094
  • HTML5基础知识

    作者导言: 引用偶像刘德华的一句话 “学到的就要教人,赚到的就要给人”! 以下是关联的web前端基础知识文章,通过这些文章,您既可以系统地学习和了解这些知识…

    数据库 2023年6月14日
    061
  • postman结合newman生成测试报告

    1. cmd窗口安装newman npm install -g newman 2. cmd窗口安装newman-html报告 nnpm install -g newman-repo…

    数据库 2023年6月14日
    0101
  • JavaWeb过滤器Filter(附tomcat部分源码分析)

    过滤器Filter 过滤器通常对一些web资源进行拦截,做完一些处理器再交给下一个过滤器处理,直到所有的过滤器处理器,再调用servlet实例的service方法进行处理。过滤器可…

    数据库 2023年6月16日
    0124
  • 五分钟搞懂POM设计模式

    大家好,我是测试蔡坨坨。 今天,我们来聊聊Web UI自动化测试中的POM设计模式。 前期,我们学会了使用Python+Selenium编写Web UI自动化测试线性脚本 线性脚本…

    数据库 2023年6月11日
    0113
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球