aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/clickhouse/src/Client/TestHint.h
blob: 982cd10dce0563690bd572f5f9f9ee8fd9172ec2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#pragma once

#include <optional>
#include <vector>

#include <fmt/format.h>

#include <Core/Types.h>


namespace DB
{

class Lexer;

/// Checks expected server and client error codes.
///
/// The following comment hints are supported:
///
/// - "-- { serverError 60 }" -- in case of you are expecting server error.
/// - "-- { serverError 16, 36 }" -- in case of you are expecting one of the 2 errors.
///
/// - "-- { clientError 20 }" -- in case of you are expecting client error.
/// - "-- { clientError 20, 60, 92 }" -- It's expected that the client will return one of the 3 errors.
///
/// - "-- { serverError FUNCTION_THROW_IF_VALUE_IS_NON_ZERO }" -- by error name.
/// - "-- { serverError NO_SUCH_COLUMN_IN_TABLE, BAD_ARGUMENTS }" -- by error name.
///
/// - "-- { clientError FUNCTION_THROW_IF_VALUE_IS_NON_ZERO }" -- by error name.
///
///   Remember that the client parse the query first (not the server), so for
///   example if you are expecting syntax error, then you should use
///   clientError not serverError.
///
/// Examples:
///
/// - echo 'select / -- { clientError 62 }' | clickhouse-client -nm
///
//    Here the client parses the query but it is incorrect, so it expects
///   SYNTAX_ERROR (62).
///
/// - echo 'select foo -- { serverError 47 }' | clickhouse-client -nm
///
///   But here the query is correct, but there is no such column "foo", so it
///   is UNKNOWN_IDENTIFIER server error.
///
/// The following hints will control the query echo mode (i.e print each query):
///
/// - "-- { echo }"
/// - "-- { echoOn }"
/// - "-- { echoOff }"
class TestHint
{
public:
    using ErrorVector = std::vector<int>;
    TestHint(const String & query_);

    const auto & serverErrors() const { return server_errors; }
    const auto & clientErrors() const { return client_errors; }
    std::optional<bool> echoQueries() const { return echo; }

    bool hasClientErrors() { return !client_errors.empty(); }
    bool hasServerErrors() { return !server_errors.empty(); }

    bool hasExpectedClientError(int error);
    bool hasExpectedServerError(int error);

private:
    const String & query;
    ErrorVector server_errors{};
    ErrorVector client_errors{};
    std::optional<bool> echo;

    void parse(Lexer & comment_lexer, bool is_leading_hint);

    bool allErrorsExpected(int actual_server_error, int actual_client_error) const
    {
        if (actual_server_error && std::find(server_errors.begin(), server_errors.end(), actual_server_error) == server_errors.end())
            return false;
        if (!actual_server_error && server_errors.size())
            return false;

        if (actual_client_error && std::find(client_errors.begin(), client_errors.end(), actual_client_error) == client_errors.end())
            return false;
        if (!actual_client_error && client_errors.size())
            return false;

        return true;
    }

    bool lostExpectedError(int actual_server_error, int actual_client_error) const
    {
        return (server_errors.size() && !actual_server_error) || (client_errors.size() && !actual_client_error);
    }
};

}

template <>
struct fmt::formatter<DB::TestHint::ErrorVector>
{
    static constexpr auto parse(format_parse_context & ctx)
    {
        const auto * it = ctx.begin();
        const auto * end = ctx.end();

        /// Only support {}.
        if (it != end && *it != '}')
            throw fmt::format_error("Invalid format");

        return it;
    }

    template <typename FormatContext>
    auto format(const DB::TestHint::ErrorVector & ErrorVector, FormatContext & ctx)
    {
        if (ErrorVector.empty())
            return fmt::format_to(ctx.out(), "{}", 0);
        else if (ErrorVector.size() == 1)
            return fmt::format_to(ctx.out(), "{}", ErrorVector[0]);
        else
            return fmt::format_to(ctx.out(), "[{}]", fmt::join(ErrorVector, ", "));
    }
};