Skip to content

Commit a869f96

Browse files
HaochengLIUliujiacheng777
authored andcommitted
apacheGH-34565: [C++] Teach dataset_writer to accept custom filename functor (apache#34984)
### Rationale for this change Existing basename_template will only use a monotonically increasing int as new filenames. when there is needs for custom filenames(left padding, hash-code), downstream users must rename the files in a post-processing step. ### What changes are included in this PR? A new functor is added to FileSystemDatasetWriteOptions which allows users to provide a custom name for dataset_writer. ### Are these changes tested? Yes. Unit tests are added for normal and ill-formed lambdas. ### Are there any user-facing changes? Yes. It allows users to customize output file names. * Closes: apache#34565 Authored-by: Haocheng Liu <lbtinglb@gmail.com> Signed-off-by: Weston Pace <weston.pace@gmail.com>
1 parent da40413 commit a869f96

3 files changed

Lines changed: 51 additions & 2 deletions

File tree

cpp/src/arrow/dataset/dataset_writer.cc

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <memory>
2222
#include <mutex>
2323
#include <unordered_map>
24+
#include <unordered_set>
2425

2526
#include "arrow/filesystem/path_util.h"
2627
#include "arrow/record_batch.h"
@@ -308,11 +309,22 @@ class DatasetWriterDirectoryQueue {
308309
}
309310

310311
Result<std::string> GetNextFilename() {
311-
auto basename = ::arrow::internal::Replace(write_options_.basename_template,
312-
kIntegerToken, ToChars(file_counter_++));
312+
std::optional<std::string> basename;
313+
if (write_options_.basename_template_functor == nullptr) {
314+
basename = ::arrow::internal::Replace(write_options_.basename_template,
315+
kIntegerToken, ToChars(file_counter_++));
316+
} else {
317+
basename = ::arrow::internal::Replace(
318+
write_options_.basename_template, kIntegerToken,
319+
write_options_.basename_template_functor(file_counter_++));
320+
}
313321
if (!basename) {
314322
return Status::Invalid("string interpolation of basename template failed");
315323
}
324+
if (!used_filenames_.insert(*basename).second) {
325+
return Status::Invalid("filename ", *basename,
326+
" is already used before. Check basename_template_functor");
327+
}
316328
return fs::internal::ConcatAbstractPath(directory_, prefix_ + *basename);
317329
}
318330

@@ -406,6 +418,7 @@ class DatasetWriterDirectoryQueue {
406418
latest_open_file_tasks_.reset();
407419
latest_open_file_ = nullptr;
408420
}
421+
used_filenames_.clear();
409422
return Status::OK();
410423
}
411424

@@ -418,6 +431,7 @@ class DatasetWriterDirectoryQueue {
418431
DatasetWriterState* writer_state_;
419432
Future<> init_future_;
420433
std::string current_filename_;
434+
std::unordered_set<std::string> used_filenames_;
421435
DatasetWriterFileQueue* latest_open_file_ = nullptr;
422436
std::unique_ptr<util::ThrottledAsyncTaskScheduler> latest_open_file_tasks_;
423437
uint64_t rows_written_ = 0;

cpp/src/arrow/dataset/dataset_writer_test.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,35 @@ TEST_F(DatasetWriterTestFixture, MaxRowsOneWrite) {
276276
{"testdir/chunk-3.arrow", 30, 5}});
277277
}
278278

279+
TEST_F(DatasetWriterTestFixture, MaxRowsOneWriteWithFunctor) {
280+
// Left padding with up to four zeros
281+
write_options_.max_rows_per_group = 10;
282+
write_options_.max_rows_per_file = 10;
283+
write_options_.basename_template_functor = [](int v) {
284+
size_t n_zero = 4;
285+
return std::string(n_zero - std::min(n_zero, std::to_string(v).length()), '0') +
286+
std::to_string(v);
287+
};
288+
auto dataset_writer = MakeDatasetWriter();
289+
dataset_writer->WriteRecordBatch(MakeBatch(25), "");
290+
EndWriterChecked(dataset_writer.get());
291+
AssertCreatedData({{"testdir/chunk-0000.arrow", 0, 10},
292+
{"testdir/chunk-0001.arrow", 10, 10},
293+
{"testdir/chunk-0002.arrow", 20, 5}});
294+
}
295+
296+
TEST_F(DatasetWriterTestFixture, MaxRowsOneWriteWithBrokenFunctor) {
297+
// Rewriting an exiting file will error out
298+
write_options_.max_rows_per_group = 10;
299+
write_options_.max_rows_per_file = 10;
300+
write_options_.basename_template_functor = [](int v) { return "SAME"; };
301+
auto dataset_writer = MakeDatasetWriter();
302+
dataset_writer->WriteRecordBatch(MakeBatch(25), "");
303+
dataset_writer->Finish();
304+
test_done_with_tasks_.MarkFinished();
305+
ASSERT_FINISHES_AND_RAISES(Invalid, scheduler_finished_);
306+
}
307+
279308
TEST_F(DatasetWriterTestFixture, MaxRowsManyWrites) {
280309
write_options_.max_rows_per_file = 10;
281310
write_options_.max_rows_per_group = 10;

cpp/src/arrow/dataset/file_base.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,12 @@ struct ARROW_DS_EXPORT FileSystemDatasetWriteOptions {
404404
/// {i} will be replaced by an auto incremented integer.
405405
std::string basename_template;
406406

407+
/// A functor which will be applied on an incremented counter. The result will be
408+
/// inserted into the basename_template in place of {i}.
409+
///
410+
/// This can be used, for example, to left-pad the file counter.
411+
std::function<std::string(int)> basename_template_functor;
412+
407413
/// If greater than 0 then this will limit the maximum number of files that can be left
408414
/// open. If an attempt is made to open too many files then the least recently used file
409415
/// will be closed. If this setting is set too low you may end up fragmenting your data

0 commit comments

Comments
 (0)