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

            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
        

Generated by: LCOV version 2.0-1