Skip to content

Commit 126fdd8

Browse files
authored
Merge branch 'main' into ac/arrow_nodes
2 parents b38c81f + 0a0ae29 commit 126fdd8

5 files changed

Lines changed: 365 additions & 100 deletions

File tree

.github/workflows/build.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -295,28 +295,28 @@ jobs:
295295
run: make dist-check
296296

297297
- name: Upload Wheel
298-
uses: actions/upload-artifact@v6
298+
uses: actions/upload-artifact@v7
299299
with:
300300
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-3.10
301301
path: dist/*.whl
302302
if: ${{ matrix.cibuildwheel == 'cp310' }}
303303

304304
- name: Upload Wheel
305-
uses: actions/upload-artifact@v6
305+
uses: actions/upload-artifact@v7
306306
with:
307307
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-3.11
308308
path: dist/*.whl
309309
if: ${{ matrix.cibuildwheel == 'cp311' }}
310310

311311
- name: Upload Wheel
312-
uses: actions/upload-artifact@v6
312+
uses: actions/upload-artifact@v7
313313
with:
314314
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-3.12
315315
path: dist/*.whl
316316
if: ${{ matrix.cibuildwheel == 'cp312' }}
317317

318318
- name: Upload Wheel
319-
uses: actions/upload-artifact@v6
319+
uses: actions/upload-artifact@v7
320320
with:
321321
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-3.13
322322
path: dist/*.whl
@@ -377,7 +377,7 @@ jobs:
377377
run: make dist-check
378378

379379
- name: Upload SDist
380-
uses: actions/upload-artifact@v6
380+
uses: actions/upload-artifact@v7
381381
with:
382382
name: csp-sdist
383383
path: dist/*.tar.gz
@@ -462,7 +462,7 @@ jobs:
462462
run: make requirements
463463

464464
- name: Download wheel
465-
uses: actions/download-artifact@v7
465+
uses: actions/download-artifact@v8
466466
with:
467467
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }}
468468

@@ -573,7 +573,7 @@ jobs:
573573
- name: Install requirements
574574
run: sudo make dependencies-debian
575575

576-
- uses: actions/download-artifact@v7
576+
- uses: actions/download-artifact@v8
577577
with:
578578
name: csp-sdist
579579
path: dist/
@@ -657,7 +657,7 @@ jobs:
657657
run: sudo apt-get install graphviz
658658

659659
- name: Download wheel
660-
uses: actions/download-artifact@v7
660+
uses: actions/download-artifact@v8
661661
with:
662662
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }}
663663

@@ -737,7 +737,7 @@ jobs:
737737

738738
steps:
739739
- name: Download wheels and sdist
740-
uses: actions/download-artifact@v7
740+
uses: actions/download-artifact@v8
741741
with:
742742
name:
743743
merge-multiple: true

ci/scripts/requirements.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ def deps():
99
return requires + develop
1010

1111
def main():
12-
os.system("python -m pip install toml")
13-
os.system("python -m pip install " + " ".join(f'"{_}"' for _ in deps()))
12+
ret = os.system("python -m pip install --prefer-binary toml")
13+
if ret != 0:
14+
raise ValueError("Python requirement install failed: see output for error")
15+
ret = os.system("python -m pip install --prefer-binary " + " ".join(f'"{_}"' for _ in deps()))
16+
if ret != 0:
17+
raise ValueError("Python requirement install failed: see output for error")
1418

1519

1620
if __name__ == "__main__":

cpp/csp/python/adapters/ArrowInputAdapter.h

Lines changed: 133 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class RecordBatchIterator
2424
{
2525
public:
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+
6380
private:
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

271287
private:
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

Comments
 (0)