我们正在复制 sqlite。sqlite的“前台”是一个SQL编译器,它解析字符串并输出称为字节码的内部表示。
该字节码被传递给虚拟机,由虚拟机执行。
将事情分成两个步骤有两个优点:
- 降低每个部分的复杂性(例如,虚拟机不担心语法错误)。
- 允许只编译一次常见查询,并缓存字节码以提高性能。
考虑到这一点,让我们重构我们的主函数,并在此过程中支持两个新的关键字:
int main(int argc, char* argv[]) {
InputBuffer* input_buffer = new_input_buffer();
while(true) {
print_prompt();
read_input(input_buffer);
if(input_buffer->buffer[0] == '.') {
switch(do_mata_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,称为“元命令”。它们都以一个点开始,所以我们检查它们并在一个单独的函数中处理它们。
接下来,我们添加一个步骤,将输入行转换为内部的语句。
最后,我们将准备好的语句传递给 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_mata_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;
prepare_statement(我们的“SQL编译器”)目前还无法解析 SQL。事实上,它只理解两个词:
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;
}
注意,我们使用 strncmp 识别 “insert”,因为“insert”关键字后面跟着数据。
最后,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 an select.\n");
break;
}
}
注意,它不会返回任何错误代码,因为还没有任何可能出错的地方。
通过这些重构,我们现在可以识别两个新的关键字!
我们数据库的骨架正在成形...如果它可以存储数据不是更好吗?在下一部分中,我们将实现 insert 和 select,创建世界上最差的数据存储。这是这一部分的全部代码请参考 github.com/yunkai123/s… 。