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

            Line data    Source code
       1              : #include "backend/engine/duckdb/transpiler/transpiler_test_fixture.h"
       2              : 
       3              : namespace bigquery_emulator {
       4              : namespace backend {
       5              : namespace engine {
       6              : namespace duckdb {
       7              : namespace transpiler {
       8              : 
       9            1 : TEST_F(TranspilerTest, EmitLiteralInt64) {
      10            1 :   const ::googlesql::ResolvedStatement* stmt = Analyze("SELECT 42 AS n");
      11            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      12            1 :   ASSERT_NE(expr, nullptr);
      13            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_LITERAL);
      14            1 :   TestTranspiler t;
      15            1 :   EXPECT_EQ(t.EmitLiteral(expr->GetAs<::googlesql::ResolvedLiteral>()), "42");
      16            1 : }
      17              : 
      18            1 : TEST_F(TranspilerTest, EmitLiteralString) {
      19            1 :   const ::googlesql::ResolvedStatement* stmt = Analyze("SELECT 'hi' AS s");
      20            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      21            1 :   ASSERT_NE(expr, nullptr);
      22            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_LITERAL);
      23            1 :   TestTranspiler t;
      24              :   // We must emit single-quoted strings: DuckDB reads double-quoted
      25              :   // text as an *identifier*, so a `"hi"` literal would be a column
      26              :   // reference rather than a string. EmitLiteral overrides
      27              :   // GoogleSQL's default double-quoted form on TYPE_STRING for
      28              :   // exactly this reason -- otherwise every string-bearing query
      29              :   // would surface UNIMPLEMENTED instead of a real result.
      30            1 :   EXPECT_EQ(t.EmitLiteral(expr->GetAs<::googlesql::ResolvedLiteral>()), "'hi'");
      31            1 : }
      32              : 
      33            1 : TEST_F(TranspilerTest, EmitLiteralBoolTrue) {
      34            1 :   const ::googlesql::ResolvedStatement* stmt = Analyze("SELECT TRUE AS b");
      35            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      36            1 :   ASSERT_NE(expr, nullptr);
      37            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_LITERAL);
      38            1 :   TestTranspiler t;
      39            1 :   EXPECT_EQ(t.EmitLiteral(expr->GetAs<::googlesql::ResolvedLiteral>()), "true");
      40            1 : }
      41              : 
      42            1 : TEST_F(TranspilerTest, EmitColumnRefQuotesIdentifier) {
      43              :   // The analyzer collapses a bare `SELECT id FROM people` straight
      44              :   // onto the TableScan (no wrapping ProjectScan), so to land a
      45              :   // standalone `ResolvedColumnRef` we wrap the column in a
      46              :   // function call. `COALESCE(id, 0)` keeps the test focused: the
      47              :   // first argument is the ColumnRef we want to assert on.
      48            1 :   const ::googlesql::ResolvedStatement* stmt =
      49            1 :       Analyze("SELECT COALESCE(id, 0) FROM people");
      50            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      51            1 :   ASSERT_NE(expr, nullptr);
      52            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
      53            1 :   const ::googlesql::ResolvedFunctionCall* call =
      54            1 :       expr->GetAs<::googlesql::ResolvedFunctionCall>();
      55            1 :   ASSERT_GE(call->argument_list_size(), 1);
      56            1 :   const ::googlesql::ResolvedExpr* arg = call->argument_list(0);
      57            1 :   ASSERT_NE(arg, nullptr);
      58            1 :   ASSERT_EQ(arg->node_kind(), ::googlesql::RESOLVED_COLUMN_REF);
      59            1 :   TestTranspiler t;
      60            1 :   EXPECT_EQ(t.EmitColumnRef(arg->GetAs<::googlesql::ResolvedColumnRef>()),
      61            1 :             "\"id\"");
      62            1 : }
      63              : 
      64            1 : TEST_F(TranspilerTest, EmitFunctionCallCoalesce) {
      65            1 :   const ::googlesql::ResolvedStatement* stmt =
      66            1 :       Analyze("SELECT COALESCE(name, 'unknown') AS n FROM people");
      67            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      68            1 :   ASSERT_NE(expr, nullptr);
      69            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
      70            1 :   TestTranspiler t;
      71            1 :   EXPECT_EQ(
      72            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()),
      73            1 :       "COALESCE(\"name\", 'unknown')");
      74            1 : }
      75              : 
      76            1 : TEST_F(TranspilerTest, EmitFunctionCallIfnull) {
      77            1 :   const ::googlesql::ResolvedStatement* stmt =
      78            1 :       Analyze("SELECT IFNULL(name, 'unknown') AS n FROM people");
      79            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      80            1 :   ASSERT_NE(expr, nullptr);
      81            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
      82            1 :   TestTranspiler t;
      83            1 :   EXPECT_EQ(
      84            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()),
      85            1 :       "IFNULL(\"name\", 'unknown')");
      86            1 : }
      87              : 
      88            1 : TEST_F(TranspilerTest, EmitFunctionCallNonLoweringDispositionReturnsEmpty) {
      89              :   // `BIT_COUNT` is on the `semantic_executor` route in
      90              :   // `functions.yaml` (BQ flavor differs from DuckDB's `bit_count`)
      91              :   // with the body deferred to `docs/ENGINE_POLICY.md`.
      92              :   // The transpiler has no DuckDB lowering for this disposition, so
      93              :   // the emit returns "" and the engine surfaces UNIMPLEMENTED for
      94              :   // the whole query.
      95            1 :   const ::googlesql::ResolvedStatement* stmt =
      96            1 :       Analyze("SELECT BIT_COUNT(id) AS n FROM people");
      97            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
      98            1 :   ASSERT_NE(expr, nullptr);
      99            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
     100            1 :   TestTranspiler t;
     101            1 :   EXPECT_EQ(
     102            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()), "");
     103            1 : }
     104              : 
     105            1 : TEST_F(TranspilerTest, EmitFunctionCallMappedFunction) {
     106              :   // Disposition-table-backed scalars: `ABS(id)` lowers to DuckDB's
     107              :   // `ABS(...)`. The casing of the emitted function name comes from
     108              :   // the YAML disposition (we render the duckdb_name verbatim); the
     109              :   // BQ-side `abs` lookup is case-insensitive.
     110            1 :   const ::googlesql::ResolvedStatement* stmt =
     111            1 :       Analyze("SELECT ABS(id) AS n FROM people");
     112            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
     113            1 :   ASSERT_NE(expr, nullptr);
     114            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
     115            1 :   TestTranspiler t;
     116            1 :   EXPECT_EQ(
     117            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()),
     118            1 :       "ABS(\"id\")");
     119            1 : }
     120              : 
     121            1 : TEST_F(TranspilerTest, EmitFunctionCallLengthMaps) {
     122              :   // `LENGTH(name)` -> `LENGTH("name")`. Two-arg variants don't exist
     123              :   // for LENGTH in either dialect; the single-arg shape is the entire
     124              :   // disposition surface.
     125            1 :   const ::googlesql::ResolvedStatement* stmt =
     126            1 :       Analyze("SELECT LENGTH(name) AS n FROM people");
     127            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
     128            1 :   ASSERT_NE(expr, nullptr);
     129            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
     130            1 :   TestTranspiler t;
     131            1 :   EXPECT_EQ(
     132            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()),
     133            1 :       "LENGTH(\"name\")");
     134            1 : }
     135              : 
     136            1 : TEST_F(TranspilerTest, EmitFunctionCallReadyDuckdbUdf) {
     137              :   // Ready `duckdb_udf` rows emit identically to `duckdb_native`:
     138              :   // the transpiler renders `<duckdb_name>(<args>)` and DuckDB
     139              :   // resolves the call to the registered polyfill macro. `MOD`
     140              :   // flipped to ready in the numeric-family commit; the row carries
     141              :   // `duckdb_name=bq_mod`.
     142            1 :   const ::googlesql::ResolvedStatement* stmt =
     143            1 :       Analyze("SELECT MOD(id, 3) AS m FROM people");
     144            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
     145            1 :   ASSERT_NE(expr, nullptr);
     146            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
     147            1 :   TestTranspiler t;
     148            1 :   EXPECT_EQ(
     149            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()),
     150            1 :       "bq_mod(\"id\", 3)");
     151            1 : }
     152              : 
     153            1 : TEST_F(TranspilerTest, EmitFunctionCallSafeModeReturnsEmpty) {
     154              :   // SAFE.<fn>(...) sets `error_mode = SAFE_ERROR_MODE`. DuckDB has no
     155              :   // native SAFE analog yet, so the emit short-circuits to "" before
     156              :   // consulting the disposition table -- this would otherwise emit
     157              :   // ABS("id") and silently lose the SAFE error semantics.
     158            1 :   const ::googlesql::ResolvedStatement* stmt =
     159            1 :       Analyze("SELECT SAFE.ABS(id) AS n FROM people");
     160            1 :   const ::googlesql::ResolvedExpr* expr = QueryFirstSelectExpr(stmt);
     161            1 :   ASSERT_NE(expr, nullptr);
     162            1 :   ASSERT_EQ(expr->node_kind(), ::googlesql::RESOLVED_FUNCTION_CALL);
     163            1 :   TestTranspiler t;
     164            1 :   EXPECT_EQ(
     165            1 :       t.EmitFunctionCall(expr->GetAs<::googlesql::ResolvedFunctionCall>()), "");
     166            1 : }
     167              : 
     168              : }  // namespace transpiler
     169              : }  // namespace duckdb
     170              : }  // namespace engine
     171              : }  // namespace backend
     172              : }  // namespace bigquery_emulator
        

Generated by: LCOV version 2.0-1