From 8ae7452dc55df7bc837152f3b064e51665a73da0 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 16 Oct 2026 10:53:12 +0200 Subject: [PATCH 1/6] DPL Analysis: distinct ccdb path configurables To better distinguish between configurable ccdb paths, defined in different ccdb request tables, prepend them with the column's namespace. --- Framework/Core/include/Framework/ASoA.h | 529 +++++++++--------- .../Core/include/Framework/Configurable.h | 3 +- .../include/Framework/TypeIdHelpers.h | 17 + 3 files changed, 278 insertions(+), 271 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index b67b36e8ab9ca..7db1014928454 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -2319,17 +2319,6 @@ O2ORIGIN("TEST"); O2HASH("TEST/0"); } // namespace o2::aod -namespace -{ -template -consteval static std::string_view namespace_prefix() -{ - constexpr auto name = o2::framework::type_name(); - const auto pos = name.rfind(std::string_view{":"}); - return name.substr(0, pos + 1); -} -} // namespace - #define DECLARE_EQUIVALENT_FOR_INDEX(_Base_, _Equiv_) \ template <> \ struct EquivalentIndexNG, o2::aod::Hash<_Equiv_::ref.desc_hash>> { \ @@ -2345,7 +2334,7 @@ consteval static std::string_view namespace_prefix() #define DECLARE_SOA_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_) \ struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ static_assert(!((*(mLabel + 1) == 'I' && *(mLabel + 2) == 'n' && *(mLabel + 3) == 'd' && *(mLabel + 4) == 'e' && *(mLabel + 5) == 'x')), "Index is not a valid column name"); \ using base = o2::soa::Column<_Type_, _Name_>; \ using type = _Type_; \ @@ -2371,73 +2360,73 @@ consteval static std::string_view namespace_prefix() }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } -#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_, _RunDependent_, ...) \ - struct _Name_ : o2::soa::Column { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const char* query = _CCDBQuery_; \ - /* How the object is keyed in CCDB: 0 queries by timestamp alone, 1 additionally sends */ \ - /* the run number as "runNumber" metadata (o2::ccdb run-dependent objects), 2 uses the */ \ - /* run number in place of the timestamp. A non-zero value needs the column's table to */ \ - /* be uniform in the run number, since that is where the run comes from. */ \ - static constexpr int run_dependent = _RunDependent_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - static constexpr bool needs_ptr_rec = true; \ - /* Post-deserialisation fixup for objects which are not usable straight out of the ROOT */ \ - /* streamer, e.g. FlatObjects whose internal pointers must be rectified first. Runs on */ \ - /* the receiving device, once per (re)deserialisation, before the object is ever handed */ \ - /* out. Returns the object to cache: a finaliser returning a different instance owns */ \ - /* disposing of the one it was given. */ \ - using finaliser_t = _ConcreteType_* (*)(_ConcreteType_*); \ - static constexpr finaliser_t finalise = __VA_ARGS__; \ - std::function const* ptrRec = nullptr; \ - using base = o2::soa::Column; \ - using type = int64_t[3]; \ - using column_t = _Name_; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - auto& [handle, segment, size] = *mColumnIterator; \ - auto span = std::span{(*ptrRec)(fair::mq::shmem::MetaHeader{ \ - static_cast(size), \ - 0, handle, 0, 0, \ - static_cast(segment), true}), \ - static_cast(size)}; \ - if constexpr (std::same_as<_ConcreteType_, std::span>) { \ - return span; \ - } else { \ - static std::byte* payload = nullptr; \ - static _ConcreteType_* deserialised = nullptr; \ - static TClass* c = TClass::GetClass(#_ConcreteType_); \ - if (payload != (std::byte*)span.data()) { \ - payload = (std::byte*)span.data(); \ - delete deserialised; \ - TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ - auto* streamed = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ - if (!streamed) { \ - LOGP(fatal, \ - "Could not deserialise a {} from the CCDB payload for {} ({} bytes). Check the configured " \ - "path (option \"ccdb:{}\") and that the object exists for this timestamp.", \ - #_ConcreteType_, _CCDBQuery_, span.size(), _Label_); \ - } \ - deserialised = finalise(streamed); \ - } \ - return *deserialised; \ - } \ - } \ - \ - decltype(auto) \ - get() const \ - { \ - return _Getter_(); \ - } \ +#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_, _RunDependent_, ...) \ + struct _Name_ : o2::soa::Column { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const char* query = _CCDBQuery_; \ + /* How the object is keyed in CCDB: 0 queries by timestamp alone, 1 additionally sends */ \ + /* the run number as "runNumber" metadata (o2::ccdb run-dependent objects), 2 uses the */ \ + /* run number in place of the timestamp. A non-zero value needs the column's table to */ \ + /* be uniform in the run number, since that is where the run comes from. */ \ + static constexpr int run_dependent = _RunDependent_; \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr bool needs_ptr_rec = true; \ + /* Post-deserialisation fixup for objects which are not usable straight out of the ROOT */ \ + /* streamer, e.g. FlatObjects whose internal pointers must be rectified first. Runs on */ \ + /* the receiving device, once per (re)deserialisation, before the object is ever handed */ \ + /* out. Returns the object to cache: a finaliser returning a different instance owns */ \ + /* disposing of the one it was given. */ \ + using finaliser_t = _ConcreteType_* (*)(_ConcreteType_*); \ + static constexpr finaliser_t finalise = __VA_ARGS__; \ + std::function const* ptrRec = nullptr; \ + using base = o2::soa::Column; \ + using type = int64_t[3]; \ + using column_t = _Name_; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + auto& [handle, segment, size] = *mColumnIterator; \ + auto span = std::span{(*ptrRec)(fair::mq::shmem::MetaHeader{ \ + static_cast(size), \ + 0, handle, 0, 0, \ + static_cast(segment), true}), \ + static_cast(size)}; \ + if constexpr (std::same_as<_ConcreteType_, std::span>) { \ + return span; \ + } else { \ + static std::byte* payload = nullptr; \ + static _ConcreteType_* deserialised = nullptr; \ + static TClass* c = TClass::GetClass(#_ConcreteType_); \ + if (payload != (std::byte*)span.data()) { \ + payload = (std::byte*)span.data(); \ + delete deserialised; \ + TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ + auto* streamed = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ + if (!streamed) { \ + LOGP(fatal, \ + "Could not deserialise a {} from the CCDB payload for {} ({} bytes). Check the configured " \ + "path (option \"ccdb:{}\") and that the object exists for this timestamp.", \ + #_ConcreteType_, _CCDBQuery_, span.size(), _Label_); \ + } \ + deserialised = finalise(streamed); \ + } \ + return *deserialised; \ + } \ + } \ + \ + decltype(auto) \ + get() const \ + { \ + return _Getter_(); \ + } \ }; /* Conventional label, and the object used exactly as the ROOT streamer produced it. Reach @@ -2458,7 +2447,7 @@ consteval static std::string_view namespace_prefix() #define DECLARE_SOA_BITMAP_COLUMN_FULL(_Name_, _Getter_, _Size_, _Label_) \ struct _Name_ : o2::soa::Column { \ static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ static_assert(!((*(mLabel + 1) == 'I' && *(mLabel + 2) == 'n' && *(mLabel + 3) == 'd' && *(mLabel + 4) == 'e' && *(mLabel + 5) == 'x')), "Index is not a valid column name"); \ using base = o2::soa::Column; \ using type = MAKEINT(_Size_); \ @@ -2488,38 +2477,38 @@ consteval static std::string_view namespace_prefix() /// An 'expression' column. i.e. a column that can be calculated from other /// columns with gandiva based on static C++ expression. -#define DECLARE_SOA_EXPRESSION_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_, _Expression_) \ - struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - using base = o2::soa::Column<_Type_, _Name_>; \ - using type = _Type_; \ - using column_t = _Name_; \ - using spawnable_t = std::true_type; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - return *mColumnIterator; \ - } \ - \ - decltype(auto) get() const \ - { \ - return _Getter_(); \ - } \ - \ - static o2::framework::expressions::Projector Projector() \ - { \ - return _Expression_; \ - } \ - }; \ +#define DECLARE_SOA_EXPRESSION_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_, _Expression_) \ + struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + using base = o2::soa::Column<_Type_, _Name_>; \ + using type = _Type_; \ + using column_t = _Name_; \ + using spawnable_t = std::true_type; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + return *mColumnIterator; \ + } \ + \ + decltype(auto) get() const \ + { \ + return _Getter_(); \ + } \ + \ + static o2::framework::expressions::Projector Projector() \ + { \ + return _Expression_; \ + } \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Expression_) \ @@ -2527,34 +2516,34 @@ consteval static std::string_view namespace_prefix() /// A configurable 'expression' column. i.e. a column that can be calculated from other /// columns with gandiva based on dynamically supplied C++ expression or a string definition. -#define DECLARE_SOA_CONFIGURABLE_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Label_) \ - struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - static constexpr const int32_t mHash = _Label_ ""_h; \ - using base = o2::soa::Column<_Type_, _Name_>; \ - using type = _Type_; \ - using column_t = _Name_; \ - using spawnable_t = std::true_type; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - return *mColumnIterator; \ - } \ - \ - decltype(auto) get() const \ - { \ - return _Getter_(); \ - } \ - }; \ +#define DECLARE_SOA_CONFIGURABLE_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Label_) \ + struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr const int32_t mHash = _Label_ ""_h; \ + using base = o2::soa::Column<_Type_, _Name_>; \ + using type = _Type_; \ + using column_t = _Name_; \ + using spawnable_t = std::true_type; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + return *mColumnIterator; \ + } \ + \ + decltype(auto) get() const \ + { \ + return _Getter_(); \ + } \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } /// An index column is a column of indices to elements / of another table named @@ -2812,81 +2801,81 @@ consteval auto getIndexTargets() #define DECLARE_SOA_ARRAY_INDEX_COLUMN_CUSTOM(_Name_, _Getter_, _Label_) DECLARE_SOA_ARRAY_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, int32_t, _Name_##s, _Label_, "") /// NORMAL -#define DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, _Label_, _Suffix_) \ - struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ - static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ - static_assert((*_Suffix_ == '\0') || (*_Suffix_ == '_'), "Suffix has to begin with _"); \ - static constexpr const char* mLabel = "fIndex" _Label_ _Suffix_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ - using base = o2::soa::Column<_Type_, _Name_##Id>; \ - using type = _Type_; \ - using column_t = _Name_##Id; \ - using binding_t = _Table_; \ - static constexpr auto index_targets = getIndexTargets<_Table_>(); \ - _Name_##Id(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_##Id() = default; \ - _Name_##Id(_Name_##Id const& other) = default; \ - _Name_##Id& operator=(_Name_##Id const& other) = default; \ - type inline getId() const \ - { \ - return _Getter_##Id(); \ - } \ - \ - type _Getter_##Id() const \ - { \ - return *mColumnIterator; \ - } \ - \ - bool has_##_Getter_() const \ - { \ - return *mColumnIterator >= 0; \ - } \ - \ - template \ - auto _Getter_##_as() const \ - { \ - if (O2_BUILTIN_UNLIKELY(mBinding.ptr == nullptr)) { \ - o2::soa::notBoundTable(#_Table_); \ - } \ - if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ - o2::soa::accessingInvalidIndexFor(#_Getter_); \ - } \ - auto t = mBinding.get(); \ - if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ - o2::soa::dereferenceWithWrongType(#_Getter_, #_Table_); \ - } \ - return t->rawIteratorAt(*mColumnIterator); \ - } \ - \ - auto _Getter_() const \ - { \ - return _Getter_##_as(); \ - } \ - \ - template \ - bool setCurrent(T* current) \ - { \ - if constexpr (o2::soa::is_binding_compatible_v()) { \ - assert(current != nullptr); \ - this->mBinding.bind(current); \ - return true; \ - } \ - return false; \ - } \ - \ - bool setCurrentRaw(o2::soa::Binding current) \ - { \ - this->mBinding = current; \ - return true; \ - } \ - binding_t const* getCurrent() const { return mBinding.get(); } \ - o2::soa::Binding getCurrentRaw() const { return mBinding; } \ - o2::soa::Binding mBinding; \ - }; \ +#define DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, _Label_, _Suffix_) \ + struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ + static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ + static_assert((*_Suffix_ == '\0') || (*_Suffix_ == '_'), "Suffix has to begin with _"); \ + static constexpr const char* mLabel = "fIndex" _Label_ _Suffix_; \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ + using base = o2::soa::Column<_Type_, _Name_##Id>; \ + using type = _Type_; \ + using column_t = _Name_##Id; \ + using binding_t = _Table_; \ + static constexpr auto index_targets = getIndexTargets<_Table_>(); \ + _Name_##Id(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_##Id() = default; \ + _Name_##Id(_Name_##Id const& other) = default; \ + _Name_##Id& operator=(_Name_##Id const& other) = default; \ + type inline getId() const \ + { \ + return _Getter_##Id(); \ + } \ + \ + type _Getter_##Id() const \ + { \ + return *mColumnIterator; \ + } \ + \ + bool has_##_Getter_() const \ + { \ + return *mColumnIterator >= 0; \ + } \ + \ + template \ + auto _Getter_##_as() const \ + { \ + if (O2_BUILTIN_UNLIKELY(mBinding.ptr == nullptr)) { \ + o2::soa::notBoundTable(#_Table_); \ + } \ + if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ + o2::soa::accessingInvalidIndexFor(#_Getter_); \ + } \ + auto t = mBinding.get(); \ + if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ + o2::soa::dereferenceWithWrongType(#_Getter_, #_Table_); \ + } \ + return t->rawIteratorAt(*mColumnIterator); \ + } \ + \ + auto _Getter_() const \ + { \ + return _Getter_##_as(); \ + } \ + \ + template \ + bool setCurrent(T* current) \ + { \ + if constexpr (o2::soa::is_binding_compatible_v()) { \ + assert(current != nullptr); \ + this->mBinding.bind(current); \ + return true; \ + } \ + return false; \ + } \ + \ + bool setCurrentRaw(o2::soa::Binding current) \ + { \ + this->mBinding = current; \ + return true; \ + } \ + binding_t const* getCurrent() const { return mBinding.get(); } \ + o2::soa::Binding getCurrentRaw() const { return mBinding; } \ + o2::soa::Binding mBinding; \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_##Id { "fIndex" _Label_ _Suffix_, _Name_##Id::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_INDEX_COLUMN_FULL(_Name_, _Getter_, _Type_, _Table_, _Suffix_) DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, #_Table_, _Suffix_) @@ -2894,60 +2883,60 @@ consteval auto getIndexTargets() #define DECLARE_SOA_INDEX_COLUMN_CUSTOM(_Name_, _Getter_, _Label_) DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, int32_t, _Name_##s, _Label_, "") /// SELF -#define DECLARE_SOA_SELF_INDEX_COLUMN_COMPLETE(_Name_, _Getter_, _Type_, _Label_, _IndexTarget_) \ - struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ - static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ - static constexpr const char* mLabel = "fIndex" _Label_; \ - static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ - using base = o2::soa::Column<_Type_, _Name_##Id>; \ - using type = _Type_; \ - using column_t = _Name_##Id; \ - using self_index_t = std::true_type; \ - using compatible_signature = std::conditional, _IndexTarget_, void>; \ - _Name_##Id(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_##Id() = default; \ - _Name_##Id(_Name_##Id const& other) = default; \ - _Name_##Id& operator=(_Name_##Id const& other) = default; \ - type inline getId() const \ - { \ - return _Getter_##Id(); \ - } \ - \ - type _Getter_##Id() const \ - { \ - return *mColumnIterator; \ - } \ - \ - bool has_##_Getter_() const \ - { \ - return *mColumnIterator >= 0; \ - } \ - \ - template \ - auto _Getter_##_as() const \ - { \ - if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ - o2::soa::accessingInvalidIndexFor(#_Getter_); \ - } \ - auto t = mBinding.get(); \ - if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ - o2::soa::dereferenceWithWrongType(#_Getter_, "self"); \ - } \ - return t->rawIteratorAt(*mColumnIterator); \ - } \ - \ - bool setCurrentRaw(o2::soa::Binding current) \ - { \ - this->mBinding = current; \ - return true; \ - } \ - o2::soa::Binding getCurrentRaw() const { return mBinding; } \ - o2::soa::Binding mBinding; \ - }; \ +#define DECLARE_SOA_SELF_INDEX_COLUMN_COMPLETE(_Name_, _Getter_, _Type_, _Label_, _IndexTarget_) \ + struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ + static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ + static constexpr const char* mLabel = "fIndex" _Label_; \ + static constexpr const uint32_t hash = compile_time_hash(o2::framework::namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ + using base = o2::soa::Column<_Type_, _Name_##Id>; \ + using type = _Type_; \ + using column_t = _Name_##Id; \ + using self_index_t = std::true_type; \ + using compatible_signature = std::conditional, _IndexTarget_, void>; \ + _Name_##Id(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_##Id() = default; \ + _Name_##Id(_Name_##Id const& other) = default; \ + _Name_##Id& operator=(_Name_##Id const& other) = default; \ + type inline getId() const \ + { \ + return _Getter_##Id(); \ + } \ + \ + type _Getter_##Id() const \ + { \ + return *mColumnIterator; \ + } \ + \ + bool has_##_Getter_() const \ + { \ + return *mColumnIterator >= 0; \ + } \ + \ + template \ + auto _Getter_##_as() const \ + { \ + if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ + o2::soa::accessingInvalidIndexFor(#_Getter_); \ + } \ + auto t = mBinding.get(); \ + if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ + o2::soa::dereferenceWithWrongType(#_Getter_, "self"); \ + } \ + return t->rawIteratorAt(*mColumnIterator); \ + } \ + \ + bool setCurrentRaw(o2::soa::Binding current) \ + { \ + this->mBinding = current; \ + return true; \ + } \ + o2::soa::Binding getCurrentRaw() const { return mBinding; } \ + o2::soa::Binding mBinding; \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_##Id { "fIndex" _Label_, _Name_##Id::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_SELF_INDEX_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_) DECLARE_SOA_SELF_INDEX_COLUMN_COMPLETE(_Name_, _Getter_, _Type_, _Label_, void) @@ -3360,7 +3349,7 @@ consteval auto getIndexTargets() return std::array{Cs::query...}; \ }(framework::pack<__VA_ARGS__>{}); \ static constexpr const auto ccdb_bindings = [](framework::pack) consteval { \ - return std::array{Cs::mLabel...}; \ + return std::array{o2::framework::with_namespace_prefix()...}; \ }(framework::pack<__VA_ARGS__>{}); \ static constexpr const auto ccdb_run_dependent = [](framework::pack) consteval { \ return std::array{Cs::run_dependent...}; \ diff --git a/Framework/Core/include/Framework/Configurable.h b/Framework/Core/include/Framework/Configurable.h index 3cbd1839b7d89..7188ea19a52be 100644 --- a/Framework/Core/include/Framework/Configurable.h +++ b/Framework/Core/include/Framework/Configurable.h @@ -12,6 +12,7 @@ #define O2_FRAMEWORK_CONFIGURABLE_H_ #include "Framework/ConfigurableKinds.h" #include "Framework/Traits.h" +#include "Framework/TypeIdHelpers.h" #include #include namespace o2::framework @@ -96,7 +97,7 @@ using MutableConfigurable = Configurable>; template struct ConfigurableCCDBPath : Configurable { ConfigurableCCDBPath() - : Configurable{std::string{"ccdb:"} + Column::mLabel, + : Configurable{std::string{"ccdb:"} + std::string{o2::framework::with_namespace_prefix()}, std::string{Column::query}, std::string{"CCDB path for "} + Column::mLabel + " (default: " + Column::query + ")"} { diff --git a/Framework/Foundation/include/Framework/TypeIdHelpers.h b/Framework/Foundation/include/Framework/TypeIdHelpers.h index 1dc2464b40ec8..4e1fdbe77163b 100644 --- a/Framework/Foundation/include/Framework/TypeIdHelpers.h +++ b/Framework/Foundation/include/Framework/TypeIdHelpers.h @@ -81,6 +81,23 @@ struct TypeIdHelpers { } }; +template +consteval static std::string_view namespace_prefix() +{ + constexpr auto name = o2::framework::type_name(); + const auto pos = name.rfind(std::string_view{":"}); + return name.substr(0, pos + 1); +} + +template +consteval static std::string_view with_namespace_prefix() +{ + constexpr auto name = o2::framework::type_name(); + const auto pos1 = name.rfind(std::string_view{"::"}); + const auto pos2 = name.substr(0, pos1).rfind(std::string_view{"::"}); + return name.substr(pos2 + 2, name.size()); +} + } // namespace o2::framework #endif // O2_FRAMEWORK_TYPEIDHELPERS_H_ From 09167467938857ca69b86076258cf33b718632ab Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 16 Oct 2026 11:52:02 +0200 Subject: [PATCH 2/6] reconstruct column name properly --- Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx | 3 ++- Framework/Core/src/runDataProcessing.cxx | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 8812d61e7d369..236a96c123cfb 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -125,7 +125,8 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) fieldMetadata->Append("url", it != ccdbUrls.end() ? it->second : m.defaultValue.asString()); auto runDep = runDependent.find("ccdb-run-dependent:" + m.name.substr(strlen("ccdb:"))); fieldMetadata->Append("runDependent", runDep != runDependent.end() ? runDep->second : "0"); - auto columnName = m.name.substr(strlen("ccdb:")); + auto pos = m.name.rfind(std::string_view{"::"}); + auto columnName = std::string{"f"} + m.name.substr(pos + 2, m.name.size()); fields.emplace_back(std::make_shared(columnName, soa::asArrowDataType(), false, fieldMetadata)); } schemas.emplace_back(std::make_shared(fields, schemaMetadata)); diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index 8cfbfddcc4067..a86e70c4f4ab0 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -1699,13 +1699,13 @@ int runStateMachine(DataProcessorSpecs const& workflow, if (!hasMetadata) { continue; } - // ignore devices with no control options - auto hasControls = std::ranges::any_of(device.inputs, [](InputSpec const& spec) { + // ignore devices with no control or ccdb options + auto hasControlsOrCcdbs = std::ranges::any_of(device.inputs, [](InputSpec const& spec) { return std::ranges::any_of(spec.metadata, [](ConfigParamSpec const& param) { - return param.type == VariantType::Bool && param.name.find("control:") != std::string::npos; + return param.type == VariantType::Bool && (param.name.starts_with("control:") || param.name.starts_with("ccdb:")); }); }); - if (!hasControls) { + if (!hasControlsOrCcdbs) { continue; } @@ -1716,7 +1716,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, auto reg = std::make_unique(std::move(configStore)); for (auto& input : device.inputs) { for (auto& param : input.metadata) { - if (param.type == VariantType::Bool && param.name.find("control:") != std::string::npos) { + if (param.type == VariantType::Bool && param.name.starts_with("control:")) { if (param.name != "control:default" && param.name != "control:spawn" && param.name != "control:build" && param.name != "control:define") { auto confName = confNameFromParam(param.name).second; param.defaultValue = reg->get(confName.c_str()); From 9b1bf6de176e7c38aae4ca66009692c0092ac5c8 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 16 Oct 2026 11:58:36 +0200 Subject: [PATCH 3/6] use fmt::format --- Framework/Core/include/Framework/Configurable.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/Configurable.h b/Framework/Core/include/Framework/Configurable.h index 7188ea19a52be..7524d6c204868 100644 --- a/Framework/Core/include/Framework/Configurable.h +++ b/Framework/Core/include/Framework/Configurable.h @@ -13,6 +13,7 @@ #include "Framework/ConfigurableKinds.h" #include "Framework/Traits.h" #include "Framework/TypeIdHelpers.h" +#include #include #include namespace o2::framework @@ -97,7 +98,7 @@ using MutableConfigurable = Configurable>; template struct ConfigurableCCDBPath : Configurable { ConfigurableCCDBPath() - : Configurable{std::string{"ccdb:"} + std::string{o2::framework::with_namespace_prefix()}, + : Configurable{fmt::format("ccdb:{}", o2::framework::with_namespace_prefix()), std::string{Column::query}, std::string{"CCDB path for "} + Column::mLabel + " (default: " + Column::query + ")"} { From 69c3a0ef5c56012d8e9e07ac2ef20fb8dfd013c3 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 16 Oct 2026 12:06:03 +0200 Subject: [PATCH 4/6] more fmt::format --- Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 236a96c123cfb..5bd21ce6bdf3f 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -126,7 +126,7 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) auto runDep = runDependent.find("ccdb-run-dependent:" + m.name.substr(strlen("ccdb:"))); fieldMetadata->Append("runDependent", runDep != runDependent.end() ? runDep->second : "0"); auto pos = m.name.rfind(std::string_view{"::"}); - auto columnName = std::string{"f"} + m.name.substr(pos + 2, m.name.size()); + auto columnName = fmt::format("f{}", m.name.substr(pos + 2, m.name.size())); fields.emplace_back(std::make_shared(columnName, soa::asArrowDataType(), false, fieldMetadata)); } schemas.emplace_back(std::make_shared(fields, schemaMetadata)); From 042b4fc1a168d5156711ae9ade4e32e4d57d0ea3 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 16 Oct 2026 13:05:29 +0200 Subject: [PATCH 5/6] DPL Analysis: update the ccdb path options from configuration During MATERIALIZE_WORKFLOW the read configuration is used to update ccdb urls in in tasks' options, so that later analysis ccdb fetcher will get updated paths in adjustTopology. --- Framework/Core/src/ArrowSupport.cxx | 29 +----------------------- Framework/Core/src/runDataProcessing.cxx | 29 ++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/Framework/Core/src/ArrowSupport.cxx b/Framework/Core/src/ArrowSupport.cxx index cf72696ff7445..584f7397e67f8 100644 --- a/Framework/Core/src/ArrowSupport.cxx +++ b/Framework/Core/src/ArrowSupport.cxx @@ -638,34 +638,7 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() analysisCCDB->outputs.clear(); analysisCCDB->inputs.clear(); AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedDYNs, *analysisCCDB); - // Register each ccdb: column path as an actual device option on the CCDB - // device so it can be read from ConfigParamRegistry at runtime. - // If any analysis task declared a Configurable with the same - // "ccdb:fXxx" name, prefer its default over the compile-time ::query value. - // First encountered wins; log a warning if two tasks declare conflicting defaults. - for (auto& input : dec.analysisCCDBInputs) { - for (auto& m : input.metadata | std::views::filter(checks::has_params_with_name_starting("ccdb:"))) { - ConfigParamSpec effective = m; // start with compile-time default - bool foundFirst = false; - for (auto& d : workflow | views::exclude_by_name(analysisCCDB->name)) { - for (auto& opt : d.options) { - if (opt.name == m.name) { - if (!foundFirst) { - effective = opt; // first task Configurable wins - foundFirst = true; - } else if (opt.defaultValue.asString() != effective.defaultValue.asString()) { - LOGP(warn, "Task '{}' declares Configurable '{}' = '{}' which conflicts " - "with an earlier value '{}'; earlier value will be used.", - d.name, opt.name, opt.defaultValue.asString(), - effective.defaultValue.asString()); - } - break; - } - } - } - ConfigParamsHelper::addOptionIfMissing(analysisCCDB->options, effective); - } - } + // load real AlgorithmSpec before deployment analysisCCDB->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "AnalysisCCDBFetcherPlugin", ctx); } diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index a86e70c4f4ab0..a7cd50577e559 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -1672,7 +1672,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, /// prune device inputs auto altered_workflow = workflow; - auto confNameFromParam = [](std::string const& paramName) { + auto controlParamFromName = [](std::string const& paramName) { std::regex name_regex(R"(^control:([\w-]+)\/(\w+))"); auto match = std::sregex_token_iterator(paramName.begin(), paramName.end(), name_regex, 0); if (match == std::sregex_token_iterator()) { @@ -1683,6 +1683,8 @@ int runStateMachine(DataProcessorSpecs const& workflow, return std::pair{task, conf}; }; bool altered = false; + // verify uniformity of ccdb request for a each unique column + std::unordered_map ccdbPaths; for (auto& device : altered_workflow) { // ignore internal devices if (device.name.find("internal") != std::string::npos) { @@ -1717,14 +1719,37 @@ int runStateMachine(DataProcessorSpecs const& workflow, for (auto& input : device.inputs) { for (auto& param : input.metadata) { if (param.type == VariantType::Bool && param.name.starts_with("control:")) { + // update inputs from process switches if (param.name != "control:default" && param.name != "control:spawn" && param.name != "control:build" && param.name != "control:define") { - auto confName = confNameFromParam(param.name).second; + auto confName = controlParamFromName(param.name).second; param.defaultValue = reg->get(confName.c_str()); } + } else if (param.type == VariantType::String && param.name.starts_with("ccdb:")) { + // update inputs ccdb urls from configuration + param.defaultValue = reg->get(param.name.c_str()); } } } } + // after updating the urls, verify the consistency + for (auto& input : device.inputs) { + for (auto& param : input.metadata) { + if (param.type != VariantType::String || param.name.starts_with("ccdb:")) { + continue; + } + if (ccdbPaths.contains(param.name)) { + if (ccdbPaths[param.name] != param.defaultValue.get()) { + throw runtime_error_f("Task {} has a CCDB path inconsistent with another found in same workflow: {} vs {}", + device.name.c_str(), + param.defaultValue.get().c_str(), + ccdbPaths[param.name].c_str()); + } + } else { + ccdbPaths.emplace(param.name, param.defaultValue.get()); + } + } + } + /// FIXME: use commandline arguments as alternative LOGP(debug, "Original inputs: "); for (auto& input : device.inputs) { From 7abba7b2d900517470ce9b56ccdf1bd37ad34406 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 16 Oct 2026 14:12:31 +0200 Subject: [PATCH 6/6] fix condition and error message --- Framework/Core/src/runDataProcessing.cxx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index a7cd50577e559..3a19bb71d5e88 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -1734,15 +1734,16 @@ int runStateMachine(DataProcessorSpecs const& workflow, // after updating the urls, verify the consistency for (auto& input : device.inputs) { for (auto& param : input.metadata) { - if (param.type != VariantType::String || param.name.starts_with("ccdb:")) { + if (param.type != VariantType::String || !param.name.starts_with("ccdb:")) { continue; } if (ccdbPaths.contains(param.name)) { if (ccdbPaths[param.name] != param.defaultValue.get()) { - throw runtime_error_f("Task {} has a CCDB path inconsistent with another found in same workflow: {} vs {}", - device.name.c_str(), - param.defaultValue.get().c_str(), - ccdbPaths[param.name].c_str()); + throw runtime_error_f(fmt::format("Task {} has a CCDB path inconsistent with another found in same workflow: {} vs {}", + device.name.c_str(), + param.defaultValue.get().c_str(), + ccdbPaths[param.name].c_str()) + .c_str()); } } else { ccdbPaths.emplace(param.name, param.defaultValue.get());