C语言从零实现SQLite数据库(二)

SQL compiler和Virtual Machine介绍

让我们继续SQLite的实现。下图是SQLite官方网站上对SQLite整体架构的描述图,图中右上部分的SQL compiler作为sqlite的前端负责解析字符串,并将其转化为内部的表达式——bytecode(字节码)后输出。字节码在后续的流程中由virtual machine(虚拟机)负责执行。
SQLite架构图
像这样把事情分成两步有几个好处:

  • 降低每个部分的复杂度(如:虚拟机无需考虑语法检查的问题)
  • 常见的查询语句在经过一次编译后可以缓存其字节码,从而提高性能

重构main函数

根据上述思路,我们开始重构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) {
      if (input_buffer->buffer[0] == '.') {
        switch (do_mate_command(input_buffer)) {
          case (MATE_COMMAND_SUCCESS):
            continue;
          case (MATE_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这样的非SQL语句叫做"元命令"(mate-commands)。它们都以“.”开头,所以在识别出这种命令后,我们使用单独的函数来处理它们。

在这之后,我们将命令行输入转换为内部语句表示形式,这就是简易版的sqlite前端。

最后,我们把生成好的语句传给execute_statement函数,这个函数最终会成为我们的虚拟机。

注意,下面的枚举类型是我们新函数的返回值,用来表示处理结果是成功还是失败。

typedef enum {
  MATE_COMMAND_SUCCESS,
  MATE_COMMAND_UNRECOGNIZED_COMMAND
} MateCommandResult;

typedef enum {
  PREPARE_SUCCESS,
  PREPARE_UNRECOGNIZED_STATEMENT
} PrepareResult;

“未识别的语句”听起来像是某种异常情况,但通常我并不喜欢使用异常处理(C语言也不支持),因此使用枚举类型来表示异常。当我的switch语句不能处理枚举类型成员时,C语言编译器会报错,所以我们有自信能处理函数的每种结果。预计之后会添加更多枚举变量成员。

do_mate_command在包装现有功能的基础上,也为后续的命令留出了空间:

MateCommandResult do_mate_command(InputBuffer* input_buffer) {
  if (strcmp(input_buffer->buffer, ".exit") == 0) {
    exit(EXIT_SUCCESS);
  } else {
    return MATE_COMMAND_UNRECOGNIZED_COMMAND;
  }
}

当前,“预处理语句”的枚举类型只包含两种可能值。随着我们语句的扩充将会有更多的值添加到枚举类型中。

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”的时候使用了strncmp,这是因为“insert”关键字后面通常跟着数据(如:insert 1 user 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;
  }
}

注意,execute_statement不会返回任何错误代码,是因为到这一步时已经不存在出现任何错误的可能了。

通过这次重构我们的程序现在能识别两个新的关键字了!

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

原文链接:Let’s Build a Simple Database:Part 2 - World’s Simplest SQL Compiler and Virtual Machine

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以使用C语言中的SQLite API来实现读取SQLite3数据库中的所有数据。下面是一个简单的示例代码: ```c #include <stdio.h> #include <sqlite3.h> int callback(void* data, int argc, char** argv, char** azColName) { for (int i = 0; i < argc; i++) { printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); } printf("\n"); return 0; } int main() { sqlite3* db; char* errMsg = 0; // 打开数据库连接 int rc = sqlite3_open("your_database.db", &db); if (rc) { fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db)); return 1; } // 执行查询语句 char* sql = "SELECT * FROM your_table;"; rc = sqlite3_exec(db, sql, callback, 0, &errMsg); if (rc != SQLITE_OK) { fprintf(stderr, "SQL查询错误: %s\n", errMsg); sqlite3_free(errMsg); return 1; } // 关闭数据库连接 sqlite3_close(db); return 0; } ``` 请注意,上述代码假设您已经安装了SQLite C语言接口库,并且数据库文件名为 "your_database.db",表名为 "your_table"。您可以根据实际情况进行修改。 该示例中的回调函数 `callback` 用于处理查询结果集中的每一行数据。在这里,我们只是简单地打印出每个列的名称和对应的值,您可以根据需要对数据进行进一步处理。 在代码中,我使用了SQLite的函数 `sqlite3_open` 打开数据库连接,`sqlite3_exec` 执行查询语句,并通过 `sqlite3_close` 关闭数据库连接。如果在打开数据库或执行查询语句时出现错误,相应的错误信息将被打印到标准错误流中。 希望这能帮助到您!如果您有任何问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值