Line data Source code
1 : // Tests for the three stub executors. Each one is supposed to
2 : // return UNIMPLEMENTED with a disposition-aware message; the tests
3 : // pin the route name + plan pointer the message advertises so the
4 : // future conformance routing matrix (planned in
5 : // `docs/ENGINE_POLICY.md`) and any operator-facing log
6 : // pipeline can grep on a stable surface.
7 :
8 : #include "backend/engine/coordinator/stub_executors.h"
9 :
10 : #include <memory>
11 : #include <vector>
12 :
13 : #include "absl/status/status.h"
14 : #include "absl/status/statusor.h"
15 : #include "absl/strings/string_view.h"
16 : #include "backend/engine/engine.h"
17 : #include "googlesql/public/analyzer.h"
18 : #include "googlesql/public/analyzer_options.h"
19 : #include "googlesql/public/analyzer_output.h"
20 : #include "googlesql/public/builtin_function_options.h"
21 : #include "googlesql/public/catalog.h"
22 : #include "googlesql/public/language_options.h"
23 : #include "googlesql/public/options.pb.h"
24 : #include "googlesql/public/simple_catalog.h"
25 : #include "googlesql/public/types/type_factory.h"
26 : #include "googlesql/resolved_ast/resolved_ast.h"
27 : #include "gtest/gtest.h"
28 :
29 : namespace bigquery_emulator {
30 : namespace backend {
31 : namespace engine {
32 : namespace coordinator {
33 : namespace {
34 :
35 4 : ::googlesql::AnalyzerOptions MakeAnalyzerOptions() {
36 4 : ::googlesql::LanguageOptions language;
37 4 : language.EnableMaximumLanguageFeatures();
38 4 : language.set_product_mode(::googlesql::PRODUCT_EXTERNAL);
39 4 : ::googlesql::AnalyzerOptions options(language);
40 4 : options.CreateDefaultArenasIfNotSet();
41 4 : return options;
42 4 : }
43 :
44 : class StubExecutorsTest : public ::testing::Test {
45 : protected:
46 4 : void SetUp() override {
47 4 : type_factory_ = std::make_unique<::googlesql::TypeFactory>();
48 4 : catalog_ = std::make_unique<::googlesql::SimpleCatalog>(
49 4 : "stub_catalog", type_factory_.get());
50 4 : catalog_->AddBuiltinFunctions(
51 4 : ::googlesql::BuiltinFunctionOptions::AllReleasedFunctions());
52 4 : auto people = std::make_unique<::googlesql::SimpleTable>(
53 4 : "people",
54 4 : std::vector<::googlesql::SimpleTable::NameAndType>{
55 4 : {"name", type_factory_->get_string()}});
56 4 : catalog_->AddOwnedTable(std::move(people));
57 4 : }
58 :
59 3 : const ::googlesql::ResolvedStatement* AnalyzeSelect1() {
60 3 : last_output_.reset();
61 3 : ::googlesql::AnalyzerOptions options = MakeAnalyzerOptions();
62 3 : absl::Status s = ::googlesql::AnalyzeStatement("SELECT 1",
63 3 : options,
64 3 : catalog_.get(),
65 3 : type_factory_.get(),
66 3 : &last_output_);
67 6 : EXPECT_TRUE(s.ok()) << s;
68 3 : if (!s.ok() || last_output_ == nullptr) return nullptr;
69 3 : return last_output_->resolved_statement();
70 3 : }
71 :
72 4 : QueryRequest MakeRequest() {
73 4 : QueryRequest req;
74 4 : req.sql = "SELECT 1";
75 4 : return req;
76 4 : }
77 :
78 : std::unique_ptr<::googlesql::TypeFactory> type_factory_{};
79 : std::unique_ptr<::googlesql::SimpleCatalog> catalog_{};
80 : std::unique_ptr<const ::googlesql::AnalyzerOutput> last_output_{};
81 : };
82 :
83 : // `SemanticExecutor` graduated out of `stub_executors.h`; its
84 : // per-route contract is exercised by the real executor's own
85 : // tests at `backend/engine/semantic/executor_test.cc`.
86 : //
87 : // `ControlOpExecutor` graduated out of `stub_executors.h`; its
88 : // per-route + per-statement contract is exercised by the real
89 : // executor's own tests at `backend/engine/control/
90 : // control_op_executor_test.cc`.
91 :
92 1 : TEST_F(StubExecutorsTest, UnsupportedExecuteQueryNamesRoute) {
93 1 : const ::googlesql::ResolvedStatement* stmt = AnalyzeSelect1();
94 1 : ASSERT_NE(stmt, nullptr);
95 1 : UnsupportedExecutor exec;
96 1 : auto out = exec.ExecuteQuery(MakeRequest(), *stmt, catalog_.get());
97 1 : ASSERT_FALSE(out.ok());
98 1 : EXPECT_EQ(out.status().code(), absl::StatusCode::kUnimplemented);
99 1 : const std::string msg(out.status().message());
100 2 : EXPECT_NE(msg.find("unsupported"), std::string::npos) << msg;
101 2 : EXPECT_NE(msg.find("docs/ENGINE_POLICY.md"), std::string::npos) << msg;
102 1 : }
103 :
104 1 : TEST_F(StubExecutorsTest, UnsupportedReportsStatementKind) {
105 : // The message must name the statement kind the executor saw so a
106 : // future operator-facing log filter can grep it out without having
107 : // to round-trip back through the resolved AST.
108 1 : const ::googlesql::ResolvedStatement* stmt = AnalyzeSelect1();
109 1 : ASSERT_NE(stmt, nullptr);
110 1 : UnsupportedExecutor exec;
111 1 : auto out = exec.ExecuteQuery(MakeRequest(), *stmt, catalog_.get());
112 1 : ASSERT_FALSE(out.ok());
113 2 : EXPECT_NE(std::string(out.status().message()).find("QueryStmt"),
114 2 : std::string::npos)
115 2 : << out.status().message();
116 1 : }
117 :
118 1 : TEST_F(StubExecutorsTest, UnsupportedMessageLinksEnginePolicyDoc) {
119 : // `docs/ENGINE_POLICY.md` requires the unsupported
120 : // envelope to point a user at `docs/ENGINE_POLICY.md` so they
121 : // can find the family-by-family posture table without first
122 : // having to locate the plan file. The plan file remains the
123 : // source of truth (the policy doc links back); both pointers
124 : // are emitted so a grep on either lands the reader in the
125 : // right place.
126 1 : const ::googlesql::ResolvedStatement* stmt = AnalyzeSelect1();
127 1 : ASSERT_NE(stmt, nullptr);
128 1 : UnsupportedExecutor exec;
129 1 : auto out = exec.ExecuteQuery(MakeRequest(), *stmt, catalog_.get());
130 1 : ASSERT_FALSE(out.ok());
131 2 : EXPECT_NE(std::string(out.status().message()).find("docs/ENGINE_POLICY.md"),
132 2 : std::string::npos)
133 2 : << out.status().message();
134 1 : }
135 :
136 1 : TEST_F(StubExecutorsTest, UnsupportedMessageNamesOffendingFamily) {
137 : // When the offending node is a specific function family (e.g.
138 : // an `unsupported` row in `functions.yaml` like
139 : // `approx_quantiles`), the message includes a `family:
140 : // function:approx_quantiles` segment so the operator can map the
141 : // failure back to the exact row that owns the posture without
142 : // first running the classifier in their head. A SELECT of an
143 : // unsupported aggregate is the realistic shape: the classifier
144 : // walks the projection list, finds the unsupported row, and
145 : // promotes the route to `kUnsupported` with `offending_node ==
146 : // function:approx_quantiles`. The re-classify inside the
147 : // executor recovers the same string. (We use APPROX_QUANTILES
148 : // because it lives in the default `AllReleasedFunctions` builtin
149 : // set the test catalog wires up; KEYS.* / ML.* / ST_* require
150 : // optional feature toggles that the test catalog does not
151 : // currently enable.)
152 1 : last_output_.reset();
153 1 : ::googlesql::AnalyzerOptions options = MakeAnalyzerOptions();
154 1 : absl::Status s = ::googlesql::AnalyzeStatement(
155 1 : "SELECT APPROX_TOP_COUNT(name, 1) FROM people",
156 1 : options,
157 1 : catalog_.get(),
158 1 : type_factory_.get(),
159 1 : &last_output_);
160 2 : ASSERT_TRUE(s.ok()) << s;
161 1 : const ::googlesql::ResolvedStatement* stmt = nullptr;
162 1 : stmt = last_output_->resolved_statement();
163 1 : ASSERT_NE(stmt, nullptr);
164 1 : UnsupportedExecutor exec;
165 1 : auto out = exec.ExecuteQuery(MakeRequest(), *stmt, catalog_.get());
166 1 : if (out.ok()) {
167 : // APPROX_TOP_COUNT routes to semantic_executor today; fall back to a
168 : // statement the classifier still marks unsupported.
169 0 : last_output_.reset();
170 0 : s = ::googlesql::AnalyzeStatement(
171 0 : "SELECT * FROM GRAPH_TABLE (g MATCH (n) RETURN n)",
172 0 : options,
173 0 : catalog_.get(),
174 0 : type_factory_.get(),
175 0 : &last_output_);
176 0 : if (!s.ok()) {
177 0 : GTEST_SKIP() << "GRAPH_TABLE not analyzable in test catalog: " << s;
178 0 : }
179 0 : stmt = last_output_->resolved_statement();
180 0 : ASSERT_NE(stmt, nullptr);
181 0 : out = exec.ExecuteQuery(MakeRequest(), *stmt, catalog_.get());
182 0 : }
183 1 : ASSERT_FALSE(out.ok());
184 1 : const std::string msg(out.status().message());
185 2 : EXPECT_NE(msg.find("family:"), std::string::npos) << msg;
186 1 : }
187 :
188 : } // namespace
189 : } // namespace coordinator
190 : } // namespace engine
191 : } // namespace backend
192 : } // namespace bigquery_emulator
|