| 26 | } |
| 27 | |
| 28 | void ClientApplicationBase::parseAndCheckOptions(OptionsDescription & options_description, po::variables_map & options, Arguments & arguments) |
| 29 | { |
| 30 | /// Parse main commandline options. |
| 31 | auto parser = po::command_line_parser(arguments) |
| 32 | .options(options_description.main_description.value()) |
| 33 | .extra_parser(OptionsAliasParser(options_description.main_description.value())) |
| 34 | .allow_unregistered(); |
| 35 | po::parsed_options parsed = parser.run(); |
| 36 | |
| 37 | /// Check unrecognized options without positional options. |
| 38 | auto unrecognized_options = po::collect_unrecognized(parsed.options, po::collect_unrecognized_mode::exclude_positional); |
| 39 | if (!unrecognized_options.empty()) |
| 40 | { |
| 41 | auto hints = this->getHints(unrecognized_options[0]); |
| 42 | if (!hints.empty()) |
| 43 | throw Exception(ErrorCodes::UNRECOGNIZED_ARGUMENTS, "Unrecognized option '{}'. Maybe you meant {}", |
| 44 | unrecognized_options[0], toString(hints)); |
| 45 | |
| 46 | throw Exception(ErrorCodes::UNRECOGNIZED_ARGUMENTS, "Unrecognized option '{}'", unrecognized_options[0]); |
| 47 | } |
| 48 | |
| 49 | /// Check positional options. |
| 50 | for (const auto & op : parsed.options) |
| 51 | { |
| 52 | /// Skip all options after empty `--`. These are processed separately into the Application configuration. |
| 53 | if (op.string_key.empty() && op.original_tokens[0].starts_with("--")) |
| 54 | break; |
| 55 | |
| 56 | if (!op.unregistered && op.string_key.empty() && !op.original_tokens[0].starts_with("--") |
| 57 | && !op.original_tokens[0].empty() && !op.value.empty()) |
| 58 | { |
| 59 | /// Two special cases for better usability: |
| 60 | /// - if the option contains a whitespace, it might be a query: clickhouse "SELECT 1" |
| 61 | /// - if the option is a filesystem file, then it's likely a queries file (clickhouse repro.sql) |
| 62 | /// These are relevant for interactive usage - user-friendly, but questionable in general. |
| 63 | /// In case of ambiguity or for scripts, prefer using proper options. |
| 64 | |
| 65 | const auto & token = op.original_tokens[0]; |
| 66 | po::variable_value value(boost::any(op.value), false); |
| 67 | |
| 68 | const char * option = nullptr; |
| 69 | std::error_code ec; |
| 70 | if (token.contains(' ')) |
| 71 | option = "query"; |
| 72 | else if (std::filesystem::is_regular_file(std::filesystem::path{token}, ec)) |
| 73 | option = "queries-file"; |
| 74 | else if (token.contains('/') || token.contains('.')) |
| 75 | /// The argument looks like a file path (contains `/` or `.`) but doesn't exist on disk. |
| 76 | /// Give a clear "no such file" error rather than the generic "positional option is not supported" |
| 77 | /// which is confusing when the user meant to pass a file, e.g.: |
| 78 | /// $ clickhouse local /tmp/aaa.rep |
| 79 | /// Positional option `/tmp/aaa.rep` is not supported. |
| 80 | throw Exception(ErrorCodes::BAD_ARGUMENTS, "No such file: {}", token); |
| 81 | else |
| 82 | throw Exception(ErrorCodes::BAD_ARGUMENTS, "Positional option `{}` is not supported.", token); |
| 83 | |
| 84 | if (!options.emplace(option, value).second) |
| 85 | throw Exception(ErrorCodes::BAD_ARGUMENTS, "Positional option `{}` is not supported.", token); |
nothing calls this directly
no test coverage detected