Line data Source code
1 : // Routing-bug defense tests for the control-op executor. Kept in a
2 : // separate file so `control_op_executor_test.cc` stays under the
3 : // cpp-lint file-length cap.
4 :
5 : #include <cstdlib>
6 : #include <filesystem>
7 : #include <memory>
8 : #include <random>
9 : #include <string>
10 : #include <system_error>
11 : #include <utility>
12 :
13 : #include "absl/status/status.h"
14 : #include "absl/strings/str_cat.h"
15 : #include "backend/catalog/googlesql_catalog.h"
16 : #include "backend/engine/control/control_op_executor.h"
17 : #include "backend/engine/engine.h"
18 : #include "backend/storage/duckdb/duckdb_storage.h"
19 : #include "googlesql/public/analyzer.h"
20 : #include "googlesql/public/analyzer_options.h"
21 : #include "googlesql/public/analyzer_output.h"
22 : #include "googlesql/public/language_options.h"
23 : #include "googlesql/public/options.pb.h"
24 : #include "googlesql/public/types/type_factory.h"
25 : #include "googlesql/resolved_ast/resolved_ast.h"
26 : #include "gtest/gtest.h"
27 :
28 : namespace bigquery_emulator {
29 : namespace backend {
30 : namespace engine {
31 : namespace control {
32 : namespace {
33 :
34 : namespace fs = std::filesystem;
35 :
36 4 : ::googlesql::LanguageOptions MakeLanguageOptions() {
37 4 : ::googlesql::LanguageOptions language;
38 4 : language.EnableMaximumLanguageFeatures();
39 4 : language.set_product_mode(::googlesql::PRODUCT_EXTERNAL);
40 4 : language.set_name_resolution_mode(::googlesql::NAME_RESOLUTION_DEFAULT);
41 4 : return language;
42 4 : }
43 :
44 2 : ::googlesql::AnalyzerOptions MakeAnalyzerOptions() {
45 2 : ::googlesql::AnalyzerOptions options(MakeLanguageOptions());
46 2 : options.set_error_message_mode(::googlesql::ERROR_MESSAGE_ONE_LINE);
47 2 : options.set_attach_error_location_payload(true);
48 2 : options.CreateDefaultArenasIfNotSet();
49 2 : options.mutable_language()->SetSupportsAllStatementKinds();
50 2 : return options;
51 2 : }
52 :
53 : class ControlOpRoutingTest : public ::testing::Test {
54 : protected:
55 2 : void SetUp() override {
56 2 : const char* tmpdir_env = std::getenv("TMPDIR");
57 2 : const std::string tmpdir = tmpdir_env != nullptr ? tmpdir_env : "/tmp";
58 2 : std::random_device rd;
59 2 : std::seed_seq seed{rd(), rd()};
60 2 : std::mt19937_64 rng(seed);
61 2 : data_dir_ = fs::path(tmpdir) /
62 2 : absl::StrCat("bqemu-control-op-routing-test-", rng());
63 2 : std::error_code ec;
64 2 : fs::remove_all(data_dir_, ec);
65 2 : auto opened = storage::duckdb::DuckDBStorage::Open(data_dir_.string());
66 4 : ASSERT_TRUE(opened.ok()) << opened.status();
67 2 : storage_ = std::move(opened).value();
68 2 : executor_ = std::make_unique<ControlOpExecutor>(storage_.get());
69 2 : ASSERT_TRUE(storage_->CreateDataset({"proj-test", "ds"}, "US").ok());
70 2 : }
71 :
72 2 : void TearDown() override {
73 2 : executor_.reset();
74 2 : storage_.reset();
75 2 : std::error_code ec;
76 2 : fs::remove_all(data_dir_, ec);
77 2 : }
78 :
79 2 : QueryRequest MakeRequest(absl::string_view sql) {
80 2 : QueryRequest req;
81 2 : req.project_id = "proj-test";
82 2 : req.sql = std::string(sql);
83 2 : return req;
84 2 : }
85 :
86 : struct CatalogBundle {
87 : std::unique_ptr<::googlesql::TypeFactory> type_factory{};
88 : std::unique_ptr<catalog::GoogleSqlCatalog> catalog{};
89 : };
90 2 : CatalogBundle MakeCatalog() {
91 2 : auto type_factory = std::make_unique<::googlesql::TypeFactory>();
92 2 : auto catalog = std::make_unique<catalog::GoogleSqlCatalog>(
93 2 : "proj-test", storage_.get(), type_factory.get(), MakeLanguageOptions());
94 2 : return {std::move(type_factory), std::move(catalog)};
95 2 : }
96 :
97 : fs::path data_dir_{};
98 : std::unique_ptr<storage::duckdb::DuckDBStorage> storage_{};
99 : std::unique_ptr<ControlOpExecutor> executor_{};
100 : };
101 :
102 1 : TEST_F(ControlOpRoutingTest, ExecuteQueryRejectsControlOpStatement) {
103 1 : CatalogBundle bundle = MakeCatalog();
104 1 : ::googlesql::AnalyzerOptions options = MakeAnalyzerOptions();
105 1 : ::googlesql::TypeFactory type_factory;
106 1 : std::unique_ptr<const ::googlesql::AnalyzerOutput> output;
107 1 : ASSERT_TRUE(::googlesql::AnalyzeStatement("CREATE TABLE ds.t (id INT64)",
108 1 : options,
109 1 : bundle.catalog.get(),
110 1 : &type_factory,
111 1 : &output)
112 1 : .ok());
113 1 : ASSERT_NE(output, nullptr);
114 1 : auto source =
115 1 : executor_->ExecuteQuery(MakeRequest("CREATE TABLE ds.t (id INT64)"),
116 1 : *output->resolved_statement(),
117 1 : bundle.catalog.get());
118 1 : ASSERT_FALSE(source.ok());
119 2 : EXPECT_EQ(source.status().code(), absl::StatusCode::kInvalidArgument)
120 2 : << source.status();
121 1 : }
122 :
123 1 : TEST_F(ControlOpRoutingTest, ExecuteDmlRejectsControlOpStatement) {
124 1 : CatalogBundle bundle = MakeCatalog();
125 1 : ::googlesql::AnalyzerOptions options = MakeAnalyzerOptions();
126 1 : ::googlesql::TypeFactory type_factory;
127 1 : std::unique_ptr<const ::googlesql::AnalyzerOutput> output;
128 1 : ASSERT_TRUE(::googlesql::AnalyzeStatement("CREATE TABLE ds.t (id INT64)",
129 1 : options,
130 1 : bundle.catalog.get(),
131 1 : &type_factory,
132 1 : &output)
133 1 : .ok());
134 1 : ASSERT_NE(output, nullptr);
135 1 : auto stats =
136 1 : executor_->ExecuteDml(MakeRequest("CREATE TABLE ds.t (id INT64)"),
137 1 : *output->resolved_statement(),
138 1 : bundle.catalog.get());
139 1 : ASSERT_FALSE(stats.ok());
140 2 : EXPECT_EQ(stats.status().code(), absl::StatusCode::kInvalidArgument)
141 2 : << stats.status();
142 1 : }
143 :
144 : } // namespace
145 : } // namespace control
146 : } // namespace engine
147 : } // namespace backend
148 : } // namespace bigquery_emulator
|