LCOV - code coverage report
Current view: top level - engine/semantic - executor_join_test.cc (source / functions) Coverage Total Hit
Test: _coverage_report.dat Lines: 100.0 % 115 115
Test Date: 2026-08-08 08:38:15 Functions: 100.0 % 7 7

            Line data    Source code
       1              : // Join materialization tests for `SemanticExecutor` (R17 hash
       2              : // equi-join). Every test asserts end results only: eligible shapes
       3              : // take `MaterializeHashEquiJoinRows`, the rest keep the nested loop,
       4              : // and both must produce identical rows. Fixture shared via
       5              : // `executor_test_fixture.h`.
       6              : 
       7              : #include <cstdint>
       8              : #include <string>
       9              : #include <utility>
      10              : #include <vector>
      11              : 
      12              : #include "backend/engine/semantic/executor.h"
      13              : #include "backend/engine/semantic/executor_test_fixture.h"
      14              : #include "backend/storage/storage.h"
      15              : #include "gtest/gtest.h"
      16              : 
      17              : namespace bigquery_emulator {
      18              : namespace backend {
      19              : namespace engine {
      20              : namespace semantic {
      21              : namespace {
      22              : 
      23              : class SemanticExecutorJoinTest : public SemanticExecutorTest {
      24              :  protected:
      25            6 :   std::vector<storage::Row> Run(const std::string& sql) {
      26            6 :     const auto* stmt = Analyze(sql, MakeAnalyzerOptions());
      27            6 :     EXPECT_NE(stmt, nullptr);
      28            6 :     if (stmt == nullptr) return {};
      29            6 :     SemanticExecutor exec;
      30            6 :     auto source = exec.ExecuteQuery(MakeRequest(sql), *stmt, catalog_.get());
      31           12 :     EXPECT_TRUE(source.ok()) << source.status();
      32            6 :     std::vector<storage::Row> rows;
      33            6 :     if (!source.ok()) return rows;
      34           23 :     while (true) {
      35           23 :       storage::Row row;
      36           23 :       auto has = (*source)->Next(&row);
      37           46 :       EXPECT_TRUE(has.ok()) << has.status();
      38           23 :       if (!has.ok() || !*has) break;
      39           17 :       rows.push_back(std::move(row));
      40           17 :     }
      41            6 :     return rows;
      42            6 :   }
      43              : };
      44              : 
      45              : // INNER equi-join over STRING keys with duplicates on both sides:
      46              : // hash-path eligible; every key pair must appear with full
      47              : // multiplicity (2 x 2 = 4 rows for the duplicated key).
      48            1 : TEST_F(SemanticExecutorJoinTest, InnerEquiJoinDuplicateKeysKeepMultiplicity) {
      49            1 :   const std::string sql =
      50            1 :       "SELECT l.k, l.lv, r.rv FROM ("
      51            1 :       "  SELECT 'a' AS k, 1 AS lv UNION ALL SELECT 'a', 2 UNION ALL"
      52            1 :       "  SELECT 'b', 3 UNION ALL SELECT 'z', 9"
      53            1 :       ") l JOIN ("
      54            1 :       "  SELECT 'a' AS k, 10 AS rv UNION ALL SELECT 'a', 20 UNION ALL"
      55            1 :       "  SELECT 'b', 30"
      56            1 :       ") r ON l.k = r.k "
      57            1 :       "ORDER BY l.k, l.lv, r.rv";
      58            1 :   std::vector<storage::Row> rows = Run(sql);
      59            1 :   ASSERT_EQ(rows.size(), 5u);  // 'a': 2x2 = 4, 'b': 1, 'z': unmatched.
      60            1 :   const std::vector<std::vector<int64_t>> want = {
      61            1 :       {1, 10}, {1, 20}, {2, 10}, {2, 20}, {3, 30}};
      62            6 :   for (size_t i = 0; i < want.size(); ++i) {
      63           10 :     EXPECT_EQ(rows[i].cells[1].int64_value(), want[i][0]) << "row " << i;
      64           10 :     EXPECT_EQ(rows[i].cells[2].int64_value(), want[i][1]) << "row " << i;
      65            5 :   }
      66            1 : }
      67              : 
      68              : // LEFT equi-join: unmatched left rows null-extend, and NULL join
      69              : // keys never match (SQL equality), including NULL-vs-NULL.
      70            1 : TEST_F(SemanticExecutorJoinTest, LeftEquiJoinNullExtendsAndNullKeysMiss) {
      71            1 :   const std::string sql =
      72            1 :       "SELECT l.id, r.rv FROM ("
      73            1 :       "  SELECT 1 AS id, 'a' AS k UNION ALL"
      74            1 :       "  SELECT 2, CAST(NULL AS STRING) UNION ALL"
      75            1 :       "  SELECT 3, 'missing'"
      76            1 :       ") l LEFT JOIN ("
      77            1 :       "  SELECT 'a' AS k, 10 AS rv UNION ALL"
      78            1 :       "  SELECT CAST(NULL AS STRING), 99"
      79            1 :       ") r ON l.k = r.k "
      80            1 :       "ORDER BY l.id";
      81            1 :   std::vector<storage::Row> rows = Run(sql);
      82            1 :   ASSERT_EQ(rows.size(), 3u);
      83            1 :   EXPECT_EQ(rows[0].cells[1].int64_value(), 10);
      84              :   // Row 2 has a NULL key: must NOT match the NULL-keyed right row.
      85            1 :   EXPECT_TRUE(rows[1].cells[1].is_null());
      86            1 :   EXPECT_TRUE(rows[2].cells[1].is_null());
      87            1 : }
      88              : 
      89              : // Equality keys plus a non-equality residual conjunct: the residual
      90              : // must filter candidate pairs after the hash probe, and a left row
      91              : // whose candidates all fail the residual must still null-extend.
      92            1 : TEST_F(SemanticExecutorJoinTest, LeftEquiJoinResidualFiltersCandidates) {
      93            1 :   const std::string sql =
      94            1 :       "SELECT l.id, r.rv FROM ("
      95            1 :       "  SELECT 1 AS id, 'a' AS k, 5 AS lo UNION ALL"
      96            1 :       "  SELECT 2, 'a', 50"
      97            1 :       ") l LEFT JOIN ("
      98            1 :       "  SELECT 'a' AS k, 10 AS rv UNION ALL SELECT 'a', 60"
      99            1 :       ") r ON l.k = r.k AND r.rv > l.lo "
     100            1 :       "ORDER BY l.id, r.rv";
     101            1 :   std::vector<storage::Row> rows = Run(sql);
     102            1 :   ASSERT_EQ(rows.size(), 3u);
     103              :   // id=1 (lo=5): both rv=10 and rv=60 pass. id=2 (lo=50): only rv=60.
     104            1 :   EXPECT_EQ(rows[0].cells[1].int64_value(), 10);
     105            1 :   EXPECT_EQ(rows[1].cells[1].int64_value(), 60);
     106            1 :   EXPECT_EQ(rows[2].cells[1].int64_value(), 60);
     107            1 : }
     108              : 
     109              : // Composite (two-column) hash key: both equalities must hold.
     110            1 : TEST_F(SemanticExecutorJoinTest, InnerEquiJoinCompositeKey) {
     111            1 :   const std::string sql =
     112            1 :       "SELECT l.v, r.w FROM ("
     113            1 :       "  SELECT 't1' AS tenant, 1 AS id, 100 AS v UNION ALL"
     114            1 :       "  SELECT 't1', 2, 200 UNION ALL"
     115            1 :       "  SELECT 't2', 1, 300"
     116            1 :       ") l JOIN ("
     117            1 :       "  SELECT 't1' AS tenant, 1 AS id, 111 AS w UNION ALL"
     118            1 :       "  SELECT 't2', 1, 333"
     119            1 :       ") r ON l.tenant = r.tenant AND l.id = r.id "
     120            1 :       "ORDER BY l.v";
     121            1 :   std::vector<storage::Row> rows = Run(sql);
     122            1 :   ASSERT_EQ(rows.size(), 2u);
     123            1 :   EXPECT_EQ(rows[0].cells[0].int64_value(), 100);
     124            1 :   EXPECT_EQ(rows[0].cells[1].int64_value(), 111);
     125            1 :   EXPECT_EQ(rows[1].cells[0].int64_value(), 300);
     126            1 :   EXPECT_EQ(rows[1].cells[1].int64_value(), 333);
     127            1 : }
     128              : 
     129              : // Non-equality join (no hashable equi conjunct) keeps the nested
     130              : // loop and still produces correct LEFT OUTER rows.
     131            1 : TEST_F(SemanticExecutorJoinTest, NonEquiLeftJoinUsesNestedLoop) {
     132            1 :   const std::string sql =
     133            1 :       "SELECT l.id, r.rv FROM ("
     134            1 :       "  SELECT 1 AS id, 5 AS k UNION ALL"
     135            1 :       "  SELECT 2, 50"
     136            1 :       ") l LEFT JOIN ("
     137            1 :       "  SELECT 10 AS rv UNION ALL SELECT 60"
     138            1 :       ") r ON r.rv > l.k "
     139            1 :       "ORDER BY l.id, r.rv";
     140            1 :   std::vector<storage::Row> rows = Run(sql);
     141            1 :   ASSERT_EQ(rows.size(), 3u);
     142              :   // id=1 (k=5): both 10 and 60. id=2 (k=50): only 60.
     143            1 :   EXPECT_EQ(rows[0].cells[1].int64_value(), 10);
     144            1 :   EXPECT_EQ(rows[1].cells[1].int64_value(), 60);
     145            1 :   EXPECT_EQ(rows[2].cells[1].int64_value(), 60);
     146            1 : }
     147              : 
     148              : // Swapped-side equality (`r.k = l.k`) must still hash-plan; result
     149              : // parity with the canonical order.
     150            1 : TEST_F(SemanticExecutorJoinTest, InnerEquiJoinSwappedEqualitySides) {
     151            1 :   const std::string sql =
     152            1 :       "SELECT l.lv, r.rv FROM ("
     153            1 :       "  SELECT 1 AS k, 100 AS lv UNION ALL SELECT 2, 200"
     154            1 :       ") l JOIN ("
     155            1 :       "  SELECT 1 AS k, 11 AS rv UNION ALL SELECT 3, 33"
     156            1 :       ") r ON r.k = l.k "
     157            1 :       "ORDER BY l.lv";
     158            1 :   std::vector<storage::Row> rows = Run(sql);
     159            1 :   ASSERT_EQ(rows.size(), 1u);
     160            1 :   EXPECT_EQ(rows[0].cells[0].int64_value(), 100);
     161            1 :   EXPECT_EQ(rows[0].cells[1].int64_value(), 11);
     162            1 : }
     163              : 
     164              : }  // namespace
     165              : }  // namespace semantic
     166              : }  // namespace engine
     167              : }  // namespace backend
     168              : }  // namespace bigquery_emulator
        

Generated by: LCOV version 2.0-1