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

            Line data    Source code
       1              : // Unit tests for the BigQuery datetime polyfill macros.
       2              : //
       3              : // Each test drives the macro directly against an in-process DuckDB
       4              : // connection and exercises both the common path and the
       5              : // BigQuery-specific edge case the wrapper exists to pin.
       6              : 
       7              : #include <cstdint>
       8              : #include <string>
       9              : 
      10              : #include "absl/status/status.h"
      11              : #include "backend/engine/duckdb/udf/registrar.h"
      12              : #include "duckdb.h"
      13              : #include "gtest/gtest.h"
      14              : 
      15              : namespace bigquery_emulator {
      16              : namespace backend {
      17              : namespace engine {
      18              : namespace duckdb {
      19              : namespace udf {
      20              : namespace {
      21              : 
      22              : class DatetimeMacrosTest : public ::testing::Test {
      23              :  protected:
      24           16 :   void SetUp() override {
      25           16 :     ASSERT_EQ(::duckdb_open(nullptr, &db_), ::DuckDBSuccess);
      26           16 :     ASSERT_EQ(::duckdb_connect(db_, &conn_), ::DuckDBSuccess);
      27           16 :     absl::Status reg = RegisterAll(conn_);
      28           32 :     ASSERT_TRUE(reg.ok()) << reg;
      29           16 :   }
      30              : 
      31           16 :   void TearDown() override {
      32           16 :     if (conn_ != nullptr) ::duckdb_disconnect(&conn_);
      33           16 :     if (db_ != nullptr) ::duckdb_close(&db_);
      34           16 :   }
      35              : 
      36           11 :   int64_t RunInt64(const std::string& sql) {
      37           11 :     ::duckdb_result result;
      38           11 :     auto rc = ::duckdb_query(conn_, sql.c_str(), &result);
      39           22 :     EXPECT_EQ(rc, ::DuckDBSuccess) << "DuckDB rejected: "
      40           22 :                                    << (::duckdb_result_error(&result) == nullptr
      41           22 :                                            ? "(no error)"
      42           22 :                                            : ::duckdb_result_error(&result))
      43           22 :                                    << " (sql=" << sql << ")";
      44           11 :     int64_t v = ::duckdb_value_int64(&result, 0, 0);
      45           11 :     ::duckdb_destroy_result(&result);
      46           11 :     return v;
      47           11 :   }
      48              : 
      49            6 :   bool RunBool(const std::string& sql) {
      50            6 :     ::duckdb_result result;
      51            6 :     auto rc = ::duckdb_query(conn_, sql.c_str(), &result);
      52           12 :     EXPECT_EQ(rc, ::DuckDBSuccess) << "DuckDB rejected: " << sql;
      53            6 :     bool v = ::duckdb_value_boolean(&result, 0, 0);
      54            6 :     ::duckdb_destroy_result(&result);
      55            6 :     return v;
      56            6 :   }
      57              : 
      58              :   ::duckdb_database db_ = nullptr;
      59              :   ::duckdb_connection conn_ = nullptr;
      60              : };
      61              : 
      62              : // 2024-01-01 00:00:00 UTC -> 1704067200 seconds since epoch.
      63              : constexpr int64_t kSec2024_01_01 = 1704067200;
      64              : constexpr int64_t kMs2024_01_01 = 1704067200LL * 1000;
      65              : constexpr int64_t kUs2024_01_01 = 1704067200LL * 1000 * 1000;
      66              : 
      67              : // --- bq_unix_seconds ---------------------------------------------
      68              : 
      69            1 : TEST_F(DatetimeMacrosTest, UnixSecondsWholeSecond) {
      70            1 :   EXPECT_EQ(
      71            1 :       RunInt64("SELECT bq_unix_seconds(TIMESTAMPTZ '2024-01-01 00:00:00+00')"),
      72            1 :       kSec2024_01_01);
      73            1 : }
      74              : 
      75            1 : TEST_F(DatetimeMacrosTest, UnixSecondsTruncatesSubsecond) {
      76              :   // Edge case pinned: BigQuery UNIX_SECONDS truncates higher
      77              :   // precision. A regression that switched the CAST to a rounding
      78              :   // form would surface here as 1704067201 (rounding .999 up to
      79              :   // the next second) instead of 1704067200.
      80            1 :   EXPECT_EQ(
      81            1 :       RunInt64(
      82            1 :           "SELECT bq_unix_seconds(TIMESTAMPTZ '2024-01-01 00:00:00.999+00')"),
      83            1 :       kSec2024_01_01);
      84            1 : }
      85              : 
      86            1 : TEST_F(DatetimeMacrosTest, UnixSecondsNullPropagation) {
      87            1 :   EXPECT_TRUE(RunBool("SELECT bq_unix_seconds(NULL::TIMESTAMPTZ) IS NULL"));
      88            1 : }
      89              : 
      90              : // --- bq_unix_millis ----------------------------------------------
      91              : 
      92            1 : TEST_F(DatetimeMacrosTest, UnixMillisWholeSecond) {
      93            1 :   EXPECT_EQ(
      94            1 :       RunInt64("SELECT bq_unix_millis(TIMESTAMPTZ '2024-01-01 00:00:00+00')"),
      95            1 :       kMs2024_01_01);
      96            1 : }
      97              : 
      98            1 : TEST_F(DatetimeMacrosTest, UnixMillisSubsecond) {
      99              :   // Fractional milliseconds. 2024-01-01 00:00:00.5 -> +500 ms.
     100            1 :   EXPECT_EQ(
     101            1 :       RunInt64("SELECT bq_unix_millis(TIMESTAMPTZ '2024-01-01 00:00:00.5+00')"),
     102            1 :       kMs2024_01_01 + 500);
     103            1 : }
     104              : 
     105            1 : TEST_F(DatetimeMacrosTest, UnixMillisNullPropagation) {
     106            1 :   EXPECT_TRUE(RunBool("SELECT bq_unix_millis(NULL::TIMESTAMPTZ) IS NULL"));
     107            1 : }
     108              : 
     109              : // --- bq_unix_micros ----------------------------------------------
     110              : 
     111            1 : TEST_F(DatetimeMacrosTest, UnixMicrosWholeSecond) {
     112            1 :   EXPECT_EQ(
     113            1 :       RunInt64("SELECT bq_unix_micros(TIMESTAMPTZ '2024-01-01 00:00:00+00')"),
     114            1 :       kUs2024_01_01);
     115            1 : }
     116              : 
     117            1 : TEST_F(DatetimeMacrosTest, UnixMicrosSubsecond) {
     118              :   // 2024-01-01 00:00:00.123456 -> +123456 microseconds.
     119            1 :   EXPECT_EQ(RunInt64("SELECT bq_unix_micros(TIMESTAMPTZ "
     120            1 :                      "'2024-01-01 00:00:00.123456+00')"),
     121            1 :             kUs2024_01_01 + 123456);
     122            1 : }
     123              : 
     124            1 : TEST_F(DatetimeMacrosTest, UnixMicrosNullPropagation) {
     125            1 :   EXPECT_TRUE(RunBool("SELECT bq_unix_micros(NULL::TIMESTAMPTZ) IS NULL"));
     126            1 : }
     127              : 
     128              : // --- bq_unix_date ------------------------------------------------
     129              : 
     130            1 : TEST_F(DatetimeMacrosTest, UnixDateEpochIsZero) {
     131            1 :   EXPECT_EQ(RunInt64("SELECT bq_unix_date(DATE '1970-01-01')"), 0);
     132            1 : }
     133              : 
     134            1 : TEST_F(DatetimeMacrosTest, UnixDatePreEpochIsNegative) {
     135              :   // Edge case pinned: pre-1970 dates return negative day counts.
     136              :   // BigQuery documents this explicitly. A regression that wrapped
     137              :   // the result in `ABS(...)` or `GREATEST(..., 0)` would surface
     138              :   // here.
     139            1 :   EXPECT_EQ(RunInt64("SELECT bq_unix_date(DATE '1969-12-31')"), -1);
     140            1 :   EXPECT_EQ(RunInt64("SELECT bq_unix_date(DATE '1969-12-01')"), -31);
     141            1 : }
     142              : 
     143            1 : TEST_F(DatetimeMacrosTest, UnixDateFutureDate) {
     144              :   // 2024-01-01 is 19723 days after 1970-01-01.
     145              :   // (54 years + 13 leap days = 19723 days.)
     146            1 :   EXPECT_EQ(RunInt64("SELECT bq_unix_date(DATE '2024-01-01')"), 19723);
     147            1 : }
     148              : 
     149            1 : TEST_F(DatetimeMacrosTest, UnixDateNullPropagation) {
     150            1 :   EXPECT_TRUE(RunBool("SELECT bq_unix_date(NULL::DATE) IS NULL"));
     151            1 : }
     152              : 
     153            1 : TEST_F(DatetimeMacrosTest, DateAddDay) {
     154            1 :   EXPECT_TRUE(RunBool(
     155            1 :       "SELECT bq_date_add(DATE '2020-09-22', 1, 3) = DATE '2020-09-23'"));
     156            1 : }
     157              : 
     158            1 : TEST_F(DatetimeMacrosTest, DateAddMonthEndSnap) {
     159            1 :   EXPECT_TRUE(RunBool(
     160            1 :       "SELECT bq_date_add(DATE '2024-01-31', 1, 2) = DATE '2024-02-29'"));
     161            1 : }
     162              : 
     163            1 : TEST_F(DatetimeMacrosTest, ExtractYear) {
     164            1 :   EXPECT_EQ(RunInt64("SELECT bq_extract(1, DATE '2020-06-15')"), 2020);
     165            1 : }
     166              : 
     167              : }  // namespace
     168              : }  // namespace udf
     169              : }  // namespace duckdb
     170              : }  // namespace engine
     171              : }  // namespace backend
     172              : }  // namespace bigquery_emulator
        

Generated by: LCOV version 2.0-1