继续上篇文章的思路来分析,先说process()方法,简化代码如下:
bool process(const String &text) {
const bool test_mode = config().has("testmode");
//`--multiquery, -n` – 如果指定, 允许处理用逗号分隔的多个查询, 只在非交互模式下生效。
//详见docs/zh/interfaces/cli.md
if (config().has("multiquery")) {
......
const char *begin = text.data();
const char *end = begin + text.size();
while (begin < end) {
const char *pos = begin;
ASTPtr ast = parseQuery(pos, end, true);
if (!ast) {
......
}
auto *insert = ast->as<ASTInsertQuery>();
if (insert && insert->data) {
pos = find_first_symbols<'\n'>(insert->data, end);
insert->end = pos;
}
String str = text.substr(begin - text.data(), pos - begin);
begin = pos;
while (isWhitespaceASCII(*begin) || *begin == ';')
++begin;
......
try {
auto ast_to_process = ast;
if (insert && insert->data)
ast_to_process = nullptr;
if (!processSingleQuery(str, ast_to_process) && !ignore_error)
return false;
}
catch (...) {
......
}
......
}
return true;
} else {
return processSingleQuery(text);
}
}
process() 方法中根据启动client应用时输入参数中是否包含-n这个参数来判断将要执行的SQL是单独的一个SQL还是多个SQL,对于多个SQL,则针对每条SQL分别调用processSingleQuery(text)方法来进行处理。
processSingleQuery(text)方法的简化代码如下:
bool processSingleQuery(const String &line, ASTPtr parsed_query_ = nullptr) {
if (exit_strings.end() !=
exit_strings.find(trim(line, [](char c) { return isWhitespaceASCII(c) || c == ';'; })))
return false;//判断SQL是否以空格或分号结尾
resetOutput();//清空buffer缓冲区
got_exception = false;
......
watch.restart();
query = line;
//针对客户端执行的SQL
/// Some parts of a query (result output and formatting) are executed client-side.
/// Thus we need to parse the query.
parsed_query = parsed_query_;
if (!parsed_query) {//如果ASTPtr是空指针(这里的目的应该是: 如果传过来的参数如果就是AST, 则不必解析)
const char *begin = query.data();
parsed_query = parseQuery(begin, begin + query.size(), false);
}
if (!parsed_query)//如果ASTPtr还是空指针, 表示解析未成功(失败或其他情况)
return true;
......
{
/// Temporarily apply query settings to context.
......
connection->forceConnected();
/// INSERT query for which data transfer is needed (not an INSERT SELECT) is processed separately.
if (insert && !insert->select)
processInsertQuery();
else
processOrdinaryQuery();
}
/// Do not change context (current DB, settings) in case of an exception.
//如果出现异常, 不会更改上下文(当前数据库, 设置等)
//如果没有出现异常, 会更改一些设置并将默认数据库置为刚刚使用的数据库
if (!got_exception) {
......
}
if (is_interactive) {
......
}
return true;
}
这部分的重点是:
if (!parsed_query) {//如果ASTPtr是空指针(这里的目的应该是: 如果传过来的参数如果就是AST, 则不必解析)
const char *begin = query.data();
parsed_query = parseQuery(begin, begin + query.size(), false);
}
if (!parsed_query)//如果ASTPtr还是空指针, 表示解析未成功(失败或其他情况)
return true;
......
{
/// Temporarily apply query settings to context.
......
const auto *insert = parsed_query->as<ASTInsertQuery>();
connection->forceConnected();
/// INSERT query for which data transfer is needed (not an INSERT SELECT) is processed separately.
if (insert && !insert->select)
processInsertQuery();
else
processOrdinaryQuery();
}
过程就是:先根据SQL生成AST,如果是INSERT语句,则调用processInsertQuery()方法进行处理;如果不是INSERT语句,则调用processOrdinaryQuery()方法进行处理。
对于INSERT语句,processInsertQuery()方法的处理逻辑是将SQL语句拆分成insert 语句部分和要插入的数据部分,代码如下:
/// Process the query that requires transferring data blocks to the server.
void processInsertQuery() {
/// Send part of query without data, because data will be sent separately.
const auto &parsed_insert_query = parsed_query->as<ASTInsertQuery &>();
String query_without_data = parsed_insert_query.data
? query.substr(0, parsed_insert_query.data - query.data())
: query;
if (!parsed_insert_query.data && (is_interactive || (stdin_is_not_tty && std_in.eof())))
throw Exception("No data to insert", ErrorCodes::NO_DATA_TO_INSERT);
connection->sendQuery(query_without_data, query_id, QueryProcessingStage::Complete,
&context.getSettingsRef(), nullptr, true);
sendExternalTables();
/// Receive description of table structure.
Block sample;
ColumnsDescription columns_description;
if (receiveSampleBlock(sample, columns_description)) {
/// If structure was received (thus, server has not thrown an exception),
/// send our data with that structure.
sendData(sample, columns_description);
receiveEndOfQuery();
}
}
对于非INSERT语句,processOrdinaryQuery()方法的代码如下:
/// Process the query that doesn't require transferring data blocks to the server.
void processOrdinaryQuery() {
connection->sendQuery(query, query_id, QueryProcessingStage::Complete, &context.getSettingsRef(), nullptr,
true);//客户端sendQuery()后, SQL及其他信息封装成数据包发送到服务端,
//数据包被发送到对应的端口, 由服务端*Handler进行具体的处理, run() -> runImpl() -> receivePacket()
sendExternalTables();
receiveResult();//客户端接收服务端的处理结果, 也是以数据包的形式发送过来的
}
本部分有两个重点,分别是根据SQL生成AST的parseQuery()方法和服务端*Handler接收到SQL后的处理。
备注:看代码发现根据SQL生成AST的过程在客户端和服务端都执行了一次,在客户端解析SQL失败则没必要发送到服务端了,算是把发现问题的时机前置了,可以提前发现问题。