Line data Source code
1 : // Extended `EvalExpr` coverage (parameters, intervals, argument refs).
2 :
3 : #include <cmath>
4 : #include <memory>
5 :
6 : #include "backend/engine/semantic/error.h"
7 : #include "backend/engine/semantic/eval_expr_test_fixture.h"
8 : #include "backend/engine/semantic/value.h"
9 : #include "googlesql/public/analyzer_options.h"
10 : #include "googlesql/public/types/type_factory.h"
11 : #include "googlesql/public/value.h"
12 : #include "googlesql/resolved_ast/resolved_ast.h"
13 :
14 : namespace bigquery_emulator {
15 : namespace backend {
16 : namespace engine {
17 : namespace semantic {
18 :
19 1 : TEST_F(EvalExprTest, ParameterByNameResolves) {
20 1 : ::googlesql::AnalyzerOptions options = MakeAnalyzerOptions();
21 1 : ASSERT_TRUE(
22 1 : options.AddQueryParameter("p", ::googlesql::types::Int64Type()).ok());
23 1 : const auto* expr = AnalyzeExpr("@p + 1", options);
24 1 : ASSERT_NE(expr, nullptr);
25 1 : ParameterBindings bindings;
26 1 : bindings.by_name["p"] = Value::Int64(40);
27 1 : EvalContext ctx;
28 1 : ctx.parameters = &bindings;
29 1 : auto v = EvalExpr(*expr, ctx);
30 2 : ASSERT_TRUE(v.ok()) << v.status();
31 1 : EXPECT_EQ(v->int64_value(), 41);
32 1 : }
33 :
34 1 : TEST_F(EvalExprTest, ParameterByPositionResolves) {
35 1 : ::googlesql::AnalyzerOptions options = MakeAnalyzerOptions();
36 1 : options.set_parameter_mode(::googlesql::PARAMETER_POSITIONAL);
37 1 : ASSERT_TRUE(
38 1 : options.AddPositionalQueryParameter(::googlesql::types::Int64Type())
39 1 : .ok());
40 1 : const auto* expr = AnalyzeExpr("? + 1", options);
41 1 : ASSERT_NE(expr, nullptr);
42 1 : ParameterBindings bindings;
43 1 : bindings.by_position.push_back(Value::Int64(40));
44 1 : EvalContext ctx;
45 1 : ctx.parameters = &bindings;
46 1 : auto v = EvalExpr(*expr, ctx);
47 2 : ASSERT_TRUE(v.ok()) << v.status();
48 1 : EXPECT_EQ(v->int64_value(), 41);
49 1 : }
50 :
51 1 : TEST_F(EvalExprTest, UnaryMinusInt64Min) {
52 1 : const auto* expr = AnalyzeExpr("-(-9223372036854775807 - 1)");
53 1 : ASSERT_NE(expr, nullptr);
54 1 : auto v = EvalExpr(*expr, EvalContext{});
55 : // -INT64_MIN overflows; the inner subtraction reaches INT64_MIN,
56 : // then unary minus overflows.
57 1 : ASSERT_FALSE(v.ok());
58 1 : EXPECT_EQ(GetSemanticErrorReason(v.status()), SemanticErrorReason::kOverflow);
59 1 : }
60 :
61 1 : TEST_F(EvalExprTest, IsNullForNullOperand) {
62 1 : const auto* expr = AnalyzeExpr("CAST(NULL AS INT64) IS NULL");
63 1 : ASSERT_NE(expr, nullptr);
64 1 : auto v = EvalExpr(*expr, EvalContext{});
65 2 : ASSERT_TRUE(v.ok()) << v.status();
66 1 : EXPECT_TRUE(v->bool_value());
67 1 : }
68 :
69 1 : TEST_F(EvalExprTest, IsNotNullForNonNullOperand) {
70 1 : const auto* expr = AnalyzeExpr("1 IS NOT NULL");
71 1 : ASSERT_NE(expr, nullptr);
72 1 : auto v = EvalExpr(*expr, EvalContext{});
73 2 : ASSERT_TRUE(v.ok()) << v.status();
74 1 : EXPECT_TRUE(v->bool_value());
75 1 : }
76 :
77 1 : TEST_F(EvalExprTest, LikeOperator) {
78 1 : const auto* expr = AnalyzeExpr(R"("abcd" LIKE "a%d")");
79 1 : ASSERT_NE(expr, nullptr);
80 1 : auto v = EvalExpr(*expr, EvalContext{});
81 2 : ASSERT_TRUE(v.ok()) << v.status();
82 1 : EXPECT_TRUE(v->bool_value());
83 1 : }
84 :
85 1 : TEST_F(EvalExprTest, BetweenOperatorOnDates) {
86 1 : const auto* expr =
87 1 : AnalyzeExpr(R"(DATE "2022-09-10" BETWEEN "2022-09-01" AND "2022-10-01")");
88 1 : ASSERT_NE(expr, nullptr);
89 1 : auto v = EvalExpr(*expr, EvalContext{});
90 2 : ASSERT_TRUE(v.ok()) << v.status();
91 1 : EXPECT_TRUE(v->bool_value());
92 1 : }
93 :
94 1 : TEST_F(EvalExprTest, InOperatorNullLhs) {
95 1 : const auto* expr = AnalyzeExpr("NULL IN (1)");
96 1 : ASSERT_NE(expr, nullptr);
97 1 : auto v = EvalExpr(*expr, EvalContext{});
98 2 : ASSERT_TRUE(v.ok()) << v.status();
99 1 : EXPECT_TRUE(v->is_null());
100 1 : }
101 :
102 1 : TEST_F(EvalExprTest, BitwiseAndOperator) {
103 1 : const auto* expr = AnalyzeExpr("3 & 1");
104 1 : ASSERT_NE(expr, nullptr);
105 1 : auto v = EvalExpr(*expr, EvalContext{});
106 2 : ASSERT_TRUE(v.ok()) << v.status();
107 1 : EXPECT_EQ(v->int64_value(), 1);
108 1 : }
109 :
110 1 : TEST_F(EvalExprTest, IsDistinctFromNullAndNull) {
111 1 : const auto* expr = AnalyzeExpr("NULL IS DISTINCT FROM NULL");
112 1 : ASSERT_NE(expr, nullptr);
113 1 : auto v = EvalExpr(*expr, EvalContext{});
114 2 : ASSERT_TRUE(v.ok()) << v.status();
115 1 : EXPECT_FALSE(v->bool_value());
116 1 : }
117 :
118 1 : TEST_F(EvalExprTest, IntervalLiteralDay) {
119 1 : const auto* expr = AnalyzeExpr("INTERVAL 29 DAY");
120 1 : ASSERT_NE(expr, nullptr);
121 1 : auto v = EvalExpr(*expr, EvalContext{});
122 2 : ASSERT_TRUE(v.ok()) << v.status();
123 1 : EXPECT_FALSE(v->is_null());
124 1 : EXPECT_EQ(v->type_kind(), ::googlesql::TYPE_INTERVAL);
125 1 : }
126 :
127 1 : TEST_F(EvalExprTest, JustifyDaysOn29Days) {
128 1 : const auto* expr = AnalyzeExpr("JUSTIFY_DAYS(INTERVAL 29 DAY)");
129 1 : ASSERT_NE(expr, nullptr);
130 1 : auto v = EvalExpr(*expr, EvalContext{});
131 2 : ASSERT_TRUE(v.ok()) << v.status();
132 1 : EXPECT_EQ(v->interval_value().ToString(), "0-0 29 0:0:0");
133 1 : }
134 :
135 1 : TEST_F(EvalExprTest, JustifyHoursNegativeMinuteLiteral) {
136 1 : const auto* expr = AnalyzeExpr("JUSTIFY_HOURS(INTERVAL -12345 MINUTE)");
137 1 : ASSERT_NE(expr, nullptr);
138 1 : auto v = EvalExpr(*expr, EvalContext{});
139 2 : ASSERT_TRUE(v.ok()) << v.status();
140 1 : EXPECT_EQ(v->interval_value().ToString(), "0-0 -8 -13:45:0");
141 1 : }
142 :
143 1 : TEST_F(EvalExprTest, ResolvedArgumentRefResolvesAgainstFrameStack) {
144 : // `ResolvedArgumentRef` resolves through `EvalContext::arguments`
145 : // (a `FrameStack`). The analyzer usually only emits this kind
146 : // inside a UDF / TVF body, but the node API supports direct
147 : // construction, so we build one with `MakeResolvedArgumentRef`
148 : // and verify the executor reads the matching frame binding.
149 1 : FrameStack args;
150 1 : ASSERT_TRUE(args.Declare("x", Value::Int64(7)).ok());
151 :
152 1 : std::unique_ptr<::googlesql::ResolvedArgumentRef> ref =
153 1 : ::googlesql::MakeResolvedArgumentRef(
154 1 : ::googlesql::types::Int64Type(),
155 1 : "x",
156 1 : ::googlesql::ResolvedArgumentDef::SCALAR);
157 1 : EvalContext ctx;
158 1 : ctx.arguments = &args;
159 1 : auto v = EvalExpr(*ref, ctx);
160 2 : ASSERT_TRUE(v.ok()) << v.status();
161 1 : EXPECT_EQ(v->int64_value(), 7);
162 1 : }
163 :
164 1 : TEST_F(EvalExprTest, ResolvedArgumentRefCaseInsensitiveMatch) {
165 : // The script driver / UDF call site lowers identifier names on
166 : // declare. The `FrameStack` is case-insensitive on lookup, so a
167 : // body reference that case-shifts an argument still resolves.
168 1 : FrameStack args;
169 1 : ASSERT_TRUE(args.Declare("FooBar", Value::Int64(11)).ok());
170 :
171 1 : std::unique_ptr<::googlesql::ResolvedArgumentRef> ref =
172 1 : ::googlesql::MakeResolvedArgumentRef(
173 1 : ::googlesql::types::Int64Type(),
174 1 : "FOOBAR",
175 1 : ::googlesql::ResolvedArgumentDef::SCALAR);
176 1 : EvalContext ctx;
177 1 : ctx.arguments = &args;
178 1 : auto v = EvalExpr(*ref, ctx);
179 2 : ASSERT_TRUE(v.ok()) << v.status();
180 1 : EXPECT_EQ(v->int64_value(), 11);
181 1 : }
182 :
183 1 : TEST_F(EvalExprTest, ResolvedArgumentRefWithoutFrameStackSurfacesError) {
184 : // No frame on the context -- the executor must surface a clean
185 : // `kInvalidArgument` naming the missing argument rather than
186 : // substituting NULL.
187 1 : std::unique_ptr<::googlesql::ResolvedArgumentRef> ref =
188 1 : ::googlesql::MakeResolvedArgumentRef(
189 1 : ::googlesql::types::Int64Type(),
190 1 : "y",
191 1 : ::googlesql::ResolvedArgumentDef::SCALAR);
192 1 : auto v = EvalExpr(*ref, EvalContext{});
193 1 : ASSERT_FALSE(v.ok());
194 1 : EXPECT_EQ(GetSemanticErrorReason(v.status()),
195 1 : SemanticErrorReason::kInvalidArgument);
196 1 : EXPECT_EQ(v.status().code(), absl::StatusCode::kInvalidArgument);
197 1 : }
198 :
199 1 : TEST_F(EvalExprTest, ResolvedArgumentRefMissingBindingSurfacesError) {
200 : // Frame exists but does not bind the referenced name.
201 1 : FrameStack args;
202 1 : ASSERT_TRUE(args.Declare("x", Value::Int64(1)).ok());
203 :
204 1 : std::unique_ptr<::googlesql::ResolvedArgumentRef> ref =
205 1 : ::googlesql::MakeResolvedArgumentRef(
206 1 : ::googlesql::types::Int64Type(),
207 1 : "missing",
208 1 : ::googlesql::ResolvedArgumentDef::SCALAR);
209 1 : EvalContext ctx;
210 1 : ctx.arguments = &args;
211 1 : auto v = EvalExpr(*ref, ctx);
212 1 : ASSERT_FALSE(v.ok());
213 1 : EXPECT_EQ(GetSemanticErrorReason(v.status()),
214 1 : SemanticErrorReason::kInvalidArgument);
215 1 : }
216 :
217 1 : TEST_F(EvalExprTest, ResolvedConstantResolvesToCatalogValue) {
218 : // Register a `SimpleConstant` on the test catalog and analyze
219 : // `SELECT <constant>`. The analyzer emits a `ResolvedConstant`
220 : // whose `constant()` points at the registered entry; `EvalExpr`
221 : // must return the bound value verbatim (no NULL substitution).
222 1 : std::unique_ptr<::googlesql::SimpleConstant> meaning;
223 1 : ASSERT_TRUE(::googlesql::SimpleConstant::Create(
224 1 : {"meaning_of_life"}, ::googlesql::Value::Int64(42), &meaning)
225 1 : .ok());
226 1 : catalog_->AddOwnedConstant(meaning.release());
227 :
228 1 : const auto* expr = AnalyzeExpr("meaning_of_life");
229 1 : ASSERT_NE(expr, nullptr);
230 2 : ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_CONSTANT)
231 2 : << "analyzer should have resolved bare identifier to ResolvedConstant; "
232 2 : "got "
233 2 : << expr->node_kind_string();
234 1 : auto v = EvalExpr(*expr, EvalContext{});
235 2 : ASSERT_TRUE(v.ok()) << v.status();
236 1 : EXPECT_EQ(v->int64_value(), 42);
237 1 : }
238 :
239 1 : TEST_F(EvalExprTest, FloatNanArithmeticProducesNan) {
240 1 : const auto* expr = AnalyzeExpr("CAST('NaN' AS FLOAT64) + 1");
241 1 : ASSERT_NE(expr, nullptr);
242 1 : auto v = EvalExpr(*expr, EvalContext{});
243 2 : ASSERT_TRUE(v.ok()) << v.status();
244 1 : EXPECT_TRUE(std::isnan(v->double_value()));
245 1 : }
246 :
247 1 : TEST_F(EvalExprTest, CastStringDateOnlyToTimestamp) {
248 1 : const auto* expr = AnalyzeExpr("CAST('1800-01-01' AS TIMESTAMP)");
249 1 : ASSERT_NE(expr, nullptr);
250 1 : auto v = EvalExpr(*expr, EvalContext{});
251 2 : ASSERT_TRUE(v.ok()) << v.status();
252 1 : EXPECT_EQ(v->type_kind(), ::googlesql::TYPE_TIMESTAMP);
253 1 : }
254 :
255 1 : TEST_F(EvalExprTest, DateBetweenOldDatesEvaluates) {
256 1 : const auto* expr = AnalyzeExpr(
257 1 : "DATE('1800-01-01') BETWEEN DATE('1800-01-01') AND DATE('1899-12-31')");
258 1 : ASSERT_NE(expr, nullptr);
259 1 : auto v = EvalExpr(*expr, EvalContext{});
260 2 : ASSERT_TRUE(v.ok()) << v.status();
261 1 : EXPECT_TRUE(v->bool_value());
262 1 : }
263 :
264 : } // namespace semantic
265 : } // namespace engine
266 : } // namespace backend
267 : } // namespace bigquery_emulator
|