| 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 | |