Skip to content

Commit 4f95ec6

Browse files
committed
Parquet output: minor cleanups (scratch reset, batch guard, docs)
Encapsulate the scratch isSet reset behind StructField::clearIsSet, add a debug-only length assert in buildRecordBatch, and document that file_visitor runs synchronously on the engine thread. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
1 parent 52cdce7 commit 4f95ec6

5 files changed

Lines changed: 25 additions & 12 deletions

File tree

cpp/csp/adapters/parquet/ArrowBackedArrayBuilder.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ void ArrowBackedArrayBuilder::handleRowFinished()
114114
m_writer -> writeNext( m_scratch.get() );
115115

116116
// Clear the isSet bit so next cycle starts with null
117-
uint8_t * mask = reinterpret_cast<uint8_t *>( m_scratch.get() ) + m_field -> maskOffset();
118-
*mask &= ~m_field -> maskBitMask();
117+
m_field -> clearIsSet( m_scratch.get() );
119118
}
120119
else
121120
{

cpp/csp/adapters/parquet/ParquetWriter.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,13 @@ std::shared_ptr<::arrow::RecordBatch> ParquetWriter::buildRecordBatch()
238238
std::vector<std::shared_ptr<::arrow::Array>> columns;
239239
columns.reserve( m_columnBuilders.size() );
240240
for( auto && builder : m_columnBuilders )
241-
columns.push_back( builder -> buildArray() );
241+
{
242+
auto column = builder -> buildArray();
243+
// Every column must contain exactly the rows accumulated this chunk; a mismatch would build a
244+
// corrupt RecordBatch (debug-only guard, compiled out in release).
245+
CSP_ASSERT( column -> length() == static_cast<std::int64_t>( m_curChunkSize ) );
246+
columns.push_back( std::move( column ) );
247+
}
242248
return ::arrow::RecordBatch::Make( m_schema, m_curChunkSize, std::move( columns ) );
243249
}
244250

cpp/csp/engine/Struct.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ class StructField
7777
(*m) |= m_noneMaskBitMask;
7878
}
7979

80+
//Marks this field "not set" without releasing/cleaning its current value. The same capability is
81+
//already reachable through the public maskOffset()/maskBitMask() accessors; this just encapsulates the
82+
//bit poke. Used by callers that reuse a struct as a per-row scratch buffer (e.g. the parquet
83+
//ArrowBackedArrayBuilder appends the scratch value, then clears the bit so the next row defaults to null).
84+
void clearIsSet( Struct * s ) const
85+
{
86+
uint8_t * m = reinterpret_cast<uint8_t *>( s ) + m_maskOffset;
87+
(*m) &= ~m_maskBitMask;
88+
}
89+
8090
//copy methods need not deal with mask set/unset, only copy values
8191
virtual void copyFrom( const Struct * src, Struct * dest ) const = 0;
8292

@@ -121,12 +131,6 @@ class StructField
121131
return reinterpret_cast<uint8_t *>( s ) + m_offset;
122132
}
123133

124-
void clearIsSet( Struct * s ) const
125-
{
126-
uint8_t * m = reinterpret_cast<uint8_t *>( s ) + m_maskOffset;
127-
(*m) &= ~m_maskBitMask;
128-
}
129-
130134
void clearIsNone( Struct * s ) const
131135
{
132136
uint8_t * m = reinterpret_cast<uint8_t *>( s ) + m_maskOffset;

cpp/csp/python/adapters/parquetadapterimpl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,8 +686,9 @@ csp::AdapterManager *create_parquet_output_adapter_manager( PyEngine *engine, co
686686
bool allowOverwrite = properties.get<bool>( "allow_overwrite" );
687687
std::string compression = properties.get<std::string>( "compression" );
688688

689-
// Optional Python file_visitor: wrapped as a plain C++ callback, invoked (with the GIL
690-
// held during the run) after each file is closed. Only the main sink visits files.
689+
// Optional Python file_visitor: wrapped as a plain C++ callback, invoked synchronously (with the
690+
// GIL held during the run) after each file is closed, so a slow visitor blocks the engine. Only
691+
// the main sink visits files.
691692
std::function<void( const std::string & )> fileVisitor;
692693
DialectGenericType visitorDG;
693694
if( properties.tryGet( "file_visitor", visitorDG ) )

csp/adapters/output_adapters/parquet.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ def __init__(
7373
file_name must be a folder into which the data will be written.
7474
:param file_metadata: optional str:str dict that will get written as file-level metadata
7575
:param column_metadata: optional dict of column : { str:str} that will get written as column-level metadata
76-
:param file_visitor: optional callable that will be called, after a file is written, with the file name.
76+
:param file_visitor: optional callable invoked with the file path after each file is closed (on rotation
77+
and at shutdown). It runs synchronously on the engine thread with the GIL held, so a slow visitor (e.g.
78+
uploading to remote storage) blocks the engine for its duration -- offload heavy work to a background
79+
queue/thread if that matters. An exception raised by the visitor propagates out of csp.run.
7780
"""
7881
super().__init__()
7982
config = ParquetOutputConfig() if config is None else config.copy()

0 commit comments

Comments
 (0)