ClickHouse client参数获取源码解析

背景

源码版本ClickHouse 21.8。在命令行输入配置项不全也可以匹配到正确的配置项(模糊匹配)。对此进行代码分析。

源码阅读

在programs/main.cpp中main函数为主入口,通过程序名称判断进入哪个子程序入口。这里执行程序名为-client所以进入programs/client/Client.cpp的mainEntryClickHouseClient函数。

int main(int argc_, char ** argv_)
{
    inside_main = true;
    SCOPE_EXIT({ inside_main = false; });
    ///  ... 不重要的代码
    MainFunc main_func = printHelp;
    for (auto & application : clickhouse_applications)
    { 
    	/// 根据程序名称获取对应子程序入口,程序名称-client则进入Client.cpp的功能入口
        if (isClickhouseApp(application.first, argv))
        {
            main_func = application.second;
            break;
        }
    }
    return main_func(static_cast<int>(argv.size()), argv.data());
}

programs/client/Client.cpp的mainEntryClickHouseClient函数中,对于命令行参数的读取部分在init函数。

int mainEntryClickHouseClient(int argc, char ** argv)
{
    try
    {
        DB::Client client;
        /// 在init函数中处理输入参数
        client.init(argc, argv);
        return client.run();
    }
    catch (const DB::Exception & e)
    {
       /// 不重要的代码
    }
}

void init(int argc, char ** argv)
    {
        /// ...
        /// 读取配置描述
        po::options_description main_description = createOptionsDescription("Main options", terminal_width);
        main_description.add_options()
            ("help", "produce help message")
            /// ...
        ;
        Settings cmd_settings;
        cmd_settings.addProgramOptions(main_description);
        /// ...
        /// 解析命令行输入的配置项
        po::parsed_options parsed = po::command_line_parser(common_arguments).options(main_description).run();
        auto unrecognized_options = po::collect_unrecognized(parsed.options, po::collect_unrecognized_mode::include_positional);
        // 配置项未识别则抛异常
        if (unrecognized_options.size() > 1)
        {
            throw Exception("Unrecognized option '" + unrecognized_options[1] + "'", ErrorCodes::UNRECOGNIZED_ARGUMENTS);
        }
        po::variables_map options;
        po::store(parsed, options);
        po::notify(options);
		/// ...
}

在contrib/boost/boost/program_options/detail/parsers.hpp的run函数中执行解析

template<class charT>    
    basic_parsed_options<charT>
    basic_command_line_parser<charT>::run()
    {
        parsed_options result(m_desc, detail::cmdline::get_canonical_option_prefix());
        /// 执行具体解析方法
        result.options = detail::cmdline::run();
        return basic_parsed_options<charT>(result);
    }

在contrib/boost/libs/program_options/src/cmdline.cpp的run函数中执行具体的解析

vector<option>
    cmdline::run()
    {
        // 添加解析方法  

        if (m_style_parser)
            style_parsers.push_back(m_style_parser);
       /// ... 
        style_parsers.push_back(boost::bind(&cmdline::parse_terminator, this, _1));

        vector<option> result;
        vector<string>& args = m_args;
        /// 逐个参数解析
        while(!args.empty())
        {
            bool ok = false;
            /// 逐个解析函数尝试解析单个参数
            for(unsigned i = 0; i < style_parsers.size(); ++i)
            {
                unsigned current_size = static_cast<unsigned>(args.size());
                vector<option> next = style_parsers[i](args);

                // 解析到内容,比如输入的是--xxx则解析到next的string_key为xxx
                if (!next.empty())
                {
                    vector<string> e;
                    for(unsigned k = 0; k < next.size()-1; ++k) {
                    	/// 匹配参数描述
                        finish_option(next[k], e, style_parsers);
                    }
                    finish_option(next.back(), args, style_parsers);
                    for (unsigned j = 0; j < next.size(); ++j)
                        result.push_back(next[j]);                    
                }
                                
                if (args.size() != current_size) {
                    ok = true;
                    break;                
                } 
            }
            /// 如果为解析成功则,将该参数保存后在参数列表中擦除,以便解析下一个参数
            if (!ok) {
                option opt;
                /// ...
                args.erase(args.begin());
            }
        }

        /// ...
        return result;
    }
    
void
    cmdline::finish_option(option& opt,
                           vector<string>& other_tokens,
                           const vector<style_parser>& style_parsers)
    {          
        if (opt.string_key.empty())
            return;
        /// ...
        try
        {
            // 在描述中匹配配置项
            const option_description* xd = m_desc->find_nothrow(opt.string_key, 
                    is_style_active(allow_guessing),
                    is_style_active(long_case_insensitive),
                    is_style_active(short_case_insensitive));

            if (!xd)
            {
                /// ... 匹配失败,抛异常              
            }
            const option_description& d = *xd;

            // 重命名,因为可能有些是模糊匹配,配置名称需要重命名为全名,比如user可以匹配到username,则将配置项user重命名为username。
            opt.string_key = d.key(opt.string_key);

            // ...
    }

在contrib/boost/libs/program_options/src/options_description.cpp的find_nothrow函数中匹配描述项

const option_description*
    options_description::find_nothrow(const std::string& name, 
                                      bool approx,
                                      bool long_ignore_case,
                                      bool short_ignore_case) const
    {
        /// ...
        for(unsigned i = 0; i < m_options.size(); ++i)
        {
        	/// 开始匹配配置项
            option_description::match_result r = 
                m_options[i]->match(name, approx, long_ignore_case, short_ignore_case);

			/// 不匹配直接下一个
            if (r == option_description::no_match)
                continue;

			/// 全匹配
            if (r == option_description::full_match)
            {                
                full_matches.push_back(m_options[i]->key(name));
                found = m_options[i];
                had_full_match = true;
            } 
            else 
            {                        
                // 模糊匹配
                approximate_matches.push_back(m_options[i]->key(name));
                if (!had_full_match)
                    found = m_options[i];
            }
        }
        /// 多个全匹配,则抛异常
        if (full_matches.size() > 1) 
            boost::throw_exception(ambiguous_option(full_matches));
        
        /// 多个匹配,程序无法知道到底是匹配哪个配置项也抛异常
        if (full_matches.empty() && approximate_matches.size() > 1)
            boost::throw_exception(ambiguous_option(approximate_matches));

        return found.get();
    }
    
    /// 三种匹配结果:不匹配,全匹配,模糊匹配(配置项的全字符与描述字段的前一截字符匹配,或者有通配符*)
option_description::match_result
    option_description::match(const std::string& option, 
                              bool approx, 
                              bool long_ignore_case,
                              bool short_ignore_case) const
    {
        match_result result = no_match;        
        std::string local_option = (long_ignore_case ? tolower_(option) : option);
        
        for(std::vector<std::string>::const_iterator it(m_long_names.begin()); it != m_long_names.end(); it++)
        {
            std::string local_long_name((long_ignore_case ? tolower_(*it) : *it));

            if (!local_long_name.empty()) {
                if ((result == no_match) && (*local_long_name.rbegin() == '*'))
                {
                    // The name ends with '*'. Any specified name with the given
                    // prefix is OK.
                    if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
                        == 0)
                        result = approximate_match;
                }

                if (local_long_name == local_option)
                {
                    result = full_match;
                    break;
                }
                else if (approx)
                {
                    if (local_long_name.find(local_option) == 0)
                    {
                        result = approximate_match;
                    }
                }
            }

        }

        if (result != full_match)
        {
            std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name);

            if (local_short_name == local_option)
            {
                result = full_match;
            }
        }

        return result;        
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值