FrametoTable for Arrow backend#980
Conversation
|
Related Issue: #981 |
|
@jmcarcell Please take a look. I'm not sure why some of the pretests fail.
|
|
I think, there is a missing symlink on the CVMFS: I will try contacting the LCG support. |
There was a problem hiding this comment.
@jmcarcell Please take a look. I'm not sure why some of the pretests fail.
* /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/scalar.h:39:10: fatal error: arrow/util/decimal.h: No such file or directory 39 | #include "arrow/util/decimal.h" * The following tests FAILED: 6 - read_python_frame_root (Not Run) 17 - read_python_frame_rntuple (Not Run) 40 - write_python_frame_root (Timeout) 41 - write_python_frame_rntuple (Timeout) 48 - read_python_frame_sio (Not Run) 54 - write_python_frame_sio (Timeout)
Timeouts happen from time to time, simply ignore them. The workflow for LCG_104 is fixed now. You will need to rebase to pick #979 to avoid issues with sanitizers. I have made some comments inline and I have a few others:
- Is mapping a Frame to a row in Arrow the natural way of doing this? What are the alternatives?
- Values are copied through the object layer, did you think about this differently? There is
arrow::Buffer::Wrap, maybe that could work?
| #include <nlohmann/json.hpp> | ||
|
|
||
| TEST_CASE("ArrowFrameConverter - convertFrameToTable Verification", "[arrow][converter]") { | ||
| podio::Frame originalFrame; |
There was a problem hiding this comment.
There are multiple tests that use the header write_frame.h and the function makeFrame, maybe that would be useful not to have to create it yourself, and it's quite complete. Eventually, when doing the roundtrip from Arrow to Frame, you could also use the stuff in read_frame.h in a similar way.
There was a problem hiding this comment.
Thank you! I wasn't aware that this existed.
| auto* collectionBuilder = static_cast<arrow::ListBuilder*>(builder.get()); | ||
| auto* objectBuilder = static_cast<arrow::StructBuilder*>(collectionBuilder->value_builder()); | ||
|
|
||
| auto checkStatus = [](const arrow::Status& s, const std::string& msg) { |
There was a problem hiding this comment.
This is defined once per type, not ideal.
There was a problem hiding this comment.
A comment explaining what changed would be better than "fixed"; in this case the fix is not trivial, a new file with the utility was made and that utility is being called. It's hard to follow otherwise.
| auto* collectionBuilder = static_cast<arrow::ListBuilder*>(builder.get()); | ||
| auto* objectBuilder = static_cast<arrow::StructBuilder*>(collectionBuilder->value_builder()); | ||
|
|
||
| auto checkStatus = [](const arrow::Status& s, const std::string& msg) { |
There was a problem hiding this comment.
Does every operation have to be run through checkStatus? Is that standard?
There was a problem hiding this comment.
I think, this mimics ARROW_RETURN_NOT_OK. As far as I know there isn't an existing one that would throw an exception for us.
| checkStatus(builder_{{ relation.name }}_collId->Append(relObj_{{ relation.name }}.getObjectID().collectionID), "Failed to append relation collectionID"); | ||
| checkStatus(builder_{{ relation.name }}_index->Append(relObj_{{ relation.name }}.getObjectID().index), "Failed to append relation index"); | ||
| } else { | ||
| checkStatus(builder_{{ relation.name }}->AppendNull(), "Failed to append null relation struct"); |
There was a problem hiding this comment.
Do we need this case? It's possible that the values for invalid objects (-1, -1) already serve as a fine marker for this.
| "is_mult": is_mult, | ||
| } | ||
|
|
||
| def _arrow_field(self, name, type_expr, nullable=True): |
There was a problem hiding this comment.
We shouldn't need nullable, definitely not by default.
Co-authored-by: Juan Miguel Carceller <22276694+jmcarcell@users.noreply.github.com>
0bf0945 to
3d3eac1
Compare
Alternative: We could store one flat Arrow Table per collection type (e.g., a HitsTable, a TracksTable) with an event_id column.But, it would require complex joins/filters to reconstruct a single frame, adding overhead.
|
| std::shared_ptr<arrow::Schema> schema() { | ||
| const auto& types = typeMap(); | ||
| static const auto instance = arrow::schema({ | ||
| {% for datatype in datatypes %} | ||
| arrow::field("{{ datatype.class.bare_type }}", types.at("{{ datatype.class.full_type }}")), | ||
| {% endfor %} | ||
| arrow::field("frame_parameters", frameParametersType()), | ||
| }); | ||
| return instance; | ||
| } | ||
|
|
||
| std::shared_ptr<arrow::Schema> | ||
| schemaWithMetadata(std::shared_ptr<const arrow::KeyValueMetadata> metadata) { | ||
| return schema()->WithMetadata(std::move(metadata)); | ||
| } | ||
|
|
There was a problem hiding this comment.
Time to remove this? (also in the header file)
There was a problem hiding this comment.
I think in the future for the Writer we could use this at some point(not sure about the exact usecase right now)
There was a problem hiding this comment.
This schema is not usable. We would be refactoring or reusing schema generation from frameToTable instead.
There was a problem hiding this comment.
have removed schema,frameparametersType and the related test.
| return() | ||
| endif() | ||
|
|
||
| add_library(${CORE_LIB}ArrowMapper SHARED ${SOURCES} ${HEADERS}) |
There was a problem hiding this comment.
Since this becomes a part of the public interface, I wanted to check: Do we want to rename ArrowMapper/ARROW_MAPPER to just Arrow/ARROW? I get that SioBlocks is not just Sio, but the "Mapper" is not conveying much information? (We can keep ArrowMapper.{cc,h} as-is for clarity)
There was a problem hiding this comment.
Sure. we can do this.
|
@jmcarcell @veprbl I have made changes according to all comments. Let me know if anything is still needed. |
veprbl
left a comment
There was a problem hiding this comment.
I have some more nitpicks on unused things.
| namespace arrow { | ||
| class Schema; | ||
| class KeyValueMetadata; | ||
| } |
| #include <arrow/type.h> | ||
| #include <arrow/util/key_value_metadata.h> | ||
|
|
||
| #include <memory> |
| schemaWithMetadata(std::shared_ptr<const arrow::KeyValueMetadata> metadata) { | ||
| return schema()->WithMetadata(std::move(metadata)); | ||
| } | ||
| bool registerTypes(); |
There was a problem hiding this comment.
And we don't need to expose this library initialization. We don't expect to call this externally?
There was a problem hiding this comment.
Have removed this from the .h file but now the ArrowMapper.h is empty entirely. Do I just remove the file entirely?
| void checkStatus(const arrow::Status& status, const std::string& msg) { | ||
| podio::arrow_utils::checkStatus(status, msg); | ||
| } |
There was a problem hiding this comment.
| void checkStatus(const arrow::Status& status, const std::string& msg) { | |
| podio::arrow_utils::checkStatus(status, msg); | |
| } | |
| using checkStatus = podio::arrow_utils::checkStatus; |
Or something like that.
| namespace { | ||
| void checkStatus(const arrow::Status& status, const std::string& msg) { | ||
| podio::arrow_utils::checkStatus(status, msg); | ||
| } |
| std::cout << "--- ARROW TABLE JSON ---" << std::endl; | ||
| std::cout << arrowJson.dump(4) << std::endl; | ||
| std::cout << "--- FRAME DIRECT JSON ---" << std::endl; | ||
| std::cout << frameJson.dump(4) << std::endl; | ||
|
|
There was a problem hiding this comment.
Not sure if tests are meant to output to stdout.
There was a problem hiding this comment.
Yes I did that to check outputs. Removed now
| ArrowTypeRegistry.cc | ||
| ArrowConverterRegistry.cc |
| "class_full_type": datatype["class"].full_type, | ||
| "class_bare_type": datatype["class"].bare_type, |
Description
This PR isolates and implements a
frameToTableconversion layer to map PODIO Frames and collection data types to Apache Arrow Tables. The implementation maps each PODIO Frame to a single row in an Arrow Table, with individual collections mapped toList<Struct<...>>columns.Proposed Changes
Core Translation & Registries
ArrowTypeRegistry: Thread-safe global registry mapping PODIO collection type name strings to their corresponding Apache Arrow DataTypes.ArrowConverterRegistry: Thread-safe global registry mapping PODIO collection type name strings to callback functions that serialize collection buffers into Arrow arrays.ArrowFrameConverter: Implements convertFrameToTable, translating:Code Generation Integration
ArrowMapper.h.jinja2 & ArrowMapper.cc.jinja2: Templates that generate schema definition maps and register serializers dynamically for custom EDMs.macros/arrow.jinja2: Jinja2 serialization macros that recursively parse complex types, fixed-size arrays, and one-to-one/one-to-many relationship structures to build Arrow array representations.cpp_generator.py: Extracts structural metadata and triggers the generation of ArrowMapper files.