#include #include #include #include #include #include namespace DB::ErrorCodes { extern const int CANNOT_PARSE_TEXT; } namespace DB { TestHint::TestHint(const String & query_) : query(query_) { // Don't parse error hints in leading comments, because it feels weird. // Leading 'echo' hint is OK. bool is_leading_hint = true; Lexer lexer(query.data(), query.data() + query.size()); for (Token token = lexer.nextToken(); !token.isEnd(); token = lexer.nextToken()) { if (token.type != TokenType::Comment && token.type != TokenType::Whitespace) { is_leading_hint = false; } else if (token.type == TokenType::Comment) { String comment(token.begin, token.begin + token.size()); if (!comment.empty()) { size_t pos_start = comment.find('{', 0); if (pos_start != String::npos) { size_t pos_end = comment.find('}', pos_start); if (pos_end != String::npos) { Lexer comment_lexer(comment.c_str() + pos_start + 1, comment.c_str() + pos_end, 0); parse(comment_lexer, is_leading_hint); } } } } } } bool TestHint::hasExpectedClientError(int error) { return std::find(client_errors.begin(), client_errors.end(), error) != client_errors.end(); } bool TestHint::hasExpectedServerError(int error) { return std::find(server_errors.begin(), server_errors.end(), error) != server_errors.end(); } void TestHint::parse(Lexer & comment_lexer, bool is_leading_hint) { std::unordered_set commands{"echo", "echoOn", "echoOff"}; std::unordered_set command_errors{ "serverError", "clientError", }; for (Token token = comment_lexer.nextToken(); !token.isEnd(); token = comment_lexer.nextToken()) { String item = String(token.begin, token.end); if (token.type == TokenType::BareWord && commands.contains(item)) { if (item == "echo") echo.emplace(true); if (item == "echoOn") echo.emplace(true); if (item == "echoOff") echo.emplace(false); } else if (!is_leading_hint && token.type == TokenType::BareWord && command_errors.contains(item)) { /// Everything after this must be a list of errors separated by comma ErrorVector error_codes; while (!token.isEnd()) { token = comment_lexer.nextToken(); if (token.type == TokenType::Whitespace) continue; if (token.type == TokenType::Number) { int code; auto [p, ec] = std::from_chars(token.begin, token.end, code); if (p == token.begin) throw DB::Exception( DB::ErrorCodes::CANNOT_PARSE_TEXT, "Could not parse integer number for errorcode: {}", std::string_view(token.begin, token.end)); error_codes.push_back(code); } else if (token.type == TokenType::BareWord) { int code = DB::ErrorCodes::getErrorCodeByName(std::string_view(token.begin, token.end)); error_codes.push_back(code); } else throw DB::Exception( DB::ErrorCodes::CANNOT_PARSE_TEXT, "Could not parse error code in {}: {}", getTokenName(token.type), std::string_view(token.begin, token.end)); do { token = comment_lexer.nextToken(); } while (!token.isEnd() && token.type == TokenType::Whitespace); if (!token.isEnd() && token.type != TokenType::Comma) throw DB::Exception( DB::ErrorCodes::CANNOT_PARSE_TEXT, "Could not parse error code. Expected ','. Got '{}'", std::string_view(token.begin, token.end)); } if (item == "serverError") server_errors = error_codes; else client_errors = error_codes; break; } } } }