@@ -34,6 +34,38 @@ namespace numpy
3434#define ARROW_OK_OR_THROW_WRITER ( expr, msg ) \
3535 do { auto __s = ( expr ); if ( !__s.ok () ) CSP_THROW ( RuntimeException, msg << " : " << __s.ToString () ); } while (0 )
3636
37+ // --- Base class for list writers that share reserve/writeNull/finish on a single ListBuilder ---
38+
39+ class ListFieldWriterBase : public csp ::adapters::arrow::FieldWriter
40+ {
41+ public:
42+ ListFieldWriterBase ( std::vector<std::string> columnNames,
43+ std::vector<std::shared_ptr<::arrow::DataType>> dataTypes,
44+ const StructFieldPtr & structField )
45+ : FieldWriter( std::move( columnNames ), std::move( dataTypes ), structField )
46+ {}
47+
48+ void reserve ( int64_t numRows ) override
49+ {
50+ ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Reserve ( numRows ), " Failed to reserve list builder" );
51+ }
52+
53+ void writeNull () override
54+ {
55+ ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> AppendNull (), " Failed to append null list" );
56+ }
57+
58+ std::vector<std::shared_ptr<::arrow::Array>> finish () override
59+ {
60+ std::shared_ptr<::arrow::Array> arr;
61+ ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Finish ( &arr ), " Failed to finish list array" );
62+ return { arr };
63+ }
64+
65+ protected:
66+ std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
67+ };
68+
3769// --- Native element writers (double, int64, bool) ---
3870// Use bulk AppendValues for a single resize + copy instead of per-element Append.
3971
@@ -58,34 +90,17 @@ inline void writeNativeElements<bool, ::arrow::BooleanBuilder>(
5890// --- Native list writer ---
5991
6092template <typename CspT, typename ArrowBuilderT>
61- class NativeListWriter final : public csp::adapters::arrow::FieldWriter
93+ class NativeListWriter final : public ListFieldWriterBase
6294{
6395public:
6496 NativeListWriter ( const std::string & columnName, const StructFieldPtr & structField,
6597 std::shared_ptr<::arrow::DataType> elementType )
66- : FieldWriter ( { columnName }, { ::arrow::list ( elementType ) }, structField )
98+ : ListFieldWriterBase ( { columnName }, { ::arrow::list ( elementType ) }, structField )
6799 {
68100 m_valueBuilder = std::make_shared<ArrowBuilderT>();
69101 m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool (), m_valueBuilder );
70102 }
71103
72- void reserve ( int64_t numRows ) override
73- {
74- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Reserve ( numRows ), " Failed to reserve list builder" );
75- }
76-
77- void writeNull () override
78- {
79- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> AppendNull (), " Failed to append null list" );
80- }
81-
82- std::vector<std::shared_ptr<::arrow::Array>> finish () override
83- {
84- std::shared_ptr<::arrow::Array> arr;
85- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Finish ( &arr ), " Failed to finish list array" );
86- return { arr };
87- }
88-
89104protected:
90105 void doWrite ( const Struct * s ) override
91106 {
@@ -102,47 +117,27 @@ class NativeListWriter final : public csp::adapters::arrow::FieldWriter
102117 }
103118 else
104119 {
105- PyObjectPtr contiguousOwner{ PyObjectPtr::own (
106- reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) ) };
107- writeNativeElements<CspT, ArrowBuilderT>( m_valueBuilder.get (),
108- reinterpret_cast <PyArrayObject *>( contiguousOwner.get () ), len );
120+ PyObjectPtr contiguousOwner{ PyObjectPtr::own ( reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) ) };
121+ writeNativeElements<CspT, ArrowBuilderT>( m_valueBuilder.get (), reinterpret_cast <PyArrayObject *>( contiguousOwner.get () ), len );
109122 }
110123 }
111124
112125private:
113126 std::shared_ptr<ArrowBuilderT> m_valueBuilder;
114- std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
115127};
116128
117129// --- String list writer ---
118130
119- class StringListWriter final : public csp::adapters::arrow::FieldWriter
131+ class StringListWriter final : public ListFieldWriterBase
120132{
121133public:
122134 StringListWriter ( const std::string & columnName, const StructFieldPtr & structField )
123- : FieldWriter ( { columnName }, { ::arrow::list ( ::arrow::utf8 () ) }, structField )
135+ : ListFieldWriterBase ( { columnName }, { ::arrow::list ( ::arrow::utf8 () ) }, structField )
124136 {
125137 m_valueBuilder = std::make_shared<::arrow::StringBuilder>();
126138 m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool (), m_valueBuilder );
127139 }
128140
129- void reserve ( int64_t numRows ) override
130- {
131- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Reserve ( numRows ), " Failed to reserve string list builder" );
132- }
133-
134- void writeNull () override
135- {
136- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> AppendNull (), " Failed to append null list" );
137- }
138-
139- std::vector<std::shared_ptr<::arrow::Array>> finish () override
140- {
141- std::shared_ptr<::arrow::Array> arr;
142- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Finish ( &arr ), " Failed to finish string list array" );
143- return { arr };
144- }
145-
146141protected:
147142 void doWrite ( const Struct * s ) override
148143 {
@@ -181,7 +176,6 @@ class StringListWriter final : public csp::adapters::arrow::FieldWriter
181176
182177private:
183178 std::shared_ptr<::arrow::StringBuilder> m_valueBuilder;
184- std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
185179 std::wstring_convert<std::codecvt_utf8<char32_t >, char32_t > m_converter; // reused across rows
186180 std::string m_utf8Buf; // reused buffer
187181};
@@ -191,34 +185,17 @@ class StringListWriter final : public csp::adapters::arrow::FieldWriter
191185// Writes numpy datetime64/timedelta64 arrays into Arrow List<Timestamp(ns,"UTC")> or List<Duration(ns)> columns.
192186// Detects the numpy datetime unit at write time and converts to nanoseconds using scalingFromNumpyDtUnit().
193187template <typename ArrowBuilderT>
194- class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
188+ class TemporalListWriter final : public ListFieldWriterBase
195189{
196190public:
197191 TemporalListWriter ( const std::string & columnName, const StructFieldPtr & structField,
198192 std::shared_ptr<::arrow::DataType> elementType )
199- : FieldWriter ( { columnName }, { ::arrow::list ( elementType ) }, structField )
193+ : ListFieldWriterBase ( { columnName }, { ::arrow::list ( elementType ) }, structField )
200194 {
201195 m_valueBuilder = std::make_shared<ArrowBuilderT>( elementType, ::arrow::default_memory_pool () );
202196 m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool (), m_valueBuilder );
203197 }
204198
205- void reserve ( int64_t numRows ) override
206- {
207- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Reserve ( numRows ), " Failed to reserve temporal list builder" );
208- }
209-
210- void writeNull () override
211- {
212- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> AppendNull (), " Failed to append null temporal list" );
213- }
214-
215- std::vector<std::shared_ptr<::arrow::Array>> finish () override
216- {
217- std::shared_ptr<::arrow::Array> arr;
218- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Finish ( &arr ), " Failed to finish temporal list array" );
219- return { arr };
220- }
221-
222199protected:
223200 void doWrite ( const Struct * s ) override
224201 {
@@ -233,8 +210,7 @@ class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
233210 PyObjectPtr contiguousOwner;
234211 if ( !PyArray_IS_C_CONTIGUOUS ( pyArr ) )
235212 {
236- contiguousOwner = PyObjectPtr::own (
237- reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) );
213+ contiguousOwner = PyObjectPtr::own ( reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) );
238214 contiguousArr = reinterpret_cast <PyArrayObject *>( contiguousOwner.get () );
239215 }
240216
@@ -244,9 +220,7 @@ class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
244220 auto unit = datetimeUnitFromDescr ( PyArray_DESCR ( pyArr ) );
245221 int64_t scaling = scalingFromNumpyDtUnit ( unit );
246222
247- ARROW_OK_OR_THROW_WRITER (
248- m_valueBuilder -> Reserve ( static_cast <int64_t >( len ) ),
249- " Failed to reserve temporal value builder" );
223+ ARROW_OK_OR_THROW_WRITER ( m_valueBuilder -> Reserve ( static_cast <int64_t >( len ) ), " Failed to reserve temporal value builder" );
250224 for ( npy_intp i = 0 ; i < len; ++i )
251225 {
252226 if ( data[i] == NPY_DATETIME_NAT )
@@ -258,37 +232,19 @@ class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
258232
259233private:
260234 std::shared_ptr<ArrowBuilderT> m_valueBuilder;
261- std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
262235};
263236
264237// Date writer: numpy datetime64 → Arrow List<Date32> (nanoseconds → days since epoch)
265- class DateListWriter final : public csp::adapters::arrow::FieldWriter
238+ class DateListWriter final : public ListFieldWriterBase
266239{
267240public:
268241 DateListWriter ( const std::string & columnName, const StructFieldPtr & structField )
269- : FieldWriter ( { columnName }, { ::arrow::list ( ::arrow::date32 () ) }, structField )
242+ : ListFieldWriterBase ( { columnName }, { ::arrow::list ( ::arrow::date32 () ) }, structField )
270243 {
271244 m_valueBuilder = std::make_shared<::arrow::Date32Builder>();
272245 m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool (), m_valueBuilder );
273246 }
274247
275- void reserve ( int64_t numRows ) override
276- {
277- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Reserve ( numRows ), " Failed to reserve date list builder" );
278- }
279-
280- void writeNull () override
281- {
282- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> AppendNull (), " Failed to append null date list" );
283- }
284-
285- std::vector<std::shared_ptr<::arrow::Array>> finish () override
286- {
287- std::shared_ptr<::arrow::Array> arr;
288- ARROW_OK_OR_THROW_WRITER ( m_listBuilder -> Finish ( &arr ), " Failed to finish date list array" );
289- return { arr };
290- }
291-
292248protected:
293249 void doWrite ( const Struct * s ) override
294250 {
@@ -302,8 +258,7 @@ class DateListWriter final : public csp::adapters::arrow::FieldWriter
302258 PyObjectPtr contiguousOwner;
303259 if ( !PyArray_IS_C_CONTIGUOUS ( pyArr ) )
304260 {
305- contiguousOwner = PyObjectPtr::own (
306- reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) );
261+ contiguousOwner = PyObjectPtr::own ( reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) );
307262 contiguousArr = reinterpret_cast <PyArrayObject *>( contiguousOwner.get () );
308263 }
309264
@@ -313,9 +268,7 @@ class DateListWriter final : public csp::adapters::arrow::FieldWriter
313268 auto unit = datetimeUnitFromDescr ( PyArray_DESCR ( pyArr ) );
314269 int64_t scaling = scalingFromNumpyDtUnit ( unit );
315270
316- ARROW_OK_OR_THROW_WRITER (
317- m_valueBuilder -> Reserve ( static_cast <int64_t >( len ) ),
318- " Failed to reserve date value builder" );
271+ ARROW_OK_OR_THROW_WRITER ( m_valueBuilder -> Reserve ( static_cast <int64_t >( len ) ), " Failed to reserve date value builder" );
319272 for ( npy_intp i = 0 ; i < len; ++i )
320273 {
321274 if ( data[i] == NPY_DATETIME_NAT )
@@ -327,7 +280,6 @@ class DateListWriter final : public csp::adapters::arrow::FieldWriter
327280
328281private:
329282 std::shared_ptr<::arrow::Date32Builder> m_valueBuilder;
330- std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
331283};
332284
333285// --- Dispatch by npy_type ---
@@ -409,21 +361,30 @@ class NumpyNDArrayWriter final : public csp::adapters::arrow::FieldWriter
409361 // We call writeNext on the inner data writer which will check isSet and delegate to its doWrite
410362 m_dataWriter -> writeNext ( s );
411363
412- // Write shape/dims
364+ // Write shape/dims — use null when shape is unchanged from previous row
413365 auto & dgt = m_field -> value<DialectGenericType>( s );
414366 auto shape = m_shapeCallback ( dgt );
415367
416- ARROW_OK_OR_THROW_WRITER ( m_dimsListBuilder -> Append (), " Failed to start dims list" );
417- ARROW_OK_OR_THROW_WRITER (
418- m_dimsValueBuilder -> AppendValues ( shape.data (), static_cast <int64_t >( shape.size () ) ),
419- " Failed to append dim values" );
368+ if ( shape == m_lastShape )
369+ {
370+ ARROW_OK_OR_THROW_WRITER ( m_dimsListBuilder -> AppendNull (), " Failed to append null dims" );
371+ }
372+ else
373+ {
374+ ARROW_OK_OR_THROW_WRITER ( m_dimsListBuilder -> Append (), " Failed to start dims list" );
375+ ARROW_OK_OR_THROW_WRITER (
376+ m_dimsValueBuilder -> AppendValues ( shape.data (), static_cast <int64_t >( shape.size () ) ),
377+ " Failed to append dim values" );
378+ m_lastShape = std::move ( shape );
379+ }
420380 }
421381
422382private:
423383 ShapeCallback m_shapeCallback;
424384 std::unique_ptr<csp::adapters::arrow::FieldWriter> m_dataWriter;
425385 std::shared_ptr<::arrow::Int64Builder> m_dimsValueBuilder;
426386 std::shared_ptr<::arrow::ListBuilder> m_dimsListBuilder;
387+ std::vector<int64_t > m_lastShape;
427388};
428389
429390#undef ARROW_OK_OR_THROW_WRITER
@@ -432,6 +393,9 @@ class NumpyNDArrayWriter final : public csp::adapters::arrow::FieldWriter
432393// These write all elements of a numpy array into an arrow value builder
433394// (list start/end is handled by the caller, e.g. ListColumnArrayBuilder).
434395
396+ template <typename T> struct ArrowArrayType { using type = T; };
397+ template <> struct ArrowArrayType <bool > { using type = uint8_t ; };
398+
435399template <typename CspT, typename ArrowBuilderT>
436400inline csp::adapters::arrow::ListItemsWriter makeNativeListItemsWriter (
437401 std::shared_ptr<ArrowBuilderT> valueBuilder )
@@ -443,48 +407,24 @@ inline csp::adapters::arrow::ListItemsWriter makeNativeListItemsWriter(
443407
444408 if ( PyArray_IS_C_CONTIGUOUS ( pyArr ) )
445409 {
446- auto * data = reinterpret_cast <const typename ArrowBuilderT::value_type *>( PyArray_DATA ( pyArr ) );
410+ using CastT = typename ArrowArrayType<typename ArrowBuilderT::value_type>::type;
411+ auto * data = reinterpret_cast <const CastT *>( PyArray_DATA ( pyArr ) );
447412 auto s = valueBuilder -> AppendValues ( data, static_cast <int64_t >( len ) );
448413 CSP_TRUE_OR_THROW_RUNTIME ( s.ok (), " Failed to append list elements: " << s.ToString () );
449414 }
450415 else
451416 {
417+ using CastT = typename ArrowArrayType<typename ArrowBuilderT::value_type>::type;
452418 PyObjectPtr contiguousOwner{ PyObjectPtr::own (
453419 reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) ) };
454- auto * data = reinterpret_cast <const typename ArrowBuilderT::value_type *>(
420+ auto * data = reinterpret_cast <const CastT *>(
455421 PyArray_DATA ( reinterpret_cast <PyArrayObject *>( contiguousOwner.get () ) ) );
456422 auto s = valueBuilder -> AppendValues ( data, static_cast <int64_t >( len ) );
457423 CSP_TRUE_OR_THROW_RUNTIME ( s.ok (), " Failed to append list elements: " << s.ToString () );
458424 }
459425 };
460426}
461427
462- inline csp::adapters::arrow::ListItemsWriter makeBoolListItemsWriter (
463- std::shared_ptr<::arrow::BooleanBuilder> valueBuilder )
464- {
465- return [valueBuilder]( const DialectGenericType & dgt )
466- {
467- auto * pyArr = reinterpret_cast <PyArrayObject *>( csp::python::toPythonBorrowed ( dgt ) );
468- npy_intp len = PyArray_SIZE ( pyArr );
469-
470- if ( PyArray_IS_C_CONTIGUOUS ( pyArr ) )
471- {
472- auto * data = reinterpret_cast <const uint8_t *>( PyArray_DATA ( pyArr ) );
473- auto s = valueBuilder -> AppendValues ( data, static_cast <int64_t >( len ) );
474- CSP_TRUE_OR_THROW_RUNTIME ( s.ok (), " Failed to append bool list elements: " << s.ToString () );
475- }
476- else
477- {
478- PyObjectPtr contiguousOwner{ PyObjectPtr::own (
479- reinterpret_cast <PyObject *>( PyArray_GETCONTIGUOUS ( pyArr ) ) ) };
480- auto * data = reinterpret_cast <const uint8_t *>(
481- PyArray_DATA ( reinterpret_cast <PyArrayObject *>( contiguousOwner.get () ) ) );
482- auto s = valueBuilder -> AppendValues ( data, static_cast <int64_t >( len ) );
483- CSP_TRUE_OR_THROW_RUNTIME ( s.ok (), " Failed to append bool list elements: " << s.ToString () );
484- }
485- };
486- }
487-
488428inline csp::adapters::arrow::ListItemsWriter makeStringListItemsWriter ( std::shared_ptr<::arrow::StringBuilder> valueBuilder )
489429{
490430 return [valueBuilder]( const DialectGenericType & dgt )
@@ -568,7 +508,7 @@ inline void registerNumpyListFieldWriterFactory()
568508 case CspType::Type::BOOL :
569509 {
570510 auto b = std::make_shared<::arrow::BooleanBuilder>();
571- return { b, numpy::makeBoolListItemsWriter ( b ) };
511+ return { b, numpy::makeNativeListItemsWriter< bool , ::arrow::BooleanBuilder> ( b ) };
572512 }
573513 case CspType::Type::STRING :
574514 {
0 commit comments