1 | #include "duckdb/common/preserved_error.hpp" |
2 | #include "duckdb/main/client_context.hpp" |
3 | #include "duckdb/parser/statement/explain_statement.hpp" |
4 | #include "duckdb/verification/statement_verifier.hpp" |
5 | #include "duckdb/main/database.hpp" |
6 | #include "duckdb/common/box_renderer.hpp" |
7 | |
8 | namespace duckdb { |
9 | |
10 | PreservedError ClientContext::VerifyQuery(ClientContextLock &lock, const string &query, |
11 | unique_ptr<SQLStatement> statement) { |
12 | D_ASSERT(statement->type == StatementType::SELECT_STATEMENT); |
13 | // Aggressive query verification |
14 | |
15 | // The purpose of this function is to test correctness of otherwise hard to test features: |
16 | // Copy() of statements and expressions |
17 | // Serialize()/Deserialize() of expressions |
18 | // Hash() of expressions |
19 | // Equality() of statements and expressions |
20 | // ToString() of statements and expressions |
21 | // Correctness of plans both with and without optimizers |
22 | |
23 | const auto &stmt = *statement; |
24 | vector<unique_ptr<StatementVerifier>> statement_verifiers; |
25 | unique_ptr<StatementVerifier> prepared_statement_verifier; |
26 | if (config.query_verification_enabled) { |
27 | statement_verifiers.emplace_back(args: StatementVerifier::Create(type: VerificationType::COPIED, statement_p: stmt)); |
28 | statement_verifiers.emplace_back(args: StatementVerifier::Create(type: VerificationType::DESERIALIZED, statement_p: stmt)); |
29 | statement_verifiers.emplace_back(args: StatementVerifier::Create(type: VerificationType::DESERIALIZED_V2, statement_p: stmt)); |
30 | statement_verifiers.emplace_back(args: StatementVerifier::Create(type: VerificationType::UNOPTIMIZED, statement_p: stmt)); |
31 | prepared_statement_verifier = StatementVerifier::Create(type: VerificationType::PREPARED, statement_p: stmt); |
32 | #ifdef DUCKDB_DEBUG_ASYNC_SINK_SOURCE |
33 | // This verification is quite slow, so we only run it for the async sink/source debug mode |
34 | statement_verifiers.emplace_back(StatementVerifier::Create(VerificationType::NO_OPERATOR_CACHING, stmt)); |
35 | #endif |
36 | } |
37 | if (config.verify_external) { |
38 | statement_verifiers.emplace_back(args: StatementVerifier::Create(type: VerificationType::EXTERNAL, statement_p: stmt)); |
39 | } |
40 | |
41 | auto original = make_uniq<StatementVerifier>(args: std::move(statement)); |
42 | for (auto &verifier : statement_verifiers) { |
43 | original->CheckExpressions(other: *verifier); |
44 | } |
45 | original->CheckExpressions(); |
46 | |
47 | // See below |
48 | auto statement_copy_for_explain = stmt.Copy(); |
49 | |
50 | // Save settings |
51 | bool optimizer_enabled = config.enable_optimizer; |
52 | bool profiling_is_enabled = config.enable_profiler; |
53 | bool force_external = config.force_external; |
54 | |
55 | // Disable profiling if it is enabled |
56 | if (profiling_is_enabled) { |
57 | config.enable_profiler = false; |
58 | } |
59 | |
60 | // Execute the original statement |
61 | bool any_failed = original->Run(context&: *this, query, run: [&](const string &q, unique_ptr<SQLStatement> s) { |
62 | return RunStatementInternal(lock, query: q, statement: std::move(s), allow_stream_result: false, verify: false); |
63 | }); |
64 | if (!any_failed) { |
65 | statement_verifiers.emplace_back( |
66 | args: StatementVerifier::Create(type: VerificationType::PARSED, statement_p: *statement_copy_for_explain)); |
67 | } |
68 | // Execute the verifiers |
69 | for (auto &verifier : statement_verifiers) { |
70 | bool failed = verifier->Run(context&: *this, query, run: [&](const string &q, unique_ptr<SQLStatement> s) { |
71 | return RunStatementInternal(lock, query: q, statement: std::move(s), allow_stream_result: false, verify: false); |
72 | }); |
73 | any_failed = any_failed || failed; |
74 | } |
75 | |
76 | if (!any_failed && prepared_statement_verifier) { |
77 | // If none failed, we execute the prepared statement verifier |
78 | bool failed = prepared_statement_verifier->Run(context&: *this, query, run: [&](const string &q, unique_ptr<SQLStatement> s) { |
79 | return RunStatementInternal(lock, query: q, statement: std::move(s), allow_stream_result: false, verify: false); |
80 | }); |
81 | if (!failed) { |
82 | // PreparedStatementVerifier fails if it runs into a ParameterNotAllowedException, which is OK |
83 | statement_verifiers.push_back(x: std::move(prepared_statement_verifier)); |
84 | } |
85 | } else { |
86 | if (ValidChecker::IsInvalidated(o&: *db)) { |
87 | return original->materialized_result->GetErrorObject(); |
88 | } |
89 | } |
90 | |
91 | // Restore config setting |
92 | config.enable_optimizer = optimizer_enabled; |
93 | config.force_external = force_external; |
94 | |
95 | // Check explain, only if q does not already contain EXPLAIN |
96 | if (original->materialized_result->success) { |
97 | auto explain_q = "EXPLAIN " + query; |
98 | auto explain_stmt = make_uniq<ExplainStatement>(args: std::move(statement_copy_for_explain)); |
99 | try { |
100 | RunStatementInternal(lock, query: explain_q, statement: std::move(explain_stmt), allow_stream_result: false, verify: false); |
101 | } catch (std::exception &ex) { // LCOV_EXCL_START |
102 | interrupted = false; |
103 | return PreservedError("EXPLAIN failed but query did not (" + string(ex.what()) + ")" ); |
104 | } // LCOV_EXCL_STOP |
105 | |
106 | #ifdef DUCKDB_VERIFY_BOX_RENDERER |
107 | // this is pretty slow, so disabled by default |
108 | // test the box renderer on the result |
109 | // we mostly care that this does not crash |
110 | RandomEngine random; |
111 | BoxRendererConfig config; |
112 | // test with a random width |
113 | config.max_width = random.NextRandomInteger() % 500; |
114 | BoxRenderer renderer(config); |
115 | renderer.ToString(*this, original->materialized_result->names, original->materialized_result->Collection()); |
116 | #endif |
117 | } |
118 | |
119 | // Restore profiler setting |
120 | if (profiling_is_enabled) { |
121 | config.enable_profiler = true; |
122 | } |
123 | |
124 | // Now compare the results |
125 | // The results of all runs should be identical |
126 | for (auto &verifier : statement_verifiers) { |
127 | auto result = original->CompareResults(other: *verifier); |
128 | if (!result.empty()) { |
129 | return PreservedError(result); |
130 | } |
131 | } |
132 | |
133 | return PreservedError(); |
134 | } |
135 | |
136 | } // namespace duckdb |
137 | |