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
|