LCOV - code coverage report
Current view: top level - engine/coordinator - stub_executors_test.cc (source / functions) Coverage Total Hit
Test: _coverage_report.dat Lines: 86.3 % 102 88
Test Date: 2026-08-08 08:38:15 Functions: 100.0 % 8 8

            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
        

Generated by: LCOV version 2.0-1