Add support for lists to Arrow <-> csp.Struct conversion nodes#701
Merged
Conversation
Add ArrowNumpyListReader and ArrowNumpyListWriter that handle numpy ndarray fields as Arrow list-of-primitive columns. Update NumpyConversions.h with helpers for Arrow↔numpy type mapping. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
Update ArrowCppNodes to register custom numpy FieldReaders/Writers for ndarray struct fields. Update arrow.py graph functions to detect and handle numpy array columns in struct types. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
Add tests for numpy ndarray fields in record_batches_to_struct and struct_to_record_batches: 1D/2D arrays, various dtypes, round-trip conversion, edge cases. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
Tests expose two bugs: - StringListWriter uses PyArray_GETPTR1 which is incorrect for ndim>1 - TemporalListWriter writes NaT as raw int64 instead of Arrow null Also fixes pre-existing MiddleStruct -> InnerStruct typo that prevented the test file from being collected. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
- StringListWriter: use flat pointer arithmetic on C-contiguous data instead of PyArray_GETPTR1 which uses strides[0] only, causing OOB access for ndim > 1 arrays - makeStringListItemsWriter: same fix for the factory path - TemporalListWriter: check for NPY_DATETIME_NAT and emit Arrow null instead of writing raw INT64_MIN as a value (which overflows on scaling and loses NaT semantics) - DateListWriter: same NaT check before date conversion Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
These functions are pure numpy utilities, not CppNode-specific. Moving them to NumpyConversions.h/.cpp keeps ArrowCppNodes.cpp focused on node wiring and makes the utilities reusable. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
bcd4e71 to
e850962
Compare
AdamGlustein
requested changes
Apr 21, 2026
…tting - Make field_map Optional in both record_batches_to_struct and struct_to_record_batches; Python resolves None to identity mapping and always passes explicit map to C++ - Reorder record_batches_to_struct signature: schema before field_map (required param before optional) - Extract _extract_numpy_metadata utility to deduplicate field categorization logic - Remove C++ auto-detect branches in StructToRecordBatch and RecordBatchToStruct; both now require explicit fieldMap and throw on invalid field names - Remove resolveFieldName helper from RecordBatchToStruct - Formatting fixes: template return+name on same line (reader), single-line reinterpret_cast (writer), makeStringListItemsWriter signature, member alignment Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
…oral reader, NPY_TYPE specializations - Move timeUnitMultiplier to ArrowFieldReader.h as inline, remove duplicate - Unify makeDurationListReadValue into makeTemporalListReadValue with dtype param - Remove static dtype cache, capture dtype by lambda instead - Add NPY_TYPE<DateTime>, NPY_TYPE<TimeDelta>, NPY_TYPE<Date> specializations - Simplify npyTypeFromPyType: add temporal types to PartialSwitchCspType - Use npy_intp loop variable when iterating over npy_intp size Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
…optimize null dims - Extract ListFieldWriterBase with shared reserve/writeNull/finish/m_listBuilder - Add ArrowArrayType<T> trait (bool -> uint8_t), remove makeBoolListItemsWriter - NumpyNDArrayWriter: write null dims when shape matches previous row - NumpyNDArrayReader: reuse last-seen dims on null Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
5d6d1e0 to
45624f2
Compare
The NPY_TYPE<DateTime/TimeDelta> specializations would cause empty_array() to create unitless datetime64/timedelta64 arrays via PyArray_SimpleNew, which newer pandas rejects. Use NPY_OBJECT for the empty (0-length) case to match prior behavior. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
45624f2 to
df9d2f2
Compare
AdamGlustein
previously approved these changes
Apr 21, 2026
AdamGlustein
left a comment
Collaborator
There was a problem hiding this comment.
All comments addressed for me, wait for @robambalu to look as well
Collaborator
|
I wonder at what point we can remove / cleanup old parquet / arrow numpy processing code does this PR help us get there? |
Collaborator
Author
Yeah, so this PR adds support for lists in the (csp.Struct <-> Arrow RB) conversion and #698 added support for the scalar types. Once these both are merged we can remove a lot of the parquet conversion code and use the conversion logic in the arrow code (which is simpler) |
Replace import_array1(false) with import_array() in a void* returning static initializer with AcquireGIL, matching NumpyConversions.cpp pattern. Remove unnecessary registerCallback wrapper. Fix formatting: createNumpyNDArrayReader call and one-liner else. Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
AdamGlustein
approved these changes
Apr 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Arrow RecordBatch ↔ csp.Struct: Numpy Array Support
Summary
Extends the Arrow RecordBatch ↔ csp.Struct conversion (introduced in #698) to support numpy array fields — both 1D (
Numpy1DArray[T]) and N-dimensional (NumpyNDArray[T]).Struct fields annotated as numpy arrays are serialized to/from Arrow
List<element>columns. For ND arrays, a companionList<Int64>dimensions column stores the shape, enabling lossless round-trip of arbitrary-dimensional data.What's New
C++ Layer
ArrowNumpyListReader.h— Reads Arrow list columns into numpy arrays. Supports native types (double, int64, int32, bool), unicode strings, and temporal types (datetime64, timedelta64, date). Includes fast-path bulkmemcpyfor contiguous numeric data with no nulls.ArrowNumpyListWriter.h— Writes numpy arrays into Arrow list columns. Handles C-contiguous and non-contiguous arrays, NaT sentinel round-tripping for temporal types, and UTF-8 ↔ char32_t conversion for strings.ArrowCppNodes.cpp— Extendedrecord_batches_to_structandstruct_to_record_batchesCppNodes to detect numpy fields in the properties dict, create the appropriate readers/writers, and wire up reshape/shape callbacks for ND arrays.NumpyConversions.h/.cpp— MovednumpyReshape,numpyShape, andnpyTypeFromPyTypeutility functions here from ArrowCppNodes for better code organization.Python Layer
csp/adapters/arrow.py—record_batches_to_structandstruct_to_record_batchesgraph functions now inspect struct metadata to separate numpy fields from scalar fields, resolve dimension column names for ND arrays, and pass everything through to the C++ nodes.Element Types Supported
Numpy1DArray[float]List<Float64>float64Numpy1DArray[int]List<Int64>int64Numpy1DArray[bool]List<Boolean>boolNumpy1DArray[str]List<Utf8>U<n>(unicode)Numpy1DArray[datetime]List<Timestamp(ns, UTC)>datetime64[ns]Numpy1DArray[timedelta]List<Duration(ns)>timedelta64[ns]Numpy1DArray[date]List<Date32>datetime64[ns]NumpyNDArray[T]List<...>+List<Int64>dimsArchitecture
This along with #698 resolve #286 .