@@ -24,7 +24,8 @@ class RecordBatchIterator
2424{
2525public:
2626 RecordBatchIterator () {}
27- RecordBatchIterator ( PyObjectPtr iter, std::shared_ptr<::arrow::Schema> schema ): m_iter( std::move( iter ) ), m_schema( schema )
27+
28+ RecordBatchIterator ( PyObjectPtr iter, std::shared_ptr<::arrow::Schema> schema ): m_iter( std::move( iter ) ), m_schema( std::move( schema ) )
2829 {
2930 }
3031
@@ -36,7 +37,7 @@ class RecordBatchIterator
3637
3738 if ( py_tuple.get () == NULL )
3839 {
39- // No more data in the input steam
40+ // No more data in the input stream
4041 return nullptr ;
4142 }
4243
@@ -47,6 +48,20 @@ class RecordBatchIterator
4748 if ( num_elems != 2 )
4849 CSP_THROW ( csp::TypeError, " Invalid arrow data, expected tuple (using the PyCapsule C interface) with 2 elements got " << num_elems );
4950
51+ // Extract schema from first batch if not provided upfront
52+ if ( !m_schema )
53+ {
54+ PyObject * py_schema = PyTuple_GetItem ( py_tuple.get (), 0 );
55+ if ( !PyCapsule_IsValid ( py_schema, " arrow_schema" ) )
56+ CSP_THROW ( csp::TypeError, " Invalid arrow data, expected schema capsule from the PyCapsule C interface" );
57+
58+ ArrowSchema * c_schema = reinterpret_cast <ArrowSchema*>( PyCapsule_GetPointer ( py_schema, " arrow_schema" ) );
59+ auto schema_result = ::arrow::ImportSchema ( c_schema );
60+ if ( !schema_result.ok () )
61+ CSP_THROW ( ValueError, " Failed to load schema through PyCapsule C Data interface: " << schema_result.status ().ToString () );
62+ m_schema = std::move ( schema_result.ValueUnsafe () );
63+ }
64+
5065 // Extract the record batch
5166 PyObject * py_array = PyTuple_GetItem ( py_tuple.get (), 1 );
5267 if ( !PyCapsule_IsValid ( py_array, " arrow_array" ) )
@@ -60,20 +75,22 @@ class RecordBatchIterator
6075 return result.ValueUnsafe ();
6176 }
6277
78+ std::shared_ptr<::arrow::Schema> schema () const { return m_schema; }
79+
6380private:
6481 PyObjectPtr m_iter;
6582 std::shared_ptr<::arrow::Schema> m_schema;
6683};
6784
68- void ReleaseArrowSchemaPyCapsule ( PyObject * capsule )
85+ inline void ReleaseArrowSchemaPyCapsule ( PyObject * capsule )
6986{
7087 ArrowSchema * schema = reinterpret_cast <ArrowSchema*>( PyCapsule_GetPointer ( capsule, " arrow_schema" ) );
7188 if ( schema -> release != NULL )
7289 schema -> release ( schema );
7390 free ( schema );
7491}
7592
76- void ReleaseArrowArrayPyCapsule ( PyObject * capsule )
93+ inline void ReleaseArrowArrayPyCapsule ( PyObject * capsule )
7794{
7895 ArrowArray * array = reinterpret_cast <ArrowArray*>( PyCapsule_GetPointer ( capsule, " arrow_array" ) );
7996 if ( array -> release != NULL )
@@ -88,36 +105,33 @@ class RecordBatchInputAdapter: public PullInputAdapter<std::vector<DialectGeneri
88105 : PullInputAdapter<std::vector<DialectGenericType>>( engine, type, PushMode::LAST_VALUE ),
89106 m_tsColName ( tsColName ),
90107 m_expectSmallBatches ( expectSmallBatches ),
91- m_finished ( false )
108+ m_finished ( false ),
109+ m_initialized ( false ),
110+ m_multiplier ( 0 ),
111+ m_arrayLastTime ( 0 ),
112+ m_numRows ( 0 ),
113+ m_startTime ( 0 ),
114+ m_endTime ( 0 ),
115+ m_curBatchIdx ( 0 )
92116 {
93- // Extract the arrow schema
94- ArrowSchema * c_schema = reinterpret_cast <ArrowSchema*>( PyCapsule_GetPointer ( pySchema.get (), " arrow_schema" ) );
95- auto result = ::arrow::ImportSchema ( c_schema );
96- if ( !result.ok () )
97- CSP_THROW ( ValueError, " Failed to load schema for record batches through the PyCapsule C Data interface: " << result.status ().ToString () );
98- m_schema = std::move ( result.ValueUnsafe () );
117+ // Check if schema is provided or should be deferred to first batch
118+ if ( pySchema.get () != Py_None )
119+ {
120+ // Extract the arrow schema upfront
121+ ArrowSchema * c_schema = reinterpret_cast <ArrowSchema*>( PyCapsule_GetPointer ( pySchema.get (), " arrow_schema" ) );
122+ auto result = ::arrow::ImportSchema ( c_schema );
123+ if ( !result.ok () )
124+ CSP_THROW ( ValueError, " Failed to load schema for record batches through the PyCapsule C Data interface: " << result.status ().ToString () );
99125
100- auto tsField = m_schema -> GetFieldByName ( m_tsColName );
101- auto timestampType = std::static_pointer_cast<::arrow::TimestampType>( tsField -> type () );
102- switch ( timestampType -> unit () )
126+ m_source = RecordBatchIterator ( source, std::move ( result.ValueUnsafe () ) );
127+ initializeTimestampMultiplier ();
128+ m_initialized = true ;
129+ }
130+ else
103131 {
104- case ::arrow::TimeUnit::SECOND :
105- m_multiplier = csp::NANOS_PER_SECOND ;
106- break ;
107- case ::arrow::TimeUnit::MILLI :
108- m_multiplier = csp::NANOS_PER_MILLISECOND ;
109- break ;
110- case ::arrow::TimeUnit::MICRO :
111- m_multiplier = csp::NANOS_PER_MICROSECOND ;
112- break ;
113- case ::arrow::TimeUnit::NANO :
114- m_multiplier = 1 ;
115- break ;
116- default :
117- CSP_THROW ( ValueError, " Unsupported unit type for arrow timestamp column" );
132+ // Schema will be extracted lazily from first batch
133+ m_source = RecordBatchIterator ( source, nullptr );
118134 }
119-
120- m_source = RecordBatchIterator ( source, m_schema );
121135 }
122136
123137 int64_t findFirstMatchingIndex ()
@@ -174,6 +188,16 @@ class RecordBatchInputAdapter: public PullInputAdapter<std::vector<DialectGeneri
174188 while ( ( rb = m_source.next () ) && ( rb -> num_rows () == 0 ) ) {}
175189 if ( rb )
176190 {
191+ // Lazy schema initialization on first batch (when schema was not provided upfront)
192+ if ( !m_initialized ) [[unlikely]]
193+ {
194+ if ( !m_source.schema () )
195+ CSP_THROW ( ValueError, " Failed to extract schema from first record batch" );
196+ initializeTimestampMultiplier ();
197+ computeTimeRange ();
198+ m_initialized = true ;
199+ }
200+
177201 auto array = rb -> GetColumnByName ( m_tsColName );
178202 if ( !array )
179203 CSP_THROW ( ValueError, " Failed to get timestamp column " << m_tsColName << " from record batch " << rb -> ToString () );
@@ -189,24 +213,10 @@ class RecordBatchInputAdapter: public PullInputAdapter<std::vector<DialectGeneri
189213
190214 void start ( DateTime start, DateTime end ) override
191215 {
192- // start and end as multiples of the unit in timestamp column
193- auto start_nanos = start.asNanoseconds ();
194- m_startTime = ( start_nanos % m_multiplier == 0 ) ? start_nanos / m_multiplier : start_nanos / m_multiplier + 1 ;
195- m_endTime = end.asNanoseconds () / m_multiplier;
216+ m_rawStartTime = start;
217+ m_rawEndTime = end;
196218
197- // Find the starting index where time >= start
198- while ( !m_finished )
199- {
200- updateStateFromNextRecordBatch ();
201- if ( !m_curRecordBatch )
202- {
203- m_finished = true ;
204- break ;
205- }
206- m_curBatchIdx = findFirstMatchingIndex ();
207- if ( m_curBatchIdx < m_numRows )
208- break ;
209- }
219+ initializeStartPosition ();
210220 PullInputAdapter<std::vector<DialectGenericType>>::start ( start, end );
211221 }
212222
@@ -215,6 +225,12 @@ class RecordBatchInputAdapter: public PullInputAdapter<std::vector<DialectGeneri
215225 ArrowSchema* rb_schema = ( ArrowSchema* )malloc ( sizeof ( ArrowSchema ) );
216226 ArrowArray* rb_array = ( ArrowArray* )malloc ( sizeof ( ArrowArray ) );
217227 ::arrow::Status st = ::arrow::ExportRecordBatch ( *rb, rb_array, rb_schema );
228+ if ( !st.ok () )
229+ {
230+ free ( rb_array );
231+ free ( rb_schema );
232+ CSP_THROW ( ValueError, " Failed to export record batch through C Data interface: " << st.ToString () );
233+ }
218234 auto py_schema = csp::python::PyObjectPtr::own ( PyCapsule_New ( rb_schema, " arrow_schema" , ReleaseArrowSchemaPyCapsule ) );
219235 auto py_array = csp::python::PyObjectPtr::own ( PyCapsule_New ( rb_array, " arrow_array" , ReleaseArrowArrayPyCapsule ) );
220236 auto py_tuple = csp::python::PyObjectPtr::own ( PyTuple_Pack ( 2 , py_schema.get (), py_array.get () ) );
@@ -269,17 +285,85 @@ class RecordBatchInputAdapter: public PullInputAdapter<std::vector<DialectGeneri
269285 }
270286
271287private:
288+ void initializeTimestampMultiplier ()
289+ {
290+ auto schema = m_source.schema ();
291+ auto tsField = schema -> GetFieldByName ( m_tsColName );
292+ if ( !tsField )
293+ CSP_THROW ( ValueError, " Timestamp column '" << m_tsColName << " ' not found in schema" );
294+
295+ if ( tsField -> type () -> id () != ::arrow::Type::TIMESTAMP )
296+ CSP_THROW ( ValueError, " Column '" << m_tsColName << " ' is not a valid timestamp column" );
297+
298+ auto timestampType = std::static_pointer_cast<::arrow::TimestampType>( tsField -> type () );
299+ switch ( timestampType -> unit () )
300+ {
301+ case ::arrow::TimeUnit::SECOND :
302+ m_multiplier = csp::NANOS_PER_SECOND ;
303+ break ;
304+ case ::arrow::TimeUnit::MILLI :
305+ m_multiplier = csp::NANOS_PER_MILLISECOND ;
306+ break ;
307+ case ::arrow::TimeUnit::MICRO :
308+ m_multiplier = csp::NANOS_PER_MICROSECOND ;
309+ break ;
310+ case ::arrow::TimeUnit::NANO :
311+ m_multiplier = 1 ;
312+ break ;
313+ default :
314+ CSP_THROW ( ValueError, " Unsupported unit type for arrow timestamp column" );
315+ }
316+ }
317+
318+ void computeTimeRange ()
319+ {
320+ auto start_nanos = m_rawStartTime.asNanoseconds ();
321+ m_startTime = ( start_nanos % m_multiplier == 0 )
322+ ? start_nanos / m_multiplier
323+ : start_nanos / m_multiplier + 1 ;
324+ m_endTime = m_rawEndTime.asNanoseconds () / m_multiplier;
325+ }
326+
327+ void initializeStartPosition ()
328+ {
329+ // If schema was provided upfront, compute time range now
330+ // Otherwise, time range will be computed lazily in updateStateFromNextRecordBatch()
331+ if ( m_initialized )
332+ computeTimeRange ();
333+
334+ // Find the starting index where time >= start
335+ while ( !m_finished )
336+ {
337+ updateStateFromNextRecordBatch ();
338+ if ( !m_curRecordBatch )
339+ {
340+ m_finished = true ;
341+ break ;
342+ }
343+ m_curBatchIdx = findFirstMatchingIndex ();
344+ if ( m_curBatchIdx < m_numRows )
345+ break ;
346+ m_curRecordBatch = nullptr ;
347+ }
348+ }
349+
272350 std::string m_tsColName;
273351 RecordBatchIterator m_source;
274352
275353 int m_expectSmallBatches;
276354 bool m_finished;
277- std::shared_ptr<::arrow::Schema> m_schema;
355+ bool m_initialized; // Whether schema and timestamp multiplier have been resolved
356+ int64_t m_multiplier;
278357 std::shared_ptr<::arrow::RecordBatch> m_curRecordBatch;
279358 std::shared_ptr<::arrow::TimestampArray> m_tsArray;
280359 ::arrow::TimestampArray::IteratorType m_endIt;
281360 int64_t m_arrayLastTime;
282- int64_t m_multiplier, m_numRows, m_startTime, m_endTime, m_curBatchIdx;
361+ int64_t m_numRows;
362+ int64_t m_startTime;
363+ int64_t m_endTime;
364+ int64_t m_curBatchIdx;
365+ DateTime m_rawStartTime;
366+ DateTime m_rawEndTime;
283367};
284368
285369};
0 commit comments