Skip to content

Commit 1939bfb

Browse files
committed
I/O fixes
Windows needs to a-priori know how to open a file (binary or text). If not, text-mode is assumed, which then will translate line endings `\n->\r\n`. This, in turn, will break the protobuf loader. This in turn means that windows can't rely on `loadPlan`s format detection, in case the underlying file is a binary file. This change adds adds a `forceBinary` flag to `loadPlan`, which must be used on Windows when providing binary files. `loadPlan` is used a few different places, and this doesn't pipe that argument in everywhere. Fixes have been added to an extent s.t., tests pass.
1 parent 040011b commit 1939bfb

File tree

6 files changed

+40
-22
lines changed

6 files changed

+40
-22
lines changed

include/substrait/common/Io.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ enum class PlanFileFormat {
3030
* amount of memory that it consumed on disk.
3131
*
3232
* \param input_filename The filename containing the plan to convert.
33+
* \param force_binary If true, the plan will be opened as a binary file.
34+
* Required on Windows to avoid text mode line-ending translation.
3335
* \return If loading was successful, returns a plan. If loading was not
3436
* successful this is a status containing a list of parse errors in the status's
3537
* message.
3638
*/
3739
absl::StatusOr<::substrait::proto::Plan> loadPlan(
38-
std::string_view input_filename);
40+
std::string_view input_filename,
41+
bool force_binary = false);
3942

4043
/*
4144
* \brief Writes the provided plan to disk.

src/substrait/common/Io.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ PlanFileFormat detectFormat(std::string_view content) {
3737
} // namespace
3838

3939
absl::StatusOr<::substrait::proto::Plan> loadPlan(
40-
std::string_view input_filename) {
41-
auto contentOrError = textplan::readFromFile(input_filename.data());
40+
std::string_view input_filename,
41+
bool forceBinary) {
42+
auto contentOrError =
43+
textplan::readFromFile(input_filename.data(), forceBinary);
4244
if (!contentOrError.ok()) {
4345
return contentOrError.status();
4446
}

src/substrait/common/tests/IoTest.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ class SaveAndLoadTestFixture : public ::testing::TestWithParam<PlanFileFormat> {
5252
std::filesystem::path("my_temp_dir"))
5353
.string();
5454

55-
if (!std::filesystem::create_directory(testFileDirectory_)) {
55+
std::filesystem::create_directory(testFileDirectory_);
56+
if (!std::filesystem::exists(testFileDirectory_)) {
5657
ASSERT_TRUE(false) << "Failed to create temporary directory.";
5758
testFileDirectory_.clear();
5859
}
@@ -87,7 +88,15 @@ TEST_P(SaveAndLoadTestFixture, SaveAndLoad) {
8788
auto status = ::io::substrait::savePlan(plan, tempFilename, encoding);
8889
ASSERT_TRUE(status.ok()) << "Save failed.\n" << status;
8990

91+
#ifdef _WIN32
92+
// Windows cannot rely on io::substrait::loadPlan to detect the file format,
93+
// since it needs to a-priori specify how the file should be loaded.
94+
bool forceBinary = encoding == PlanFileFormat::kBinary;
95+
auto result = ::io::substrait::loadPlan(tempFilename, forceBinary);
96+
#else
9097
auto result = ::io::substrait::loadPlan(tempFilename);
98+
#endif
99+
91100
ASSERT_TRUE(result.ok()) << "Load failed.\n" << result.status();
92101
ASSERT_THAT(
93102
*result,

src/substrait/textplan/converter/LoadBinary.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,24 @@ class StringErrorCollector : public google::protobuf::io::ErrorCollector {
4242

4343
} // namespace
4444

45-
absl::StatusOr<std::string> readFromFile(std::string_view msgPath) {
46-
std::ifstream textFile(std::string{msgPath});
47-
if (textFile.fail()) {
45+
absl::StatusOr<std::string> readFromFile(
46+
std::string_view msgPath,
47+
bool forceBinary) {
48+
std::ifstream file;
49+
if (forceBinary)
50+
file.open(std::string{msgPath}, std::ios::binary);
51+
else
52+
file.open(std::string{msgPath}, std::ios::in);
53+
54+
if (file.fail()) {
4855
auto currDir = std::filesystem::current_path().string();
4956
return absl::ErrnoToStatus(
5057
errno,
5158
fmt::format(
5259
"Failed to open file {} when running in {}", msgPath, currDir));
5360
}
5461
std::stringstream buffer;
55-
buffer << textFile.rdbuf();
62+
buffer << file.rdbuf();
5663
return buffer.str();
5764
}
5865

src/substrait/textplan/converter/LoadBinary.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ class Plan;
1212

1313
namespace io::substrait::textplan {
1414

15-
// Read the contents of a file from disk.
16-
absl::StatusOr<std::string> readFromFile(std::string_view msgPath);
15+
// Read the contents of a file from disk. 'forceBinary' enables file reading in
16+
// binary mode.
17+
absl::StatusOr<std::string> readFromFile(
18+
std::string_view msgPath,
19+
bool forceBinary = false);
1720

1821
// Reads a plan from a json-encoded text proto.
1922
// Returns a list of errors if the file cannot be parsed.

src/substrait/textplan/converter/SaveBinary.cpp

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,18 @@ namespace io::substrait::textplan {
2727
absl::Status savePlanToBinary(
2828
const ::substrait::proto::Plan& plan,
2929
std::string_view output_filename) {
30-
int outputFileDescriptor =
31-
creat(std::string{output_filename}.c_str(), S_IREAD | S_IWRITE);
32-
if (outputFileDescriptor == -1) {
33-
return absl::ErrnoToStatus(
34-
errno,
30+
// Open file in binary mode and get its file descriptor
31+
std::ofstream of(std::string{output_filename}, std::ios::binary);
32+
if (!of) {
33+
return absl::InternalError(
3534
fmt::format("Failed to open file {} for writing", output_filename));
3635
}
37-
auto stream =
38-
new google::protobuf::io::FileOutputStream(outputFileDescriptor);
3936

40-
if (!plan.SerializeToZeroCopyStream(stream)) {
37+
if (!plan.SerializeToOstream(&of)) {
4138
return ::absl::UnknownError("Failed to write plan to stream.");
4239
}
4340

44-
if (!stream->Close()) {
45-
return absl::AbortedError("Failed to close file descriptor.");
46-
}
47-
delete stream;
41+
of.close();
4842
return absl::OkStatus();
4943
}
5044

0 commit comments

Comments
 (0)