Writing a sqlite clone from scratch in C++
由于我们的情况增多,我们可能会对之前的测试情况进行修改,所以各个章节内的单元测试可能相同,但实际期望的返回值会有所不同,希望注意。此外,头部的run_script
函数是相同的,至此不再赘述。
it 'test exit and unrecognized command and sql sentence' do
result = run_script([
"hello world",
".HELLO WORLD",
".exit",
])
expect(result).to match_array([
"db > Unrecognized keyword at start of 'hello world'.",
"db > Unrecognized command: .HELLO WORLD",
"db > Bye!",
])
end
it 'test insert and select' do
result = run_script([
"insert 1 user1",
"select",
".exit",
])
expect(result).to match_array([
"db > Executing insert statement",
"db > Executing select statement",
"db > Bye!",
])
end
注意到我们现在所期望的返回值已经是不同的了,我们对输入内容做出了不同的解析,现在就让我们来看一下如何实现这个解析前端。
它将传统输入的string
字符串,解析成可被机器识别的字节码内部表现形式,并传递给虚拟机进一步执行。
先从我们上一章所解析的command
来看起。我们将以.
开头的非sql语句称作元命令 (meta command) 所以我们在一开始就检查是否以其开头,并单独封装一个do_meta_command
函数来处理它。
bool DB::parse_meta_command(std::string command)
{
if (command[0] == '.')
{
switch (do_meta_command(command))
{
case META_COMMAND_SUCCESS:
return true;
case META_COMMAND_UNRECOGNIZED_COMMAND:
std::cout << "Unrecognized command: " << command << std::endl;
return true;
}
}
return false;
}
MetaCommandResult DB::do_meta_command(std::string command)
{
if (command == ".exit")
{
std::cout << "Bye!" << std::endl;
exit(EXIT_SUCCESS);
}
else
{
return META_COMMAND_UNRECOGNIZED_COMMAND;
}
}
同时我们观察到,我们现在引入了一些枚举类的解析结果,例如我们现在所使用用于解析元命令的 ` enum MetaCommandResult { META_COMMAND_SUCCESS, META_COMMAND_UNRECOGNIZED_COMMAND }; `
因此接下来,我将一次性定义另外两个解析结果用于解析sql
语句。
`
enum PrepareResult
{
PREPARE_SUCCESS,
PREPARE_UNRECOGNIZED_STATEMENT
};
`
这个枚举结果用于返回我们所传递的sql
语句是否是符合标准的状态。这个时候就能将传统的以select
或insert
开头的sql
语句转化成对应的statement
状态字节码。
PrepareResult DB::prepare_statement(std::string &input_line, Statement &statement)
{
if (!input_line.compare(0, 6, "insert"))
{
statement.type = STATEMENT_INSERT;
return PREPARE_SUCCESS;
}
else if (!input_line.compare(0, 6, "select"))
{
statement.type = STATEMENT_SELECT;
return PREPARE_SUCCESS;
}
else
{
return PREPARE_UNRECOGNIZED_STATEMENT;
}
}
我们为sql
语句目前仅定义了如下简单的两种状态字节码
`
enum StatementType
{
STATEMENT_INSERT,
STATEMENT_SELECT
};
`
同时再将上一步成功转化后得到的statement
交给虚拟机进行解析。
bool DB::parse_statement(std::string &input_line, Statement &statement)
{
switch (prepare_statement(input_line, statement))
{
case PREPARE_SUCCESS:
return false;
case PREPARE_UNRECOGNIZED_STATEMENT:
std::cout << "Unrecognized keyword at start of '" << input_line << "'." << std::endl;
return true;
}
return false;
}
至此我们初步完成了解析前端的工作,根据command
或者sql
得到了我们所需要的statement
。
我们先根据得到的statement
让虚拟机伪执行一下对应sql
语句的操作效果。
void DB::excute_statement(Statement &statement)
{
switch (statement.type)
{
case STATEMENT_INSERT:
std::cout << "Executing insert statement" << std::endl;
break;
case STATEMENT_SELECT:
std::cout << "Executing select statement" << std::endl;
break;
}
}
void DB::start()
{
while (true)
{
print_prompt();
std::string input_line;
std::getline(std::cin, input_line);
if (parse_meta_command(input_line))
{
continue;
}
Statement statement;
if (parse_statement(input_line, statement))
{
continue;
}
execute_statement(statement);
}
}
再次对我们的结果进行测试检验
..
Finished in 0.00775 seconds (files took 0.07753 seconds to load)
2 examples, 0 failures
恭喜大家又一次通过了所创建的测试。
作为教程的第二章,我们引入了解析前端
和虚拟机
的概念,同时将各种状态映射到对应的枚举类型。至此,我们数据库的基本框架已经搭建完成,下一章就要一起来实现数据存储
功能了。