1919
2020#include " gtest/gtest.h"
2121
22- #include < arrow/dataset/dataset.h>
23- #include < arrow/dataset/file_base.h>
24- #include < arrow/dataset/file_parquet.h>
2522#include " arrow/array.h"
23+ #include " arrow/dataset/dataset.h"
24+ #include " arrow/dataset/file_base.h"
25+ #include " arrow/dataset/file_parquet.h"
2626#include " arrow/dataset/parquet_encryption_config.h"
2727#include " arrow/dataset/partition.h"
2828#include " arrow/filesystem/mockfs.h"
2929#include " arrow/io/api.h"
3030#include " arrow/status.h"
3131#include " arrow/table.h"
3232#include " arrow/testing/gtest_util.h"
33+ #include " arrow/testing/random.h"
3334#include " arrow/type.h"
3435#include " parquet/arrow/reader.h"
3536#include " parquet/encryption/crypto_factory.h"
36- #include " parquet/encryption/encryption.h"
3737#include " parquet/encryption/encryption_internal.h"
3838#include " parquet/encryption/kms_client.h"
3939#include " parquet/encryption/test_in_memory_kms.h"
@@ -51,14 +51,14 @@ using arrow::internal::checked_pointer_cast;
5151namespace arrow {
5252namespace dataset {
5353
54- class DatasetEncryptionTest : public ::testing::Test {
54+ // Base class to test writing and reading encrypted dataset.
55+ class DatasetEncryptionTestBase : public ::testing::Test {
5556 public:
5657 // This function creates a mock file system using the current time point, creates a
5758 // directory with the given base directory path, and writes a dataset to it using
58- // provided Parquet file write options. The dataset is partitioned using a Hive
59- // partitioning scheme. The function also checks if the written files exist in the file
60- // system.
61- static void SetUpTestSuite () {
59+ // provided Parquet file write options. The function also checks if the written files
60+ // exist in the file system.
61+ void SetUp () override {
6262#ifdef ARROW_VALGRIND
6363 // Not necessary otherwise, but prevents a Valgrind leak by making sure
6464 // OpenSSL initialization is done from the main thread
@@ -71,24 +71,8 @@ class DatasetEncryptionTest : public ::testing::Test {
7171 std::chrono::system_clock::now (), {}));
7272 ASSERT_OK (file_system_->CreateDir (std::string (kBaseDir )));
7373
74- // Prepare table data.
75- auto table_schema = schema ({field (" a" , int64 ()), field (" c" , int64 ()),
76- field (" e" , int64 ()), field (" part" , utf8 ())});
77- table_ = TableFromJSON (table_schema, {R"( [
78- [ 0, 9, 1, "a" ],
79- [ 1, 8, 2, "a" ],
80- [ 2, 7, 1, "c" ],
81- [ 3, 6, 2, "c" ],
82- [ 4, 5, 1, "e" ],
83- [ 5, 4, 2, "e" ],
84- [ 6, 3, 1, "g" ],
85- [ 7, 2, 2, "g" ],
86- [ 8, 1, 1, "i" ],
87- [ 9, 0, 2, "i" ]
88- ])" });
89-
90- // Use a Hive-style partitioning scheme.
91- partitioning_ = std::make_shared<HivePartitioning>(schema ({field (" part" , utf8 ())}));
74+ // Init dataset and partitioning.
75+ ASSERT_NO_FATAL_FAILURE (PrepareTableAndPartitioning ());
9276
9377 // Prepare encryption properties.
9478 std::unordered_map<std::string, std::string> key_map;
@@ -133,13 +117,81 @@ class DatasetEncryptionTest : public ::testing::Test {
133117 ASSERT_OK (FileSystemDataset::Write (write_options, std::move (scanner)));
134118 }
135119
120+ virtual void PrepareTableAndPartitioning () = 0;
121+
122+ void TestScanDataset () {
123+ // Create decryption properties.
124+ auto decryption_config =
125+ std::make_shared<parquet::encryption::DecryptionConfiguration>();
126+ auto parquet_decryption_config = std::make_shared<ParquetDecryptionConfig>();
127+ parquet_decryption_config->crypto_factory = crypto_factory_;
128+ parquet_decryption_config->kms_connection_config = kms_connection_config_;
129+ parquet_decryption_config->decryption_config = std::move (decryption_config);
130+
131+ // Set scan options.
132+ auto parquet_scan_options = std::make_shared<ParquetFragmentScanOptions>();
133+ parquet_scan_options->parquet_decryption_config =
134+ std::move (parquet_decryption_config);
135+
136+ auto file_format = std::make_shared<ParquetFileFormat>();
137+ file_format->default_fragment_scan_options = std::move (parquet_scan_options);
138+
139+ // Get FileInfo objects for all files under the base directory
140+ fs::FileSelector selector;
141+ selector.base_dir = kBaseDir ;
142+ selector.recursive = true ;
143+
144+ FileSystemFactoryOptions factory_options;
145+ factory_options.partitioning = partitioning_;
146+ factory_options.partition_base_dir = kBaseDir ;
147+ ASSERT_OK_AND_ASSIGN (auto dataset_factory,
148+ FileSystemDatasetFactory::Make (file_system_, selector,
149+ file_format, factory_options));
150+
151+ // Read dataset into table
152+ ASSERT_OK_AND_ASSIGN (auto dataset, dataset_factory->Finish ());
153+ ASSERT_OK_AND_ASSIGN (auto scanner_builder, dataset->NewScan ());
154+ ASSERT_OK_AND_ASSIGN (auto scanner, scanner_builder->Finish ());
155+ ASSERT_OK_AND_ASSIGN (auto read_table, scanner->ToTable ());
156+
157+ // Verify the data was read correctly
158+ ASSERT_OK_AND_ASSIGN (auto combined_table, read_table->CombineChunks ());
159+ // Validate the table
160+ ASSERT_OK (combined_table->ValidateFull ());
161+ AssertTablesEqual (*combined_table, *table_);
162+ }
163+
136164 protected:
137- inline static std::shared_ptr<fs::FileSystem> file_system_;
138- inline static std::shared_ptr<Table> table_;
139- inline static std::shared_ptr<HivePartitioning> partitioning_;
140- inline static std::shared_ptr<parquet::encryption::CryptoFactory> crypto_factory_;
141- inline static std::shared_ptr<parquet::encryption::KmsConnectionConfig>
142- kms_connection_config_;
165+ std::shared_ptr<fs::FileSystem> file_system_;
166+ std::shared_ptr<Table> table_;
167+ std::shared_ptr<Partitioning> partitioning_;
168+ std::shared_ptr<parquet::encryption::CryptoFactory> crypto_factory_;
169+ std::shared_ptr<parquet::encryption::KmsConnectionConfig> kms_connection_config_;
170+ };
171+
172+ class DatasetEncryptionTest : public DatasetEncryptionTestBase {
173+ public:
174+ // The dataset is partitioned using a Hive partitioning scheme.
175+ void PrepareTableAndPartitioning () override {
176+ // Prepare table data.
177+ auto table_schema = schema ({field (" a" , int64 ()), field (" c" , int64 ()),
178+ field (" e" , int64 ()), field (" part" , utf8 ())});
179+ table_ = TableFromJSON (table_schema, {R"( [
180+ [ 0, 9, 1, "a" ],
181+ [ 1, 8, 2, "a" ],
182+ [ 2, 7, 1, "c" ],
183+ [ 3, 6, 2, "c" ],
184+ [ 4, 5, 1, "e" ],
185+ [ 5, 4, 2, "e" ],
186+ [ 6, 3, 1, "g" ],
187+ [ 7, 2, 2, "g" ],
188+ [ 8, 1, 1, "i" ],
189+ [ 9, 0, 2, "i" ]
190+ ])" });
191+
192+ // Use a Hive-style partitioning scheme.
193+ partitioning_ = std::make_shared<HivePartitioning>(schema ({field (" part" , utf8 ())}));
194+ }
143195};
144196
145197// This test demonstrates the process of writing a partitioned Parquet file with the same
@@ -148,44 +200,7 @@ class DatasetEncryptionTest : public ::testing::Test {
148200// test reads the data back and verifies that it can be successfully decrypted and
149201// scanned.
150202TEST_F (DatasetEncryptionTest, WriteReadDatasetWithEncryption) {
151- // Create decryption properties.
152- auto decryption_config =
153- std::make_shared<parquet::encryption::DecryptionConfiguration>();
154- auto parquet_decryption_config = std::make_shared<ParquetDecryptionConfig>();
155- parquet_decryption_config->crypto_factory = crypto_factory_;
156- parquet_decryption_config->kms_connection_config = kms_connection_config_;
157- parquet_decryption_config->decryption_config = std::move (decryption_config);
158-
159- // Set scan options.
160- auto parquet_scan_options = std::make_shared<ParquetFragmentScanOptions>();
161- parquet_scan_options->parquet_decryption_config = std::move (parquet_decryption_config);
162-
163- auto file_format = std::make_shared<ParquetFileFormat>();
164- file_format->default_fragment_scan_options = std::move (parquet_scan_options);
165-
166- // Get FileInfo objects for all files under the base directory
167- fs::FileSelector selector;
168- selector.base_dir = kBaseDir ;
169- selector.recursive = true ;
170-
171- FileSystemFactoryOptions factory_options;
172- factory_options.partitioning = partitioning_;
173- factory_options.partition_base_dir = kBaseDir ;
174- ASSERT_OK_AND_ASSIGN (auto dataset_factory,
175- FileSystemDatasetFactory::Make (file_system_, selector, file_format,
176- factory_options));
177-
178- // Read dataset into table
179- ASSERT_OK_AND_ASSIGN (auto dataset, dataset_factory->Finish ());
180- ASSERT_OK_AND_ASSIGN (auto scanner_builder, dataset->NewScan ());
181- ASSERT_OK_AND_ASSIGN (auto scanner, scanner_builder->Finish ());
182- ASSERT_OK_AND_ASSIGN (auto read_table, scanner->ToTable ());
183-
184- // Verify the data was read correctly
185- ASSERT_OK_AND_ASSIGN (auto combined_table, read_table->CombineChunks ());
186- // Validate the table
187- ASSERT_OK (combined_table->ValidateFull ());
188- AssertTablesEqual (*combined_table, *table_);
203+ ASSERT_NO_FATAL_FAILURE (TestScanDataset ());
189204}
190205
191206// Read a single parquet file with and without decryption properties.
@@ -220,5 +235,30 @@ TEST_F(DatasetEncryptionTest, ReadSingleFile) {
220235 ASSERT_EQ (checked_pointer_cast<Int64Array>(table->column (2 )->chunk (0 ))->GetView (0 ), 1 );
221236}
222237
238+ // GH-39444: This test covers the case where parquet dataset scanner crashes when
239+ // processing encrypted datasets over 2^15 rows in multi-threaded mode.
240+ class LargeRowEncryptionTest : public DatasetEncryptionTestBase {
241+ public:
242+ // The dataset is partitioned using a Hive partitioning scheme.
243+ void PrepareTableAndPartitioning () override {
244+ // Specifically chosen to be greater than batch size for triggering prefetch.
245+ constexpr int kRowCount = 32769 ;
246+
247+ // Create a random floating-point array with large number of rows.
248+ arrow::random::RandomArrayGenerator rand_gen (0 );
249+ auto array = rand_gen.Float32 (kRowCount , 0.0 , 1.0 , false );
250+ auto table_schema = schema ({field (" a" , float32 ())});
251+
252+ // Prepare table and partitioning.
253+ table_ = arrow::Table::Make (table_schema, {array});
254+ partitioning_ = std::make_shared<dataset::DirectoryPartitioning>(arrow::schema ({}));
255+ }
256+ };
257+
258+ // Test for writing and reading encrypted dataset with large row count.
259+ TEST_F (LargeRowEncryptionTest, ReadEncryptLargeRows) {
260+ ASSERT_NO_FATAL_FAILURE (TestScanDataset ());
261+ }
262+
223263} // namespace dataset
224264} // namespace arrow
0 commit comments