From 34a8b199997bd0b744f5ef3aaeb681f3d58585f6 Mon Sep 17 00:00:00 2001 From: Timur Safin Date: Sun, 18 Jan 2026 18:00:29 +0300 Subject: [PATCH 1/3] refactor(dbgen): generic BatchIteratorImpl; move always_false; use template aliases for simple table iterators --- .gitmodules | 3 + include/tpch/dbgen_wrapper.hpp | 297 +++++++++++++------- src/dbgen/dbgen_wrapper.cpp | 482 ++------------------------------- third_party/arrow | 1 + 4 files changed, 223 insertions(+), 560 deletions(-) create mode 160000 third_party/arrow diff --git a/.gitmodules b/.gitmodules index 4336558..8d6cdea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "third_party/xsimd"] path = third_party/xsimd url = https://github.com/xtensor-stack/xsimd.git +[submodule "third_party/arrow"] + path = third_party/arrow + url = https://github.com/apache/arrow.git diff --git a/include/tpch/dbgen_wrapper.hpp b/include/tpch/dbgen_wrapper.hpp index d420763..8fa3ded 100644 --- a/include/tpch/dbgen_wrapper.hpp +++ b/include/tpch/dbgen_wrapper.hpp @@ -29,6 +29,66 @@ enum class TableType { COUNT_ }; +// Forward declaration for RAII session helper +class DBGenWrapper; + +/** + * RAII helper for DBGen generation sessions. + * Keeps basic session state together; concrete behavior is implemented + * in the .cpp file and this type is intentionally lightweight here + * so it can be introduced without touching existing generation logic. + */ +class DBGenSession { + public: + DBGenSession(DBGenWrapper* wrapper, int table_id, long start_row, long stop_row); + ~DBGenSession(); + + // Non-copyable + DBGenSession(const DBGenSession&) = delete; + DBGenSession& operator=(const DBGenSession&) = delete; + + private: + DBGenWrapper* wrapper_; + int table_id_; + long start_row_; + long stop_row_; +}; + +// Lightweight per-table traits used for future generic generators. +// They only expose the concrete row type and table identifier. +struct OrdersTraits { + using Row = order_t; + static constexpr TableType table = TableType::ORDERS; +}; +struct LineitemTraits { + using Row = line_t; + static constexpr TableType table = TableType::LINEITEM; +}; +struct CustomerTraits { + using Row = customer_t; + static constexpr TableType table = TableType::CUSTOMER; +}; +struct PartTraits { + using Row = part_t; + static constexpr TableType table = TableType::PART; +}; +struct PartsuppTraits { + using Row = partsupp_t; + static constexpr TableType table = TableType::PARTSUPP; +}; +struct SupplierTraits { + using Row = supplier_t; + static constexpr TableType table = TableType::SUPPLIER; +}; +struct NationTraits { + using Row = code_t; + static constexpr TableType table = TableType::NATION; +}; +struct RegionTraits { + using Row = code_t; + static constexpr TableType table = TableType::REGION; +}; + /** * Get table name for display/debugging */ @@ -189,6 +249,15 @@ class DBGenWrapper { void generate_region( std::function callback); + /** + * Generic generator dispatcher that forwards to the table-specific + * generator implementation. This member template allows incremental + * migration of the per-table generate_* methods to a single generic + * entrypoint without changing external APIs. + */ + template + void generate_generic(std::function callback, long max_rows = -1); + /** * Get scale factor */ @@ -211,6 +280,106 @@ class DBGenWrapper { // Phase 13.4: Batch generation interfaces for zero-copy optimization // ======================================================================= + // Helper used in static_assert for unreachable branches + template + struct always_false : std::false_type {}; + + // Generic batch iterator implementation used by simple tables. + // Defined as a nested template so it can access DBGenWrapper's + // initialization helpers and private members. + template + class BatchIteratorImpl { + public: + using Row = typename Traits::Row; + using Batch = DBGenBatch; + + BatchIteratorImpl(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows) + : wrapper_(wrapper), batch_size_(batch_size) { + // Determine total remaining rows + size_t total = static_cast(get_row_count(Traits::table, wrapper_->scale_factor_)); + remaining_ = (max_rows == 0) ? total : std::min(static_cast(max_rows), total); + current_row_ = 1; + + if (!wrapper_->initialized_) { + wrapper_->init_dbgen(); + } + + dbgen_reset_seeds(); + if constexpr (Traits::table == TableType::ORDERS) { + row_start(DBGEN_ORDER); + } else if constexpr (Traits::table == TableType::CUSTOMER) { + row_start(DBGEN_CUST); + } else if constexpr (Traits::table == TableType::PART) { + row_start(DBGEN_PART); + } else if constexpr (Traits::table == TableType::SUPPLIER) { + row_start(DBGEN_SUPP); + } else if constexpr (Traits::table == TableType::NATION) { + row_start(DBGEN_NATION); + } else if constexpr (Traits::table == TableType::REGION) { + row_start(DBGEN_REGION); + } + } + + bool has_next() const { return remaining_ > 0; } + + Batch next() { + Batch batch; + if (remaining_ == 0) return batch; + + batch.rows.reserve(std::min(batch_size_, remaining_)); + + size_t total_rows = static_cast(get_row_count(Traits::table, wrapper_->scale_factor_)); + + while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= total_rows) { + Row r{}; + if constexpr (Traits::table == TableType::ORDERS) { + if (mk_order(static_cast(current_row_), &r, 0) < 0) break; + } else if constexpr (Traits::table == TableType::CUSTOMER) { + if (mk_cust(static_cast(current_row_), &r) < 0) break; + } else if constexpr (Traits::table == TableType::PART) { + if (mk_part(static_cast(current_row_), &r) < 0) break; + } else if constexpr (Traits::table == TableType::SUPPLIER) { + if (mk_supp(static_cast(current_row_), &r) < 0) break; + } else if constexpr (Traits::table == TableType::NATION) { + if (mk_nation(static_cast(current_row_), &r) < 0) break; + } else if constexpr (Traits::table == TableType::REGION) { + if (mk_region(static_cast(current_row_), &r) < 0) break; + } else { + static_assert(always_false::value, "Unsupported batch iterator table"); + } + + batch.rows.push_back(r); + remaining_--; + current_row_++; + } + + // Stop generation if complete + if (remaining_ == 0 || current_row_ > total_rows) { + if constexpr (Traits::table == TableType::ORDERS) { + row_stop(DBGEN_ORDER); + } else if constexpr (Traits::table == TableType::CUSTOMER) { + row_stop(DBGEN_CUST); + } else if constexpr (Traits::table == TableType::PART) { + row_stop(DBGEN_PART); + } else if constexpr (Traits::table == TableType::SUPPLIER) { + row_stop(DBGEN_SUPP); + } else if constexpr (Traits::table == TableType::NATION) { + row_stop(DBGEN_NATION); + } else if constexpr (Traits::table == TableType::REGION) { + row_stop(DBGEN_REGION); + } + } + + return batch; + } + + private: + DBGenWrapper* wrapper_; + size_t batch_size_; + size_t remaining_; + size_t current_row_; + }; + /** * Batch iterator for lineitem rows (zero-copy friendly) * @@ -255,69 +424,25 @@ class DBGenWrapper { /** * Batch iterator for orders rows (zero-copy friendly) */ - class OrdersBatchIterator { - public: - using Batch = DBGenBatch; - - OrdersBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using OrdersBatchIterator = BatchIteratorImpl; OrdersBatchIterator generate_orders_batches(size_t batch_size, size_t max_rows); /** * Batch iterator for customer rows (zero-copy friendly) */ - class CustomerBatchIterator { - public: - using Batch = DBGenBatch; - - CustomerBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using CustomerBatchIterator = BatchIteratorImpl; CustomerBatchIterator generate_customer_batches(size_t batch_size, size_t max_rows); /** * Batch iterator for part rows (zero-copy friendly) */ - class PartBatchIterator { - public: - using Batch = DBGenBatch; - - PartBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using PartBatchIterator = BatchIteratorImpl; PartBatchIterator generate_part_batches(size_t batch_size, size_t max_rows); /** * Batch iterator for partsupp rows (zero-copy friendly) */ + // Partsupp is nested per-part; keep concrete iterator for now class PartsuppBatchIterator { public: using Batch = DBGenBatch; @@ -339,64 +464,19 @@ class DBGenWrapper { /** * Batch iterator for supplier rows (zero-copy friendly) */ - class SupplierBatchIterator { - public: - using Batch = DBGenBatch; - - SupplierBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using SupplierBatchIterator = BatchIteratorImpl; SupplierBatchIterator generate_supplier_batches(size_t batch_size, size_t max_rows); /** * Batch iterator for nation rows (zero-copy friendly) */ - class NationBatchIterator { - public: - using Batch = DBGenBatch; - - NationBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using NationBatchIterator = BatchIteratorImpl; NationBatchIterator generate_nation_batches(size_t batch_size, size_t max_rows); /** * Batch iterator for region rows (zero-copy friendly) */ - class RegionBatchIterator { - public: - using Batch = DBGenBatch; - - RegionBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using RegionBatchIterator = BatchIteratorImpl; RegionBatchIterator generate_region_batches(size_t batch_size, size_t max_rows); private: @@ -436,6 +516,31 @@ class DBGenWrapper { * @param scale_factor TPC-H scale factor (1 = 1GB baseline) * @param verbose_flag Enable verbose debug output */ + + +template +void DBGenWrapper::generate_generic(std::function callback, long max_rows) { + if constexpr (Traits::table == TableType::ORDERS) { + generate_orders(callback, max_rows); + } else if constexpr (Traits::table == TableType::CUSTOMER) { + generate_customer(callback, max_rows); + } else if constexpr (Traits::table == TableType::PART) { + generate_part(callback, max_rows); + } else if constexpr (Traits::table == TableType::PARTSUPP) { + generate_partsupp(callback, max_rows); + } else if constexpr (Traits::table == TableType::SUPPLIER) { + generate_supplier(callback, max_rows); + } else if constexpr (Traits::table == TableType::NATION) { + generate_nation(callback); + } else if constexpr (Traits::table == TableType::REGION) { + generate_region(callback); + } else if constexpr (Traits::table == TableType::LINEITEM) { + generate_lineitem(callback, max_rows); + } else { + static_assert(always_false::value, "Unsupported table in generate_generic"); + } +} + void dbgen_init_global(long scale_factor, bool verbose_flag); /** diff --git a/src/dbgen/dbgen_wrapper.cpp b/src/dbgen/dbgen_wrapper.cpp index 5b183c2..2ee536e 100644 --- a/src/dbgen/dbgen_wrapper.cpp +++ b/src/dbgen/dbgen_wrapper.cpp @@ -172,103 +172,22 @@ void tpch::DBGenWrapper::generate_lineitem( void tpch::DBGenWrapper::generate_orders( std::function callback, long max_rows) { - - if (!initialized_) { - init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_ORDER); - - long rows_generated = 0; - long total_rows = get_row_count(TableType::ORDERS, scale_factor_); - - order_t order; - for (DSS_HUGE i = 1; i <= total_rows; ++i) { - if (mk_order(i, &order, 0) < 0) { - break; - } - - if (callback) { - callback(&order); - } - rows_generated++; - - if (max_rows > 0 && rows_generated >= max_rows) { - row_stop(DBGEN_ORDER); - return; - } - } - - row_stop(DBGEN_ORDER); + // Forward to the generic implementation for Orders + generate_generic(callback, max_rows); } void tpch::DBGenWrapper::generate_customer( std::function callback, long max_rows) { - - if (!initialized_) { - init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_CUST); - - long rows_generated = 0; - long total_rows = get_row_count(TableType::CUSTOMER, scale_factor_); - - customer_t customer; - for (DSS_HUGE i = 1; i <= total_rows; ++i) { - if (mk_cust(i, &customer) < 0) { - break; - } - - if (callback) { - callback(&customer); - } - rows_generated++; - - if (max_rows > 0 && rows_generated >= max_rows) { - row_stop(DBGEN_CUST); - return; - } - } - - row_stop(DBGEN_CUST); + // Forward to the generic implementation for Customer + generate_generic(callback, max_rows); } void tpch::DBGenWrapper::generate_part( std::function callback, long max_rows) { - - if (!initialized_) { - init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_PART); - - long rows_generated = 0; - long total_rows = get_row_count(TableType::PART, scale_factor_); - - part_t part; - for (DSS_HUGE i = 1; i <= total_rows; ++i) { - if (mk_part(i, &part) < 0) { - break; - } - - if (callback) { - callback(&part); - } - rows_generated++; - - if (max_rows > 0 && rows_generated >= max_rows) { - row_stop(DBGEN_PART); - return; - } - } - - row_stop(DBGEN_PART); + // Forward to the generic implementation for Part + generate_generic(callback, max_rows); } void tpch::DBGenWrapper::generate_partsupp( @@ -311,83 +230,20 @@ void tpch::DBGenWrapper::generate_partsupp( void tpch::DBGenWrapper::generate_supplier( std::function callback, long max_rows) { - - if (!initialized_) { - init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_SUPP); - - long rows_generated = 0; - long total_rows = get_row_count(TableType::SUPPLIER, scale_factor_); - - supplier_t supplier; - for (DSS_HUGE i = 1; i <= total_rows; ++i) { - if (mk_supp(i, &supplier) < 0) { - break; - } - - if (callback) { - callback(&supplier); - } - rows_generated++; - - if (max_rows > 0 && rows_generated >= max_rows) { - row_stop(DBGEN_SUPP); - return; - } - } - - row_stop(DBGEN_SUPP); + // Forward to the generic implementation for Supplier + generate_generic(callback, max_rows); } void tpch::DBGenWrapper::generate_nation( std::function callback) { - - if (!initialized_) { - init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_NATION); - - code_t nation; - for (DSS_HUGE i = 1; i <= 25; ++i) { - if (mk_nation(i, &nation) < 0) { - break; - } - - if (callback) { - callback(&nation); - } - } - - row_stop(DBGEN_NATION); + // Forward to the generic implementation for Nation + generate_generic(callback); } void tpch::DBGenWrapper::generate_region( std::function callback) { - - if (!initialized_) { - init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_REGION); - - code_t region; - for (DSS_HUGE i = 1; i <= 5; ++i) { - if (mk_region(i, ®ion) < 0) { - break; - } - - if (callback) { - callback(®ion); - } - } - - row_stop(DBGEN_REGION); + // Forward to the generic implementation for Region + generate_generic(callback); } void tpch::DBGenWrapper::generate_all_tables( @@ -669,170 +525,19 @@ DBGenWrapper::generate_lineitem_batches(size_t batch_size, size_t max_rows) { return LineitemBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Orders batch iterator -// ======================================================================= - -DBGenWrapper::OrdersBatchIterator::OrdersBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? static_cast(get_row_count(TableType::ORDERS, wrapper_->scale_factor_)) - : std::min(max_rows, static_cast(get_row_count(TableType::ORDERS, wrapper_->scale_factor_)))) - , current_row_(1) { - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_ORDER); -} - -DBGenWrapper::OrdersBatchIterator::Batch -DBGenWrapper::OrdersBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - long total_rows = get_row_count(TableType::ORDERS, wrapper_->scale_factor_); - - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= static_cast(total_rows)) { - order_t order; - if (mk_order(current_row_, &order, 0) < 0) { - break; - } - - batch.rows.push_back(order); - remaining_--; - current_row_++; - } - - if (remaining_ == 0 || current_row_ > static_cast(total_rows)) { - row_stop(DBGEN_ORDER); - } - - return batch; -} - +// Orders batch iterator: implementation provided by BatchIteratorImpl in header DBGenWrapper::OrdersBatchIterator DBGenWrapper::generate_orders_batches(size_t batch_size, size_t max_rows) { return OrdersBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Customer batch iterator -// ======================================================================= - -DBGenWrapper::CustomerBatchIterator::CustomerBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? static_cast(get_row_count(TableType::CUSTOMER, wrapper_->scale_factor_)) - : std::min(max_rows, static_cast(get_row_count(TableType::CUSTOMER, wrapper_->scale_factor_)))) - , current_row_(1) { - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_CUST); -} - -DBGenWrapper::CustomerBatchIterator::Batch -DBGenWrapper::CustomerBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - while (batch.rows.size() < batch_size_ && remaining_ > 0 ) { - customer_t cust; - if (mk_cust(current_row_, &cust) < 0) { - break; - } - - batch.rows.push_back(cust); - remaining_--; - current_row_++; - } - - if (remaining_ <= 0) { - row_stop(DBGEN_CUST); - } - - return batch; -} - +// Customer batch iterator: implementation provided by BatchIteratorImpl in header DBGenWrapper::CustomerBatchIterator DBGenWrapper::generate_customer_batches(size_t batch_size, size_t max_rows) { return CustomerBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Part batch iterator -// ======================================================================= - -DBGenWrapper::PartBatchIterator::PartBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? static_cast(get_row_count(TableType::PART, wrapper_->scale_factor_)) - : std::min(max_rows, static_cast(get_row_count(TableType::PART, wrapper_->scale_factor_)))) - , current_row_(1) { - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_PART); -} - -DBGenWrapper::PartBatchIterator::Batch -DBGenWrapper::PartBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - long total_rows = get_row_count(TableType::PART, wrapper_->scale_factor_); - - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= static_cast(total_rows)) { - part_t part; - if (mk_part(current_row_, &part) < 0) { - break; - } - - batch.rows.push_back(part); - remaining_--; - current_row_++; - } - - if (remaining_ == 0 || current_row_ > static_cast(total_rows)) { - row_stop(DBGEN_PART); - } - - return batch; -} - +// Part batch iterator: implementation provided by BatchIteratorImpl in header DBGenWrapper::PartBatchIterator DBGenWrapper::generate_part_batches(size_t batch_size, size_t max_rows) { return PartBatchIterator(this, batch_size, max_rows); @@ -905,170 +610,19 @@ DBGenWrapper::generate_partsupp_batches(size_t batch_size, size_t max_rows) { return PartsuppBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Supplier batch iterator -// ======================================================================= - -DBGenWrapper::SupplierBatchIterator::SupplierBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? static_cast(get_row_count(TableType::SUPPLIER, wrapper_->scale_factor_)) - : std::min(max_rows, static_cast(get_row_count(TableType::SUPPLIER, wrapper_->scale_factor_)))) - , current_row_(1) { - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_SUPP); -} - -DBGenWrapper::SupplierBatchIterator::Batch -DBGenWrapper::SupplierBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - long total_rows = get_row_count(TableType::SUPPLIER, wrapper_->scale_factor_); - - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= static_cast(total_rows)) { - supplier_t supp; - if (mk_supp(current_row_, &supp) < 0) { - break; - } - - batch.rows.push_back(supp); - remaining_--; - current_row_++; - } - - if (remaining_ == 0 || current_row_ > static_cast(total_rows)) { - row_stop(DBGEN_SUPP); - } - - return batch; -} - +// Supplier batch iterator: implementation provided by BatchIteratorImpl in header DBGenWrapper::SupplierBatchIterator DBGenWrapper::generate_supplier_batches(size_t batch_size, size_t max_rows) { return SupplierBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Nation batch iterator -// ======================================================================= - -DBGenWrapper::NationBatchIterator::NationBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? size_t(25) : std::min(max_rows, size_t(25))) // Nation table has exactly 25 rows - , current_row_(1) { // Nation IDs are 1-indexed - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_NATION); -} - -DBGenWrapper::NationBatchIterator::Batch -DBGenWrapper::NationBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - const long total_rows = 25; // Nation table always has 25 rows - - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= static_cast(total_rows)) { - code_t nation; - if (mk_nation(current_row_, &nation) < 0) { - break; - } - - batch.rows.push_back(nation); - remaining_--; - current_row_++; - } - - if (remaining_ == 0 || current_row_ > static_cast(total_rows)) { - row_stop(DBGEN_NATION); - } - - return batch; -} - +// Nation batch iterator: implementation provided by BatchIteratorImpl in header DBGenWrapper::NationBatchIterator DBGenWrapper::generate_nation_batches(size_t batch_size, size_t max_rows) { return NationBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Region batch iterator -// ======================================================================= - -DBGenWrapper::RegionBatchIterator::RegionBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? size_t(5) : std::min(max_rows, size_t(5))) // Region table has exactly 5 rows - , current_row_(1) { // Region IDs are 1-indexed - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_REGION); -} - -DBGenWrapper::RegionBatchIterator::Batch -DBGenWrapper::RegionBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - const long total_rows = 5; // Region table always has 5 rows - - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= static_cast(total_rows)) { - code_t region; - if (mk_region(current_row_, ®ion) < 0) { - break; - } - - batch.rows.push_back(region); - remaining_--; - current_row_++; - } - - if (remaining_ == 0 || current_row_ > static_cast(total_rows)) { - row_stop(DBGEN_REGION); - } - - return batch; -} - +// Region batch iterator: implementation provided by BatchIteratorImpl in header DBGenWrapper::RegionBatchIterator DBGenWrapper::generate_region_batches(size_t batch_size, size_t max_rows) { return RegionBatchIterator(this, batch_size, max_rows); diff --git a/third_party/arrow b/third_party/arrow new file mode 160000 index 0000000..6ec9162 --- /dev/null +++ b/third_party/arrow @@ -0,0 +1 @@ +Subproject commit 6ec91627ecee34b53291c915d53e674e32ba3367 From 3ee4eb20fcc137a598d5599a0b462f5f8d0f5f66 Mon Sep 17 00:00:00 2001 From: Timur Safin Date: Sun, 18 Jan 2026 19:52:49 +0300 Subject: [PATCH 2/3] refactor(dbgen): handle LINEITEM and PARTSUPP in BatchIteratorImpl; migrate iterators to template alias --- include/tpch/dbgen_wrapper.hpp | 84 ++++++++++++---------- src/dbgen/dbgen_wrapper.cpp | 123 --------------------------------- 2 files changed, 49 insertions(+), 158 deletions(-) diff --git a/include/tpch/dbgen_wrapper.hpp b/include/tpch/dbgen_wrapper.hpp index 8fa3ded..9b2c98d 100644 --- a/include/tpch/dbgen_wrapper.hpp +++ b/include/tpch/dbgen_wrapper.hpp @@ -315,6 +315,12 @@ class DBGenWrapper { row_start(DBGEN_SUPP); } else if constexpr (Traits::table == TableType::NATION) { row_start(DBGEN_NATION); + } else if constexpr (Traits::table == TableType::LINEITEM) { + // Lineitem generation is driven by orders + row_start(DBGEN_LINE); + } else if constexpr (Traits::table == TableType::PARTSUPP) { + // Partsupp generation is driven by part rows + row_start(DBGEN_PSUPP); } else if constexpr (Traits::table == TableType::REGION) { row_start(DBGEN_REGION); } @@ -328,7 +334,16 @@ class DBGenWrapper { batch.rows.reserve(std::min(batch_size_, remaining_)); - size_t total_rows = static_cast(get_row_count(Traits::table, wrapper_->scale_factor_)); + size_t total_rows; + if constexpr (Traits::table == TableType::LINEITEM) { + // LINEITEM rows are produced while iterating ORDERS + total_rows = static_cast(get_row_count(TableType::ORDERS, wrapper_->scale_factor_)); + } else if constexpr (Traits::table == TableType::PARTSUPP) { + // PARTSUPP rows are produced while iterating PART + total_rows = static_cast(get_row_count(TableType::PART, wrapper_->scale_factor_)); + } else { + total_rows = static_cast(get_row_count(Traits::table, wrapper_->scale_factor_)); + } while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= total_rows) { Row r{}; @@ -344,10 +359,36 @@ class DBGenWrapper { if (mk_nation(static_cast(current_row_), &r) < 0) break; } else if constexpr (Traits::table == TableType::REGION) { if (mk_region(static_cast(current_row_), &r) < 0) break; + } else if constexpr (Traits::table == TableType::LINEITEM) { + // Generate next order and emit its lineitems + order_t ord{}; + if (mk_order(static_cast(current_row_), &ord, 0) < 0) break; + + for (int j = 0; j < (int)ord.lines && j < O_LCNT_MAX; ++j) { + if (batch.rows.size() >= batch_size_) break; + batch.rows.push_back(ord.l[j]); + remaining_--; + } + current_row_++; + // continue to next iteration (we already pushed rows) + if (batch.rows.size() >= batch_size_ || remaining_ == 0) break; + else continue; + } else if constexpr (Traits::table == TableType::PARTSUPP) { + // Generate next part and emit its partsupp rows + part_t prt{}; + if (mk_part(static_cast(current_row_), &prt) < 0) break; + + for (int j = 0; j < SUPP_PER_PART; ++j) { + if (batch.rows.size() >= batch_size_) break; + batch.rows.push_back(prt.s[j]); + remaining_--; + } + current_row_++; + if (batch.rows.size() >= batch_size_ || remaining_ == 0) break; + else continue; } else { static_assert(always_false::value, "Unsupported batch iterator table"); } - batch.rows.push_back(r); remaining_--; current_row_++; @@ -367,6 +408,10 @@ class DBGenWrapper { row_stop(DBGEN_NATION); } else if constexpr (Traits::table == TableType::REGION) { row_stop(DBGEN_REGION); + } else if constexpr (Traits::table == TableType::LINEITEM) { + row_stop(DBGEN_LINE); + } else if constexpr (Traits::table == TableType::PARTSUPP) { + row_stop(DBGEN_PSUPP); } } @@ -396,22 +441,7 @@ class DBGenWrapper { * } * ``` */ - class LineitemBatchIterator { - public: - using Batch = DBGenBatch; - - LineitemBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_order_; - }; - + using LineitemBatchIterator = BatchIteratorImpl; /** * Create a batch iterator for lineitem generation * @@ -442,23 +472,7 @@ class DBGenWrapper { /** * Batch iterator for partsupp rows (zero-copy friendly) */ - // Partsupp is nested per-part; keep concrete iterator for now - class PartsuppBatchIterator { - public: - using Batch = DBGenBatch; - - PartsuppBatchIterator(DBGenWrapper* wrapper, size_t batch_size, size_t max_rows); - - bool has_next() const { return remaining_ > 0; } - Batch next(); - - private: - DBGenWrapper* wrapper_; - size_t batch_size_; - size_t remaining_; - size_t current_row_; - }; - + using PartsuppBatchIterator = BatchIteratorImpl; PartsuppBatchIterator generate_partsupp_batches(size_t batch_size, size_t max_rows); /** diff --git a/src/dbgen/dbgen_wrapper.cpp b/src/dbgen/dbgen_wrapper.cpp index 2ee536e..de3f563 100644 --- a/src/dbgen/dbgen_wrapper.cpp +++ b/src/dbgen/dbgen_wrapper.cpp @@ -459,67 +459,6 @@ bool dbgen_is_initialized() { // Phase 13.4: Batch generation implementation for zero-copy optimizations // ============================================================================ -DBGenWrapper::LineitemBatchIterator::LineitemBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? static_cast(get_row_count(TableType::LINEITEM, wrapper_->scale_factor_)) - : std::min(max_rows, static_cast(get_row_count(TableType::LINEITEM, wrapper_->scale_factor_)))) - , current_order_(1) { - - // Initialize dbgen if needed - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - // Reset RNG state before generating rows - dbgen_reset_seeds(); - row_start(DBGEN_LINE); -} - -DBGenWrapper::LineitemBatchIterator::Batch -DBGenWrapper::LineitemBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; // Empty batch - } - - // Pre-allocate space for batch - batch.rows.reserve(std::min(batch_size_, remaining_)); - - // Generate orders and extract lineitem rows until we fill the batch - order_t order; - long total_orders = get_row_count(TableType::ORDERS, wrapper_->scale_factor_); - - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_order_ <= total_orders) { - if (mk_order(current_order_, &order, 0) < 0) { - break; - } - - // Extract each lineitem from the order - for (int j = 0; j < (int)order.lines && j < O_LCNT_MAX; ++j) { - batch.rows.push_back(order.l[j]); - remaining_--; - - if (batch.rows.size() >= batch_size_ || remaining_ == 0) { - break; - } - } - - current_order_++; - } - - // If we're done, stop the row generation - if (remaining_ == 0 || current_order_ > total_orders) { - row_stop(DBGEN_LINE); - } - - return batch; -} - DBGenWrapper::LineitemBatchIterator DBGenWrapper::generate_lineitem_batches(size_t batch_size, size_t max_rows) { return LineitemBatchIterator(this, batch_size, max_rows); @@ -543,68 +482,6 @@ DBGenWrapper::generate_part_batches(size_t batch_size, size_t max_rows) { return PartBatchIterator(this, batch_size, max_rows); } -// ======================================================================= -// Partsupp batch iterator -// ======================================================================= - -DBGenWrapper::PartsuppBatchIterator::PartsuppBatchIterator( - DBGenWrapper* wrapper, - size_t batch_size, - size_t max_rows) - : wrapper_(wrapper) - , batch_size_(batch_size) - , remaining_(max_rows == 0 ? static_cast(get_row_count(TableType::PARTSUPP, wrapper_->scale_factor_)) - : std::min(max_rows, static_cast(get_row_count(TableType::PARTSUPP, wrapper_->scale_factor_)))) - , current_row_(1) { - - if (!wrapper_->initialized_) { - wrapper_->init_dbgen(); - } - - dbgen_reset_seeds(); - row_start(DBGEN_PSUPP); -} - -DBGenWrapper::PartsuppBatchIterator::Batch -DBGenWrapper::PartsuppBatchIterator::next() { - Batch batch; - - if (remaining_ == 0) { - return batch; - } - - batch.rows.reserve(std::min(batch_size_, remaining_)); - - long total_parts = get_row_count(TableType::PART, wrapper_->scale_factor_); - - // Partsupp rows are generated as part of part generation - // Each part has SUPP_PER_PART (4) partsupps - while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= static_cast(total_parts)) { - part_t part; - if (mk_part(current_row_, &part) < 0) { - break; - } - - // Extract each partsupp from the part - for (int j = 0; j < SUPP_PER_PART; ++j) { - batch.rows.push_back(part.s[j]); - remaining_--; - - if (batch.rows.size() >= batch_size_ || remaining_ == 0) { - break; - } - } - - current_row_++; - } - - if (remaining_ == 0 || current_row_ > static_cast(total_parts)) { - row_stop(DBGEN_PSUPP); - } - - return batch; -} - DBGenWrapper::PartsuppBatchIterator DBGenWrapper::generate_partsupp_batches(size_t batch_size, size_t max_rows) { return PartsuppBatchIterator(this, batch_size, max_rows); From 408b0d60302aa40fb1504bfe4d198ab97f212123 Mon Sep 17 00:00:00 2001 From: Timur Safin Date: Sun, 18 Jan 2026 22:12:24 +0300 Subject: [PATCH 3/3] dbgen: refactor wrapper (templates/traits + RAII), seed-snapshot API, deterministic init; add GIT_RULES.md --- GIT_RULES.md | 8 ++ include/tpch/dbgen_wrapper.hpp | 131 +++++++++++++++++++++++++++------ src/dbgen/dbgen_stubs.c | 16 ++++ src/dbgen/dbgen_wrapper.cpp | 7 +- tests/CMakeLists.txt | 18 +++++ 5 files changed, 154 insertions(+), 26 deletions(-) create mode 100644 GIT_RULES.md diff --git a/GIT_RULES.md b/GIT_RULES.md new file mode 100644 index 0000000..744d632 --- /dev/null +++ b/GIT_RULES.md @@ -0,0 +1,8 @@ +Repository Git Rules + +- Never use `git add -A` or `git add .` to stage changes. +- Stage only specific files: `git add path/to/file` or use `git add -p` to interactively select hunks. +- Review changes before committing: `git status --porcelain`, `git diff` and `git diff --staged`. +- Keep commits focused and small; prefer descriptive commit messages. +- If you accidentally commit unwanted files, use `git reset --mixed HEAD~1` and then selectively stage the intended files. +- When in doubt, ask before running broad git commands. diff --git a/include/tpch/dbgen_wrapper.hpp b/include/tpch/dbgen_wrapper.hpp index 9b2c98d..619b08d 100644 --- a/include/tpch/dbgen_wrapper.hpp +++ b/include/tpch/dbgen_wrapper.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -298,13 +299,16 @@ class DBGenWrapper { // Determine total remaining rows size_t total = static_cast(get_row_count(Traits::table, wrapper_->scale_factor_)); remaining_ = (max_rows == 0) ? total : std::min(static_cast(max_rows), total); + (void)total; (void)max_rows; (void)remaining_; current_row_ = 1; if (!wrapper_->initialized_) { wrapper_->init_dbgen(); } - dbgen_reset_seeds(); + /* Restore captured seed snapshot so iterator starts from + the same RNG state as the callback-based generator */ + dbgen_restore_seed_snapshot(); if constexpr (Traits::table == TableType::ORDERS) { row_start(DBGEN_ORDER); } else if constexpr (Traits::table == TableType::CUSTOMER) { @@ -316,10 +320,10 @@ class DBGenWrapper { } else if constexpr (Traits::table == TableType::NATION) { row_start(DBGEN_NATION); } else if constexpr (Traits::table == TableType::LINEITEM) { - // Lineitem generation is driven by orders + // Match callback-based generation which uses DBGEN_LINE row_start(DBGEN_LINE); } else if constexpr (Traits::table == TableType::PARTSUPP) { - // Partsupp generation is driven by part rows + // Match callback-based generation which uses DBGEN_PSUPP row_start(DBGEN_PSUPP); } else if constexpr (Traits::table == TableType::REGION) { row_start(DBGEN_REGION); @@ -348,54 +352,128 @@ class DBGenWrapper { while (batch.rows.size() < batch_size_ && remaining_ > 0 && current_row_ <= total_rows) { Row r{}; if constexpr (Traits::table == TableType::ORDERS) { - if (mk_order(static_cast(current_row_), &r, 0) < 0) break; + if (mk_order(static_cast(current_row_), &r, 0) < 0) { remaining_ = 0; break; } + batch.rows.push_back(r); + remaining_--; + current_row_++; } else if constexpr (Traits::table == TableType::CUSTOMER) { - if (mk_cust(static_cast(current_row_), &r) < 0) break; + if (mk_cust(static_cast(current_row_), &r) < 0) { remaining_ = 0; break; } + batch.rows.push_back(r); + remaining_--; + current_row_++; } else if constexpr (Traits::table == TableType::PART) { - if (mk_part(static_cast(current_row_), &r) < 0) break; + if (mk_part(static_cast(current_row_), &r) < 0) { remaining_ = 0; break; } + batch.rows.push_back(r); + remaining_--; + current_row_++; } else if constexpr (Traits::table == TableType::SUPPLIER) { - if (mk_supp(static_cast(current_row_), &r) < 0) break; + if (mk_supp(static_cast(current_row_), &r) < 0) { remaining_ = 0; break; } + batch.rows.push_back(r); + remaining_--; + current_row_++; } else if constexpr (Traits::table == TableType::NATION) { - if (mk_nation(static_cast(current_row_), &r) < 0) break; + if (mk_nation(static_cast(current_row_), &r) < 0) { remaining_ = 0; break; } + batch.rows.push_back(r); + remaining_--; + current_row_++; } else if constexpr (Traits::table == TableType::REGION) { - if (mk_region(static_cast(current_row_), &r) < 0) break; + if (mk_region(static_cast(current_row_), &r) < 0) { remaining_ = 0; break; } + batch.rows.push_back(r); + remaining_--; + current_row_++; } else if constexpr (Traits::table == TableType::LINEITEM) { - // Generate next order and emit its lineitems + // First, resume any pending children from a partially-emitted order + while (pending_index_ < pending_children_.size() && batch.rows.size() < batch_size_ && remaining_ > 0) { + batch.rows.push_back(pending_children_[pending_index_++]); + remaining_--; + } + + if (pending_index_ == pending_children_.size() && !pending_children_.empty()) { + // We finished emitting the pending order's children; advance to next order + pending_children_.clear(); + pending_index_ = 0; + current_row_++; + } + + if (batch.rows.size() >= batch_size_ || remaining_ == 0) break; + + // No pending children (or we completed them) — generate the next order order_t ord{}; - if (mk_order(static_cast(current_row_), &ord, 0) < 0) break; + if (mk_order(static_cast(current_row_), &ord, 0) < 0) { remaining_ = 0; break; } + // Fill pending_children_ with this order's lineitems and emit as many as fit + pending_children_.clear(); + pending_index_ = 0; for (int j = 0; j < (int)ord.lines && j < O_LCNT_MAX; ++j) { - if (batch.rows.size() >= batch_size_) break; - batch.rows.push_back(ord.l[j]); + pending_children_.push_back(ord.l[j]); + } + + // Emit from pending_children_ in this same loop iteration + while (pending_index_ < pending_children_.size() && batch.rows.size() < batch_size_ && remaining_ > 0) { + batch.rows.push_back(pending_children_[pending_index_++]); remaining_--; } - current_row_++; - // continue to next iteration (we already pushed rows) + + // If we consumed all children, advance to next order now + if (pending_index_ == pending_children_.size()) { + pending_children_.clear(); + pending_index_ = 0; + current_row_++; + } + + // If batch is full or we're done, break; otherwise continue outer while if (batch.rows.size() >= batch_size_ || remaining_ == 0) break; else continue; } else if constexpr (Traits::table == TableType::PARTSUPP) { - // Generate next part and emit its partsupp rows + // Resume any pending partsupp children + while (pending_index_ < pending_children_.size() && batch.rows.size() < batch_size_ && remaining_ > 0) { + batch.rows.push_back(pending_children_[pending_index_++]); + remaining_--; + } + + if (pending_index_ == pending_children_.size() && !pending_children_.empty()) { + pending_children_.clear(); + pending_index_ = 0; + current_row_++; + } + + if (batch.rows.size() >= batch_size_ || remaining_ == 0) break; + part_t prt{}; - if (mk_part(static_cast(current_row_), &prt) < 0) break; + if (mk_part(static_cast(current_row_), &prt) < 0) { remaining_ = 0; break; } + pending_children_.clear(); + pending_index_ = 0; for (int j = 0; j < SUPP_PER_PART; ++j) { - if (batch.rows.size() >= batch_size_) break; - batch.rows.push_back(prt.s[j]); + pending_children_.push_back(prt.s[j]); + } + + while (pending_index_ < pending_children_.size() && batch.rows.size() < batch_size_) { + batch.rows.push_back(pending_children_[pending_index_++]); remaining_--; } - current_row_++; + + if (pending_index_ == pending_children_.size()) { + pending_children_.clear(); + pending_index_ = 0; + current_row_++; + } + if (batch.rows.size() >= batch_size_ || remaining_ == 0) break; else continue; } else { static_assert(always_false::value, "Unsupported batch iterator table"); } - batch.rows.push_back(r); - remaining_--; - current_row_++; } - // Stop generation if complete + // Stop generation if complete. If we've advanced past the + // producer's total_rows but still have a non-zero `remaining_`, + // force termination so has_next() will return false and we + // won't emit an infinite stream of empty batches. if (remaining_ == 0 || current_row_ > total_rows) { + if (current_row_ > total_rows) { + remaining_ = 0; + } if constexpr (Traits::table == TableType::ORDERS) { row_stop(DBGEN_ORDER); } else if constexpr (Traits::table == TableType::CUSTOMER) { @@ -423,6 +501,11 @@ class DBGenWrapper { size_t batch_size_; size_t remaining_; size_t current_row_; + // Buffer to hold child rows when an order/part's children are split + // across batch boundaries (LINEITEM, PARTSUPP). We keep them here + // and resume emitting on the next next() call. + std::vector pending_children_; + size_t pending_index_ = 0; }; /** diff --git a/src/dbgen/dbgen_stubs.c b/src/dbgen/dbgen_stubs.c index ff0f5ec..fd0fc1a 100644 --- a/src/dbgen/dbgen_stubs.c +++ b/src/dbgen/dbgen_stubs.c @@ -88,6 +88,22 @@ void row_stop(int t) { extern unsigned long Seed[]; +/* Snapshot buffer and helpers to capture/restore Seed[] state */ +#include +static seed_t seed_snapshot[MAX_STREAM + 1]; +static int seed_snapshot_initialized = 0; + +void dbgen_capture_seed_snapshot(void) { + memcpy(seed_snapshot, Seed, sizeof(seed_snapshot)); + seed_snapshot_initialized = 1; +} + +void dbgen_restore_seed_snapshot(void) { + if (seed_snapshot_initialized) { + memcpy(Seed, seed_snapshot, sizeof(seed_snapshot)); + } +} + void dbg_text(char *tgt, int min, int max, int sd) { /* Generate a simple deterministic comment based on seed and length */ diff --git a/src/dbgen/dbgen_wrapper.cpp b/src/dbgen/dbgen_wrapper.cpp index de3f563..1da2282 100644 --- a/src/dbgen/dbgen_wrapper.cpp +++ b/src/dbgen/dbgen_wrapper.cpp @@ -139,6 +139,9 @@ void tpch::DBGenWrapper::generate_lineitem( // Reset RNG state before generating rows dbgen_reset_seeds(); + /* Capture the initial RNG seed snapshot so other generators can restore it + and reproduce the same sequence */ + dbgen_capture_seed_snapshot(); row_start(DBGEN_LINE); @@ -146,8 +149,8 @@ void tpch::DBGenWrapper::generate_lineitem( // LineItem rows are generated implicitly via order generation // Each order has between 1-7 line items - order_t order; for (DSS_HUGE i = 1; i <= get_row_count(TableType::ORDERS, scale_factor_); ++i) { + order_t order{}; if (mk_order(i, &order, 0) < 0) { break; } @@ -204,8 +207,8 @@ void tpch::DBGenWrapper::generate_partsupp( long rows_generated = 0; long total_rows_part = get_row_count(TableType::PART, scale_factor_); - part_t part; for (DSS_HUGE i = 1; i <= total_rows_part; ++i) { + part_t part{}; if (mk_part(i, &part) < 0) { break; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8be6577..0d5716e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,5 +40,23 @@ if(TPCH_BUILD_TESTS) # Register tests gtest_discover_tests(buffer_lifetime_manager_test) + # DBGen batch iterator tests + add_executable(dbgen_batch_iterator_test + dbgen_batch_iterator_test.cpp + ) + + target_link_libraries(dbgen_batch_iterator_test + PRIVATE + tpch_core + GTest::gtest_main + ) + + target_include_directories(dbgen_batch_iterator_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ) + + gtest_discover_tests(dbgen_batch_iterator_test) + message(STATUS "Test targets configured successfully") endif()