Skip to content

FrametoTable for Arrow backend#980

Open
arnavdham wants to merge 12 commits into
AIDASoft:masterfrom
arnavdham:feature/frameToTable
Open

FrametoTable for Arrow backend#980
arnavdham wants to merge 12 commits into
AIDASoft:masterfrom
arnavdham:feature/frameToTable

Conversation

@arnavdham

Copy link
Copy Markdown
Contributor

Description
This PR isolates and implements a frameToTable conversion 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 to List<Struct<...>> columns.

Proposed Changes
Core Translation & Registries

  1. ArrowTypeRegistry: Thread-safe global registry mapping PODIO collection type name strings to their corresponding Apache Arrow DataTypes.
  2. ArrowConverterRegistry: Thread-safe global registry mapping PODIO collection type name strings to callback functions that serialize collection buffers into Arrow arrays.
  3. ArrowFrameConverter: Implements convertFrameToTable, translating:
    • Regular collections (delegated to registered converters).
    • Subset collections (translated to reference coordinate structs with collectionID and index).
    • Frame parameters (bundled into a frame_parameters struct column containing maps of string keys to list values).

Code Generation Integration

  1. ArrowMapper.h.jinja2 & ArrowMapper.cc.jinja2: Templates that generate schema definition maps and register serializers dynamically for custom EDMs.
  2. 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.
  3. cpp_generator.py: Extracts structural metadata and triggers the generation of ArrowMapper files.

@arnavdham arnavdham changed the title Feature/frame to table FrametoTable for Arrow backend Jun 23, 2026
@arnavdham

arnavdham commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Related Issue: #981

@arnavdham arnavdham marked this pull request as ready for review June 24, 2026 04:06
@arnavdham

Copy link
Copy Markdown
Contributor Author

@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)

@veprbl

veprbl commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

I think, there is a missing symlink on the CVMFS:

% ls /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h
ls: cannot access /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h: No such file or directory
% ls /cvmfs/sft.cern.ch/lcg/releases/arrow/11.0.0-27569/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h
/cvmfs/sft.cern.ch/lcg/releases/arrow/11.0.0-27569/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h

I will try contacting the LCG support.

@jmcarcell jmcarcell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

Comment thread cmake/podioMacros.cmake Outdated
Comment thread tests/unittests/CMakeLists.txt Outdated
Comment thread src/CMakeLists.txt Outdated
Comment thread cmake/podioMacros.cmake Outdated
Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
#include <nlohmann/json.hpp>

TEST_CASE("ArrowFrameConverter - convertFrameToTable Verification", "[arrow][converter]") {
podio::Frame originalFrame;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I wasn't aware that this existed.

Comment thread tests/unittests/test_arrow_converter.cpp
Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is defined once per type, not ideal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does every operation have to be run through checkStatus? Is that standard?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
Comment thread include/podio/utilities/ArrowConverterRegistry.h Outdated
Comment thread python/templates/macros/arrow.jinja2 Outdated
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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this case? It's possible that the values for invalid objects (-1, -1) already serve as a fine marker for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Comment thread python/podio_gen/cpp_generator.py Outdated
"is_mult": is_mult,
}

def _arrow_field(self, name, type_expr, nullable=True):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need nullable, definitely not by default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appears fixed now.

@arnavdham arnavdham force-pushed the feature/frameToTable branch from 0bf0945 to 3d3eac1 Compare June 29, 2026 02:18
@arnavdham

Copy link
Copy Markdown
Contributor Author

@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?
  1. In the future, we plan to map a Category to an Arrow Table, where each row in that table represents one individual Frame belonging to that category.
    It keeps all collections in a given category grouped together in a single self-contained table, similar to how other formats (like ROOT RNTuple) structure events.

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.

  1. Using arrow::Buffer::Wrap for zero-copy doesn't work directly here because:
    AOS vs. SOA Layout: PODIO stores collection elements contiguously as Array-of-Structs (AOS), whereas Arrow's StructArray requires a columnar Struct-of-Arrays (SOA) layout with separate memory buffers for each field.
    Might work for primitive members but not for complex structs.

Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
Comment on lines +57 to +72
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));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time to remove this? (also in the header file)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the future for the Writer we could use this at some point(not sure about the exact usecase right now)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This schema is not usable. We would be refactoring or reusing schema generation from frameToTable instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have removed schema,frameparametersType and the related test.

Comment thread cmake/podioMacros.cmake Outdated
return()
endif()

add_library(${CORE_LIB}ArrowMapper SHARED ${SOURCES} ${HEADERS})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. we can do this.

@arnavdham

Copy link
Copy Markdown
Contributor Author

@jmcarcell @veprbl I have made changes according to all comments. Let me know if anything is still needed.

@veprbl veprbl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some more nitpicks on unused things.

Comment thread python/templates/ArrowMapper.h.jinja2 Outdated
Comment on lines 8 to 11
namespace arrow {
class Schema;
class KeyValueMetadata;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now also unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment thread python/templates/ArrowMapper.h.jinja2 Outdated
#include <arrow/type.h>
#include <arrow/util/key_value_metadata.h>

#include <memory>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment thread python/templates/ArrowMapper.h.jinja2 Outdated
schemaWithMetadata(std::shared_ptr<const arrow::KeyValueMetadata> metadata) {
return schema()->WithMetadata(std::move(metadata));
}
bool registerTypes();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we don't need to expose this library initialization. We don't expect to call this externally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have removed this from the .h file but now the ArrowMapper.h is empty entirely. Do I just remove the file entirely?

Comment thread src/ArrowFrameConverter.cc Outdated
Comment on lines +21 to +23
void checkStatus(const arrow::Status& status, const std::string& msg) {
podio::arrow_utils::checkStatus(status, msg);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
namespace {
void checkStatus(const arrow::Status& status, const std::string& msg) {
podio::arrow_utils::checkStatus(status, msg);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +479 to +483
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if tests are meant to output to stdout.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did that to check outputs. Removed now

Comment thread src/CMakeLists.txt Outdated
Comment on lines +62 to +63
ArrowTypeRegistry.cc
ArrowConverterRegistry.cc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these go below instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment thread python/podio_gen/cpp_generator.py Outdated
Comment on lines +232 to +233
"class_full_type": datatype["class"].full_type,
"class_bare_type": datatype["class"].bare_type,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

@veprbl veprbl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants