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

            Line data    Source code
       1              : #include "backend/engine/duckdb/transpiler/transpiler_test_fixture.h"
       2              : #include "googlesql/public/types/array_type.h"
       3              : 
       4              : // R9: Anti-join over QUALIFY-deduped views — DuckDB binder "column id not
       5              : // found". Indexed in conformance/REGRESSIONS.md. Part of the transpiler
       6              : // binding property-test suite: generated query compositions must always
       7              : // transpile to SQL that binds in DuckDB.
       8              : 
       9              : namespace bigquery_emulator {
      10              : namespace backend {
      11              : namespace engine {
      12              : namespace duckdb {
      13              : namespace transpiler {
      14              : namespace {
      15              : 
      16              : constexpr int kCompositionGeneratorSeed = 0x06060606;
      17              : constexpr int kCompositionGeneratorCases = 24;
      18              : constexpr int kDistinctAfterDedupGeneratorCases = 12;
      19              : 
      20              : std::string WrapQualifyDedupSubquery(absl::string_view partition_col,
      21           26 :                                      absl::string_view inner_sql) {
      22           26 :   return absl::StrCat(
      23           26 :       "SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY ",
      24           26 :       partition_col,
      25           26 :       " ORDER BY ",
      26           26 :       partition_col,
      27           26 :       " DESC) AS rn FROM (",
      28           26 :       inner_sql,
      29           26 :       ")) WHERE rn = 1");
      30           26 : }
      31              : 
      32           84 : uint32_t LcgNext(uint32_t* state) {
      33           84 :   *state = *state * 1664525u + 1013904223u;
      34           84 :   return *state;
      35           84 : }
      36              : 
      37              : }  // namespace
      38              : 
      39              : class TranspilerCompositionTest : public TranspilerBindFixture {
      40              :  protected:
      41           12 :   void SetUp() override {
      42           12 :     TranspilerBindFixture::SetUp();
      43              : 
      44           12 :     const ::googlesql::ArrayType* profile_tags_type = nullptr;
      45           12 :     ASSERT_TRUE(
      46           12 :         type_factory_
      47           12 :             ->MakeArrayType(type_factory_->get_string(), &profile_tags_type)
      48           12 :             .ok());
      49           12 :     auto profiles = std::make_unique<::googlesql::SimpleTable>(
      50           12 :         "profiles",
      51           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
      52           12 :             {"id", type_factory_->get_int64()},
      53           12 :             {"name", type_factory_->get_string()},
      54           12 :         });
      55           12 :     catalog_->AddOwnedTable(std::move(profiles));
      56              : 
      57           12 :     auto dedup_profiles = std::make_unique<::googlesql::SimpleTable>(
      58           12 :         "dedup_profiles",
      59           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
      60           12 :             {"id", type_factory_->get_int64()},
      61           12 :             {"city", type_factory_->get_string()},
      62           12 :             {"tags", profile_tags_type},
      63           12 :             {"source_updated_at", type_factory_->get_timestamp()},
      64           12 :         });
      65           12 :     catalog_->AddOwnedTable(std::move(dedup_profiles));
      66              : 
      67           12 :     auto bq_orders = std::make_unique<::googlesql::SimpleTable>(
      68           12 :         "bq_orders",
      69           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
      70           12 :             {"order_id", type_factory_->get_int64()},
      71           12 :             {"customer_id", type_factory_->get_int64()},
      72           12 :         });
      73           12 :     catalog_->AddOwnedTable(std::move(bq_orders));
      74              : 
      75           12 :     const ::googlesql::ArrayType* vals_type = nullptr;
      76           12 :     ASSERT_TRUE(
      77           12 :         type_factory_->MakeArrayType(type_factory_->get_int64(), &vals_type)
      78           12 :             .ok());
      79           12 :     auto items = std::make_unique<::googlesql::SimpleTable>(
      80           12 :         "items",
      81           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
      82           12 :             {"id", type_factory_->get_int64()},
      83           12 :             {"vals", vals_type},
      84           12 :         });
      85           12 :     catalog_->AddOwnedTable(std::move(items));
      86              : 
      87           12 :     const ::googlesql::ArrayType* tags_type = nullptr;
      88           12 :     ASSERT_TRUE(
      89           12 :         type_factory_->MakeArrayType(type_factory_->get_string(), &tags_type)
      90           12 :             .ok());
      91           12 :     auto arrays = std::make_unique<::googlesql::SimpleTable>(
      92           12 :         "arrays",
      93           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
      94           12 :             {"tags", tags_type},
      95           12 :         });
      96           12 :     catalog_->AddOwnedTable(std::move(arrays));
      97              : 
      98           12 :     const ::googlesql::ArrayType* collection_ids_type = nullptr;
      99           12 :     ASSERT_TRUE(
     100           12 :         type_factory_
     101           12 :             ->MakeArrayType(type_factory_->get_int64(), &collection_ids_type)
     102           12 :             .ok());
     103           12 :     auto products = std::make_unique<::googlesql::SimpleTable>(
     104           12 :         "products",
     105           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
     106           12 :             {"id", type_factory_->get_int64()},
     107           12 :             {"title", type_factory_->get_string()},
     108           12 :             {"is_published", type_factory_->get_bool()},
     109           12 :             {"collection_ids", collection_ids_type},
     110           12 :         });
     111           12 :     catalog_->AddOwnedTable(std::move(products));
     112              : 
     113           12 :     auto collections = std::make_unique<::googlesql::SimpleTable>(
     114           12 :         "collections",
     115           12 :         std::vector<::googlesql::SimpleTable::NameAndType>{
     116           12 :             {"id", type_factory_->get_int64()},
     117           12 :             {"title", type_factory_->get_string()},
     118           12 :             {"is_published", type_factory_->get_bool()},
     119           12 :             {"products_count", type_factory_->get_int64()},
     120           12 :         });
     121           12 :     catalog_->AddOwnedTable(std::move(collections));
     122              : 
     123           12 :     ExecDdl("CREATE TABLE bq_orders (order_id BIGINT, customer_id BIGINT)");
     124           12 :     ExecDdl("CREATE TABLE profiles (id BIGINT, name VARCHAR)");
     125           12 :     ExecDdl("CREATE TABLE people (id BIGINT, name VARCHAR)");
     126           12 :     ExecDdl("CREATE TABLE items (id BIGINT, vals BIGINT[])");
     127           12 :     ExecDdl("CREATE TABLE arrays (tags STRING[])");
     128           12 :     ExecDdl(
     129           12 :         "CREATE TABLE dedup_profiles (id BIGINT, city VARCHAR, tags STRING[], "
     130           12 :         "source_updated_at TIMESTAMPTZ)");
     131           12 :     ExecDdl(
     132           12 :         "CREATE TABLE products (id BIGINT, title VARCHAR, is_published "
     133           12 :         "BOOLEAN, collection_ids BIGINT[])");
     134           12 :     ExecDdl(
     135           12 :         "CREATE TABLE collections (id BIGINT, title VARCHAR, is_published "
     136           12 :         "BOOLEAN, products_count BIGINT)");
     137           12 :   }
     138              : };
     139              : 
     140            1 : TEST_F(TranspilerCompositionTest, DistinctCityAfterQualifyDedupBinds) {
     141            1 :   static constexpr const char kSql[] = R"sql(
     142            1 : SELECT DISTINCT city
     143            1 : FROM (
     144            1 :   SELECT * FROM dedup_profiles
     145            1 :   QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY source_updated_at DESC) = 1
     146            1 : )
     147            1 : WHERE city IS NOT NULL
     148            1 : )sql";
     149            1 :   AssertSqlTranspileBinds(kSql);
     150            1 : }
     151              : 
     152            1 : TEST_F(TranspilerCompositionTest, DistinctUnnestAfterQualifyDedupBinds) {
     153            1 :   static constexpr const char kSql[] = R"sql(
     154            1 : SELECT DISTINCT tag
     155            1 : FROM (
     156            1 :   SELECT * FROM dedup_profiles
     157            1 :   QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY source_updated_at DESC) = 1
     158            1 : ), UNNEST(tags) AS tag
     159            1 : )sql";
     160            1 :   AssertSqlTranspileBinds(kSql);
     161            1 : }
     162              : 
     163            1 : TEST_F(TranspilerCompositionTest, CorrelatedUnnestFromTableBinds) {
     164            1 :   static constexpr const char kSql[] = R"sql(
     165            1 : SELECT id, n
     166            1 : FROM items, UNNEST(items.vals) AS n
     167            1 : ORDER BY id, n
     168            1 : )sql";
     169            1 :   AssertSqlTranspileBinds(kSql);
     170            1 : }
     171              : 
     172              : // R13: UNNEST + GROUP BY inside a CTE, then outer LEFT JOIN on the
     173              : // unnested value — CTE alias/rn state must not leak into the outer join.
     174            1 : TEST_F(TranspilerCompositionTest, UnnestGroupByInCteThenJoinBinds) {
     175            1 :   static constexpr const char kSql[] = R"sql(
     176            1 : WITH product_counts AS (
     177            1 :   SELECT col_id, COUNT(DISTINCT p.id) AS calculated_product_count
     178            1 :   FROM products p, UNNEST(p.collection_ids) AS col_id
     179            1 :   GROUP BY col_id
     180            1 : )
     181            1 : SELECT
     182            1 :   c.id AS collection_id,
     183            1 :   c.title AS collection_title,
     184            1 :   c.products_count AS collection_table_count,
     185            1 :   COALESCE(pc.calculated_product_count, 0) AS calculated_product_count,
     186            1 :   (c.products_count - COALESCE(pc.calculated_product_count, 0)) AS discrepancy
     187            1 : FROM collections c
     188            1 : LEFT JOIN product_counts pc ON c.id = pc.col_id
     189            1 : WHERE COALESCE(c.products_count, 0) != COALESCE(pc.calculated_product_count, 0)
     190            1 : )sql";
     191            1 :   AssertSqlTranspileBinds(kSql);
     192            1 : }
     193              : 
     194              : // R13: correlated UNNEST in CTE (no GROUP BY) + outer JOIN — anchors and
     195              : // join sides must not reference stale `__bq_j_<id>` aliases.
     196            1 : TEST_F(TranspilerCompositionTest, UnnestInCteThenJoinBinds) {
     197            1 :   static constexpr const char kSql[] = R"sql(
     198            1 : WITH product_collections AS (
     199            1 :   SELECT p.id AS product_id, col_id
     200            1 :   FROM products p, UNNEST(p.collection_ids) AS col_id
     201            1 : )
     202            1 : SELECT c.id AS collection_id, c.title AS collection_title, pc.product_id
     203            1 : FROM collections c
     204            1 : LEFT JOIN product_collections pc ON c.id = pc.col_id
     205            1 : )sql";
     206            1 :   AssertSqlTranspileBinds(kSql);
     207            1 : }
     208              : 
     209            1 : TEST_F(TranspilerCompositionTest, CoreUsageUnnestArrayShapeBinds) {
     210            1 :   static constexpr const char kSql[] = R"sql(
     211            1 : SELECT tag FROM arrays, UNNEST(tags) AS tag
     212            1 : )sql";
     213            1 :   AssertSqlTranspileBinds(kSql);
     214            1 : }
     215              : 
     216            1 : TEST_F(TranspilerCompositionTest, NestedUnnestCrossProductBinds) {
     217            1 :   static constexpr const char kSql[] = R"sql(
     218            1 : SELECT n, m
     219            1 : FROM UNNEST(GENERATE_ARRAY(1, 2)) AS n
     220            1 : CROSS JOIN UNNEST(GENERATE_ARRAY(10, 11)) AS m
     221            1 : )sql";
     222            1 :   AssertSqlTranspileBinds(kSql);
     223            1 : }
     224              : 
     225            1 : TEST_F(TranspilerCompositionTest, OrphanOrdersQualifyDedupAntiJoinBinds) {
     226            1 :   static constexpr const char kSql[] = R"sql(
     227            1 : SELECT o.order_id
     228            1 : FROM (
     229            1 :   SELECT * FROM bq_orders
     230            1 :   QUALIFY ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY order_id) = 1
     231            1 : ) o
     232            1 : LEFT JOIN (
     233            1 :   SELECT * FROM profiles
     234            1 :   QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) = 1
     235            1 : ) p ON o.customer_id = p.id
     236            1 : WHERE p.id IS NULL
     237            1 : ORDER BY o.order_id
     238            1 : )sql";
     239            1 :   AssertSqlTranspileBinds(kSql);
     240            1 : }
     241              : 
     242            1 : TEST_F(TranspilerCompositionTest, OrphanOrdersSubqueryDedupAntiJoinBinds) {
     243            1 :   static constexpr const char kSql[] = R"sql(
     244            1 : SELECT o.order_id, o.customer_id
     245            1 : FROM (
     246            1 :   SELECT order_id, customer_id FROM (
     247            1 :     SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY order_id) AS rn
     248            1 :     FROM (
     249            1 :       SELECT 1 AS order_id, 10 AS customer_id UNION ALL
     250            1 :       SELECT 2 AS order_id, 99 AS customer_id
     251            1 :     )
     252            1 :   ) WHERE rn = 1
     253            1 : ) o
     254            1 : LEFT JOIN (
     255            1 :   SELECT id FROM (
     256            1 :     SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS rn
     257            1 :     FROM (SELECT 10 AS id)
     258            1 :   ) WHERE rn = 1
     259            1 : ) p ON o.customer_id = p.id
     260            1 : WHERE o.customer_id IS NOT NULL AND p.id IS NULL
     261            1 : )sql";
     262            1 :   AssertSqlTranspileBinds(kSql);
     263            1 : }
     264              : 
     265            1 : TEST_F(TranspilerCompositionTest, NestedQualifyJoinCteExceptBinds) {
     266            1 :   static constexpr const char kSql[] = R"sql(
     267            1 : WITH dedup AS (
     268            1 :   SELECT id, name FROM (
     269            1 :     SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS rn
     270            1 :     FROM people
     271            1 :   ) WHERE rn = 1
     272            1 : )
     273            1 : SELECT a.id FROM dedup a
     274            1 : LEFT JOIN dedup b ON a.id = b.id
     275            1 : WHERE b.id IS NULL
     276            1 : EXCEPT DISTINCT
     277            1 : SELECT CAST(0 AS INT64) AS id
     278            1 : )sql";
     279            1 :   AssertSqlTranspileBinds(kSql);
     280            1 : }
     281              : 
     282            1 : TEST_F(TranspilerCompositionTest, SeededCompositionGeneratorBinds) {
     283            1 :   uint32_t rng = kCompositionGeneratorSeed;
     284           25 :   for (int i = 0; i < kCompositionGeneratorCases; ++i) {
     285           24 :     const int base = static_cast<int>(LcgNext(&rng) % 2);
     286           24 :     const int wrap1 = static_cast<int>(LcgNext(&rng) % 3);
     287           24 :     const int wrap2 = static_cast<int>(LcgNext(&rng) % 3);
     288              : 
     289           24 :     const char* key_col = base == 0 ? "id" : "order_id";
     290           24 :     std::string inner = base == 0
     291           24 :                             ? "SELECT id, name FROM people"
     292           24 :                             : "SELECT order_id, customer_id FROM bq_orders";
     293              : 
     294           24 :     if (wrap1 == 1) {
     295            5 :       inner = WrapQualifyDedupSubquery(key_col, inner);
     296           19 :     } else if (wrap1 == 2) {
     297            9 :       inner = absl::StrCat("SELECT * EXCEPT(rn) FROM (",
     298            9 :                            WrapQualifyDedupSubquery(key_col, inner),
     299            9 :                            ")");
     300            9 :     }
     301              : 
     302           24 :     std::string sql;
     303           24 :     if (wrap2 == 0) {
     304            6 :       sql = absl::StrCat(
     305            6 :           "SELECT ", key_col, " FROM (", inner, ") t WHERE ", key_col, " >= 0");
     306           18 :     } else if (wrap2 == 1) {
     307           10 :       sql = absl::StrCat("WITH w AS (", inner, ") SELECT COUNT(*) AS c FROM w");
     308           10 :     } else {
     309            8 :       sql = absl::StrCat("SELECT a.",
     310            8 :                          key_col,
     311            8 :                          " FROM (",
     312            8 :                          inner,
     313            8 :                          ") a LEFT JOIN (",
     314            8 :                          inner,
     315            8 :                          ") b ON a.",
     316            8 :                          key_col,
     317            8 :                          " = b.",
     318            8 :                          key_col,
     319            8 :                          " WHERE b.",
     320            8 :                          key_col,
     321            8 :                          " IS NULL");
     322            8 :     }
     323              : 
     324           24 :     SCOPED_TRACE(absl::StrCat("case=", i, " sql=", sql));
     325           24 :     AssertSqlTranspileBinds(sql);
     326           24 :   }
     327            1 : }
     328              : 
     329            1 : TEST_F(TranspilerCompositionTest, SeededDistinctAfterDedupGeneratorBinds) {
     330            1 :   uint32_t rng = kCompositionGeneratorSeed ^ 0xD157111Cu;
     331           13 :   for (int i = 0; i < kDistinctAfterDedupGeneratorCases; ++i) {
     332           12 :     const int wrap3 = static_cast<int>(LcgNext(&rng) % 3);
     333           12 :     const std::string deduped = WrapQualifyDedupSubquery(
     334           12 :         "id", "SELECT id, city, tags FROM dedup_profiles");
     335              : 
     336           12 :     std::string sql;
     337           12 :     if (wrap3 == 0) {
     338            3 :       sql = absl::StrCat(
     339            3 :           "SELECT DISTINCT city FROM (", deduped, ") WHERE city IS NOT NULL");
     340            9 :     } else if (wrap3 == 1) {
     341            4 :       sql = absl::StrCat(
     342            4 :           "SELECT DISTINCT tag FROM (", deduped, "), UNNEST(tags) AS tag");
     343            5 :     } else {
     344            5 :       sql = absl::StrCat(
     345            5 :           "SELECT city, COUNT(*) AS c FROM (", deduped, ") GROUP BY city");
     346            5 :     }
     347              : 
     348           12 :     SCOPED_TRACE(absl::StrCat("distinct_case=", i, " sql=", sql));
     349           12 :     AssertSqlTranspileBinds(sql);
     350           12 :   }
     351            1 : }
     352              : 
     353              : }  // namespace transpiler
     354              : }  // namespace duckdb
     355              : }  // namespace engine
     356              : }  // namespace backend
     357              : }  // namespace bigquery_emulator
        

Generated by: LCOV version 2.0-1