LCOV - code coverage report
Current view: top level - backend/engine/semantic/functions - dispatch_test.cc (source / functions) Coverage Total Hit
Test: _coverage_report.dat Lines: 100.0 % 109 109
Test Date: 2026-07-02 21:01:18 Functions: 100.0 % 14 14

            Line data    Source code
       1              : // End-to-end tests for the semantic-executor function dispatch.
       2              : //
       3              : // Walks the analyzer the same way `eval_expr_test.cc` does so the
       4              : // `ResolvedFunctionCall` the dispatch sees is exactly what the
       5              : // engine sees at runtime. Each test drives a SQL fragment that
       6              : // resolves to a dispatch-table function and asserts both the
       7              : // observable cell and any structured error reason.
       8              : 
       9              : #include "backend/engine/semantic/functions/dispatch.h"
      10              : 
      11              : #include <cmath>
      12              : #include <memory>
      13              : #include <optional>
      14              : #include <string>
      15              : 
      16              : #include "absl/status/status.h"
      17              : #include "absl/status/statusor.h"
      18              : #include "absl/strings/str_cat.h"
      19              : #include "absl/strings/string_view.h"
      20              : #include "backend/engine/semantic/error.h"
      21              : #include "backend/engine/semantic/eval_expr.h"
      22              : #include "backend/engine/semantic/value.h"
      23              : #include "googlesql/public/analyzer.h"
      24              : #include "googlesql/public/analyzer_options.h"
      25              : #include "googlesql/public/analyzer_output.h"
      26              : #include "googlesql/public/builtin_function_options.h"
      27              : #include "googlesql/public/catalog.h"
      28              : #include "googlesql/public/language_options.h"
      29              : #include "googlesql/public/options.pb.h"
      30              : #include "googlesql/public/simple_catalog.h"
      31              : #include "googlesql/public/types/type_factory.h"
      32              : #include "googlesql/public/value.h"
      33              : #include "googlesql/resolved_ast/resolved_ast.h"
      34              : #include "gtest/gtest.h"
      35              : 
      36              : namespace bigquery_emulator {
      37              : namespace backend {
      38              : namespace engine {
      39              : namespace semantic {
      40              : namespace functions {
      41              : namespace {
      42              : 
      43           10 : ::googlesql::AnalyzerOptions MakeAnalyzerOptions() {
      44           10 :   ::googlesql::LanguageOptions language;
      45           10 :   language.EnableMaximumLanguageFeatures();
      46           10 :   language.set_product_mode(::googlesql::PRODUCT_EXTERNAL);
      47           10 :   ::googlesql::AnalyzerOptions options(language);
      48           10 :   options.CreateDefaultArenasIfNotSet();
      49           10 :   return options;
      50           10 : }
      51              : 
      52              : class DispatchTest : public ::testing::Test {
      53              :  protected:
      54           10 :   void SetUp() override {
      55           10 :     type_factory_ = std::make_unique<::googlesql::TypeFactory>();
      56           10 :     catalog_ = std::make_unique<::googlesql::SimpleCatalog>(
      57           10 :         "dispatch_catalog", type_factory_.get());
      58           10 :     catalog_->AddBuiltinFunctions(
      59           10 :         ::googlesql::BuiltinFunctionOptions::AllReleasedFunctions());
      60           10 :   }
      61              : 
      62           10 :   const ::googlesql::ResolvedExpr* AnalyzeExpr(absl::string_view expr) {
      63           10 :     last_output_.reset();
      64           10 :     const std::string sql = absl::StrCat("SELECT ", expr);
      65           10 :     absl::Status s = ::googlesql::AnalyzeStatement(sql,
      66           10 :                                                    MakeAnalyzerOptions(),
      67           10 :                                                    catalog_.get(),
      68           10 :                                                    type_factory_.get(),
      69           10 :                                                    &last_output_);
      70           20 :     EXPECT_TRUE(s.ok()) << s;
      71           10 :     if (!s.ok() || last_output_ == nullptr) return nullptr;
      72           10 :     const auto* stmt = last_output_->resolved_statement()
      73           10 :                            ->GetAs<::googlesql::ResolvedQueryStmt>();
      74           10 :     if (stmt == nullptr) return nullptr;
      75           10 :     const auto* project =
      76           10 :         stmt->query()->GetAs<::googlesql::ResolvedProjectScan>();
      77           10 :     if (project == nullptr || project->expr_list_size() == 0) return nullptr;
      78           10 :     return project->expr_list(0)->expr();
      79           10 :   }
      80              : 
      81              :   std::unique_ptr<::googlesql::TypeFactory> type_factory_{};
      82              :   std::unique_ptr<::googlesql::SimpleCatalog> catalog_{};
      83              :   std::unique_ptr<const ::googlesql::AnalyzerOutput> last_output_{};
      84              : };
      85              : 
      86            1 : TEST_F(DispatchTest, BitCountReturnsSetBitCount) {
      87              :   // Drive `BIT_COUNT(7)` end-to-end through the analyzer + executor.
      88            1 :   const auto* expr = AnalyzeExpr("BIT_COUNT(7)");
      89            1 :   ASSERT_NE(expr, nullptr);
      90            1 :   auto v = EvalExpr(*expr, EvalContext{});
      91            2 :   ASSERT_TRUE(v.ok()) << v.status();
      92            1 :   EXPECT_EQ(v->int64_value(), 3);
      93            1 : }
      94              : 
      95            1 : TEST_F(DispatchTest, BitCountOnNegativeOneReturnsSixtyFour) {
      96              :   // The two's-complement edge case the row exists to pin.
      97            1 :   const auto* expr = AnalyzeExpr("BIT_COUNT(-1)");
      98            1 :   ASSERT_NE(expr, nullptr);
      99            1 :   auto v = EvalExpr(*expr, EvalContext{});
     100            2 :   ASSERT_TRUE(v.ok()) << v.status();
     101            1 :   EXPECT_EQ(v->int64_value(), 64);
     102            1 : }
     103              : 
     104            1 : TEST_F(DispatchTest, IeeeDivideByZeroProducesInf) {
     105              :   // IEEE_DIVIDE never errors: 1.0 / 0.0 -> +Inf per IEEE 754, not
     106              :   // a structured `kDivisionByZero` status the way `/` would.
     107            1 :   const auto* expr = AnalyzeExpr("IEEE_DIVIDE(1.0, 0.0)");
     108            1 :   ASSERT_NE(expr, nullptr);
     109            1 :   auto v = EvalExpr(*expr, EvalContext{});
     110            2 :   ASSERT_TRUE(v.ok()) << v.status();
     111            1 :   EXPECT_TRUE(std::isinf(v->double_value()));
     112            1 :   EXPECT_GT(v->double_value(), 0.0);
     113            1 : }
     114              : 
     115            1 : TEST_F(DispatchTest, IeeeDivideZeroOverZeroProducesNan) {
     116            1 :   const auto* expr = AnalyzeExpr("IEEE_DIVIDE(0.0, 0.0)");
     117            1 :   ASSERT_NE(expr, nullptr);
     118            1 :   auto v = EvalExpr(*expr, EvalContext{});
     119            2 :   ASSERT_TRUE(v.ok()) << v.status();
     120            1 :   EXPECT_TRUE(std::isnan(v->double_value()));
     121            1 : }
     122              : 
     123            1 : TEST_F(DispatchTest, IeeeDivideFinitePath) {
     124            1 :   const auto* expr = AnalyzeExpr("IEEE_DIVIDE(10.0, 4.0)");
     125            1 :   ASSERT_NE(expr, nullptr);
     126            1 :   auto v = EvalExpr(*expr, EvalContext{});
     127            2 :   ASSERT_TRUE(v.ok()) << v.status();
     128            1 :   EXPECT_DOUBLE_EQ(v->double_value(), 2.5);
     129            1 : }
     130              : 
     131            1 : TEST_F(DispatchTest, SoundexReferenceValue) {
     132              :   // BigQuery documentation example: `SOUNDEX('Ashcraft') -> A261`.
     133            1 :   const auto* expr = AnalyzeExpr("SOUNDEX('Ashcraft')");
     134            1 :   ASSERT_NE(expr, nullptr);
     135            1 :   auto v = EvalExpr(*expr, EvalContext{});
     136            2 :   ASSERT_TRUE(v.ok()) << v.status();
     137            1 :   EXPECT_EQ(v->string_value(), "A261");
     138            1 : }
     139              : 
     140            1 : TEST_F(DispatchTest, SoundexEmptyString) {
     141            1 :   const auto* expr = AnalyzeExpr("SOUNDEX('')");
     142            1 :   ASSERT_NE(expr, nullptr);
     143            1 :   auto v = EvalExpr(*expr, EvalContext{});
     144            2 :   ASSERT_TRUE(v.ok()) << v.status();
     145            1 :   EXPECT_EQ(v->string_value(), "");
     146            1 : }
     147              : 
     148            1 : TEST_F(DispatchTest, InstrTwoArgBasic) {
     149            1 :   const auto* expr = AnalyzeExpr("INSTR('hello world', 'world')");
     150            1 :   ASSERT_NE(expr, nullptr);
     151            1 :   auto v = EvalExpr(*expr, EvalContext{});
     152            2 :   ASSERT_TRUE(v.ok()) << v.status();
     153            1 :   EXPECT_EQ(v->int64_value(), 7);
     154            1 : }
     155              : 
     156            1 : TEST_F(DispatchTest, InstrNegativePosition) {
     157              :   // INSTR('ababab', 'ab', -1) -> 5 (last occurrence, 1-based).
     158            1 :   const auto* expr = AnalyzeExpr("INSTR('ababab', 'ab', -1)");
     159            1 :   ASSERT_NE(expr, nullptr);
     160            1 :   auto v = EvalExpr(*expr, EvalContext{});
     161            2 :   ASSERT_TRUE(v.ok()) << v.status();
     162            1 :   EXPECT_EQ(v->int64_value(), 5);
     163            1 : }
     164              : 
     165            1 : TEST_F(DispatchTest, InstrOccurrence) {
     166              :   // INSTR('ababab', 'ab', 1, 2) -> 3 (second occurrence).
     167            1 :   const auto* expr = AnalyzeExpr("INSTR('ababab', 'ab', 1, 2)");
     168            1 :   ASSERT_NE(expr, nullptr);
     169            1 :   auto v = EvalExpr(*expr, EvalContext{});
     170            2 :   ASSERT_TRUE(v.ok()) << v.status();
     171            1 :   EXPECT_EQ(v->int64_value(), 3);
     172            1 : }
     173              : 
     174              : // The dispatch table returns nullopt for unknown function names;
     175              : // `EvalFunctionCall` is expected to surface a `kNotImplemented`
     176              : // status in that case. The marker here is a sanity check that the
     177              : // table is consulted; the actual status is asserted in the
     178              : // matching `eval_expr_test.cc` case.
     179            1 : TEST(DispatchTableTest, UnknownNameReturnsNullopt) {
     180            1 :   std::vector<Value> args = {Value::Int64(1)};
     181            1 :   auto v = Dispatch("__not_a_function__", args, nullptr);
     182            1 :   EXPECT_FALSE(v.has_value());
     183            1 : }
     184              : 
     185              : }  // namespace
     186              : }  // namespace functions
     187              : }  // namespace semantic
     188              : }  // namespace engine
     189              : }  // namespace backend
     190              : }  // namespace bigquery_emulator
        

Generated by: LCOV version 2.0-1