1111#include < arrow/util/config.h>
1212#include < parquet/arrow/writer.h>
1313
14+ #include < algorithm>
15+ #include < cctype>
16+ #include < exception>
1417#include < memory>
1518#include < optional>
1619#include < unordered_map>
@@ -35,27 +38,32 @@ using FileWriterFactory =
3538
3639::arrow::Compression::type resolveCompression ( const std::string & name )
3740{
38- static const std::unordered_map<std::string, ::arrow::Compression::type> mapping{
39- { " " , ::arrow::Compression::UNCOMPRESSED },
40- { " none" , ::arrow::Compression::UNCOMPRESSED },
41- { " snappy" , ::arrow::Compression::SNAPPY },
42- { " gzip" , ::arrow::Compression::GZIP },
43- { " brotli" , ::arrow::Compression::BROTLI },
44- { " zstd" , ::arrow::Compression::ZSTD },
45- { " lz4" , ::arrow::Compression::LZ4 },
46- { " lz4_frame" , ::arrow::Compression::LZ4_FRAME },
47- };
48- auto it = mapping.find ( name );
49- CSP_TRUE_OR_THROW_RUNTIME ( it != mapping.end (), " Unable to resolve compression: " << name );
50- return it -> second;
41+ // csp treats "" / "none" as no compression; Arrow expects lower-case codec names.
42+ std::string lower ( name );
43+ std::transform ( lower.begin (), lower.end (), lower.begin (),
44+ []( unsigned char c ) { return static_cast <char >( std::tolower ( c ) ); } );
45+ if ( lower.empty () || lower == " none" )
46+ return ::arrow::Compression::UNCOMPRESSED ;
47+
48+ // Arrow is the source of truth for which compression names exist...
49+ auto compressionType = ::arrow::util::Codec::GetCompressionType ( lower );
50+ CSP_TRUE_OR_THROW_RUNTIME ( compressionType.ok (), " Unsupported compression '" << name << " '" );
51+ // ...and whether this Arrow build was actually compiled with support for it.
52+ CSP_TRUE_OR_THROW_RUNTIME ( ::arrow::util::Codec::IsAvailable ( compressionType.ValueUnsafe () ),
53+ " Compression '" << name << " ' is not available in this Arrow build" );
54+ return compressionType.ValueUnsafe ();
5155}
5256
5357// Enforce overwrite policy, create parent dirs, and open the output stream.
5458std::shared_ptr<::arrow::io::OutputStream> openOutputStream ( const std::string & path, bool allowOverwrite )
5559{
5660 if ( !allowOverwrite && utils::fileExists ( path ) )
5761 CSP_THROW ( FileExistsError, " Trying to overwrite existing file " << path << " while allow_overwrite is false" );
58- utils::mkdir ( utils::dirname ( path ) );
62+ // Only create the parent directory when the path has one; a bare relative
63+ // filename (e.g. "out.parquet") has an empty dirname, and mkdir("") fails.
64+ auto parentDir = utils::dirname ( path );
65+ if ( !parentDir.empty () )
66+ utils::mkdir ( parentDir );
5967
6068 auto result = ::arrow::io::FileOutputStream::Open ( path );
6169 STATUS_OK_OR_THROW_RUNTIME ( result.status (), " Failed to open output stream " << path );
@@ -94,8 +102,10 @@ FileWriter makeParquetFileWriter( const std::string & path, const std::shared_pt
94102 },
95103 [writer, stream]()
96104 {
97- STATUS_OK_OR_THROW_RUNTIME ( writer -> Close (), " Failed to close parquet writer" );
98- STATUS_OK_OR_THROW_RUNTIME ( stream -> Close (), " Failed to close parquet output stream" );
105+ auto writerStatus = writer -> Close ();
106+ auto streamStatus = stream -> Close (); // always close the stream, even if the footer flush failed
107+ STATUS_OK_OR_THROW_RUNTIME ( writerStatus, " Failed to close parquet writer" );
108+ STATUS_OK_OR_THROW_RUNTIME ( streamStatus, " Failed to close parquet output stream" );
99109 } };
100110}
101111
@@ -124,8 +134,10 @@ FileWriter makeIpcFileWriter( const std::string & path, const std::shared_ptr<::
124134 },
125135 [writer, stream]()
126136 {
127- STATUS_OK_OR_THROW_RUNTIME ( writer -> Close (), " Failed to close arrow IPC writer" );
128- STATUS_OK_OR_THROW_RUNTIME ( stream -> Close (), " Failed to close arrow output stream" );
137+ auto writerStatus = writer -> Close ();
138+ auto streamStatus = stream -> Close (); // always close the stream, even if the writer close failed
139+ STATUS_OK_OR_THROW_RUNTIME ( writerStatus, " Failed to close arrow IPC writer" );
140+ STATUS_OK_OR_THROW_RUNTIME ( streamStatus, " Failed to close arrow output stream" );
129141 } };
130142}
131143
@@ -134,7 +146,8 @@ FileWriter makeIpcFileWriter( const std::string & path, const std::shared_ptr<::
134146FileWriter makeSplitWriter ( const std::string & dir, const std::shared_ptr<::arrow::Schema> & schema,
135147 const FileWriterFactory & perColumn, const std::string & extension )
136148{
137- utils::mkdir ( dir );
149+ if ( !dir.empty () )
150+ utils::mkdir ( dir );
138151
139152 auto subWriters = std::make_shared<std::vector<FileWriter>>();
140153 auto colSchemas = std::make_shared<std::vector<std::shared_ptr<::arrow::Schema>>>();
@@ -159,8 +172,14 @@ FileWriter makeSplitWriter( const std::string & dir, const std::shared_ptr<::arr
159172 },
160173 [subWriters]()
161174 {
175+ std::exception_ptr firstError;
162176 for ( auto & w : *subWriters )
163- w.close ();
177+ {
178+ try { w.close (); }
179+ catch ( ... ) { if ( !firstError ) firstError = std::current_exception (); }
180+ }
181+ if ( firstError )
182+ std::rethrow_exception ( firstError );
164183 } };
165184}
166185
@@ -197,10 +216,13 @@ RecordBatchSink makeFileSink( bool writeArrowBinary, bool splitColumns,
197216 {
198217 if ( current -> has_value () )
199218 {
200- ( *current ) -> close ();
219+ // Detach + reset BEFORE invoking the (user-supplied) visitor, so that a throwing
220+ // visitor cannot cause a later onStop()->closeCurrent() to close this writer twice.
221+ FileWriter w = std::move ( current -> value () );
222+ current -> reset ();
223+ w.close ();
201224 if ( fileVisitor )
202225 fileVisitor ( *currentPath );
203- current -> reset ();
204226 }
205227 };
206228
0 commit comments