ClickHouse源码阅读(0000 0100) —— ClickHouse是如何执行SQL的

继续上篇文章的思路来分析,先说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失败则没必要发送到服务端了,算是把发现问题的时机前置了,可以提前发现问题。

 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值