From 64f7f35a00b44a229064ac5ac9596be8d0cbf2a6 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 29 Jun 2026 15:41:59 +0200 Subject: [PATCH 01/25] initial --- Framework/Core/include/Framework/ASoA.h | 100 ++++++-------------- Framework/Core/include/Framework/Concepts.h | 98 +++++++++++++++++++ 2 files changed, 128 insertions(+), 70 deletions(-) create mode 100644 Framework/Core/include/Framework/Concepts.h diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index a21532e42e692..9cde267252011 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -15,7 +15,7 @@ #if defined(__CLING__) #error "Please do not include this file in ROOT dictionary generation" #endif - +#include "Framework/Concepts.h" #include "Framework/ConcreteDataMatcher.h" #include "Framework/Pack.h" // IWYU pragma: export #include "Framework/FunctionalHelpers.h" // IWYU pragma: export @@ -189,30 +189,13 @@ consteval auto intersectOriginals() namespace o2::soa { -struct Binding; - -template -concept not_void = requires { !std::same_as; }; - -/// column identification concepts -template -concept is_persistent_column = requires(C c) { c.mColumnIterator; }; - +/// column identification template constexpr bool is_persistent_v = is_persistent_column; template using is_persistent_column_t = std::conditional_t, std::true_type, std::false_type>; -template -concept is_self_index_column = not_void && std::same_as; - -template -concept is_index_column = !is_self_index_column && requires(C c, o2::soa::Binding b) { - { c.setCurrentRaw(b) } -> std::same_as; - requires std::same_as; -}; - template using is_external_index_t = typename std::conditional_t, std::true_type, std::false_type>; @@ -239,6 +222,8 @@ static consteval int getIndexPosToKey_impl() /// Base type for table metadata template struct TableMetadata { + static constexpr void isTableMetadata() + {}; using columns = framework::pack; using persistent_columns_t = framework::selected_pack; using external_index_columns_t = framework::selected_pack; @@ -270,6 +255,8 @@ struct TableMetadata { template struct MetadataTrait { + static constexpr void isMetadataTrait() + {}; using metadata = void; }; @@ -277,6 +264,8 @@ struct MetadataTrait { /// type signature template struct Hash { + static constexpr void isHash() + {}; static constexpr uint32_t hash = H; static constexpr char const* const str{""}; }; @@ -298,6 +287,8 @@ consteval auto filterForKey() #define O2HASH(_Str_) \ template <> \ struct Hash<_Str_ ""_h> { \ + static constexpr void isHash() \ + {}; \ static constexpr uint32_t hash = _Str_ ""_h; \ static constexpr char const* const str{_Str_}; \ }; @@ -306,6 +297,10 @@ consteval auto filterForKey() #define O2ORIGIN(_Str_) \ template <> \ struct Hash<_Str_ ""_h> { \ + static constexpr void isHash() \ + {}; \ + static constexpr void isOriginHash() \ + {}; \ static constexpr header::DataOrigin origin{_Str_}; \ static constexpr uint32_t hash = _Str_ ""_h; \ static constexpr char const* const str{_Str_}; \ @@ -386,13 +381,6 @@ constexpr framework::ConcreteDataMatcher matcher() return {origin(), description(signature()), R.version}; } -/// hash identification concepts -template -concept is_aod_hash = requires(T t) { t.hash; t.str; }; - -template -concept is_origin_hash = is_aod_hash && requires(T t) { t.origin; }; - /// convert TableRef to a DPL source specification template static constexpr auto sourceSpec() @@ -446,27 +434,6 @@ struct Binding { using SelectionVector = std::vector; -template -concept has_parent_t = not_void; - -template -concept is_metadata = framework::base_of_template; - -template -concept is_metadata_trait = framework::specialization_of_template; - -template -concept has_metadata = is_metadata_trait && not_void; - -template -concept has_extension = is_metadata && not_void; - -template -concept has_configurable_extension = has_extension && requires(T t) { typename T::configurable_t; requires std::same_as; }; - -template -concept is_spawnable_column = std::same_as; - template struct EquivalentIndex { constexpr static bool value = false; @@ -696,6 +663,9 @@ class ColumnIterator : ChunkingPolicy template struct Column { + static constexpr void isIteratableColumn() + {}; + using inherited_t = INHERIT; Column(ColumnIterator const& it) : mColumnIterator{it} @@ -730,6 +700,8 @@ struct Column { /// method call. template struct DynamicColumn { + static constexpr void isDynamicColumn() + {}; using inherited_t = INHERIT; static constexpr const char* const& columnLabel() { return INHERIT::mLabel; } @@ -737,6 +709,8 @@ struct DynamicColumn { template struct IndexColumn { + static constexpr void isEnumeratingColumn() + {}; using inherited_t = INHERIT; static constexpr const uint32_t hash = 0; @@ -745,6 +719,8 @@ struct IndexColumn { template struct MarkerColumn { + static constexpr void isMarkingColumn() + {}; using inherited_t = INHERIT; static constexpr const uint32_t hash = 0; @@ -846,29 +822,6 @@ struct Index : o2::soa::IndexColumn> { std::tuple rowOffsets; }; -template -concept is_indexing_column = requires(C& c) { - c.rowIndices; - c.rowOffsets; -}; - -template -concept is_dynamic_column = requires(C& c) { - c.boundIterators; -}; - -template -concept is_marker_column = requires { &C::mark; }; - -template -using is_dynamic_t = std::conditional_t, std::true_type, std::false_type>; - -template -concept is_column = is_persistent_column || is_dynamic_column || is_indexing_column || is_marker_column; - -template -using is_indexing_t = std::conditional_t, std::true_type, std::false_type>; - struct IndexPolicyBase { /// Position inside the current table int64_t mRowIndex = 0; @@ -877,10 +830,13 @@ struct IndexPolicyBase { }; struct RowViewSentinel { + static constexpr void isRowViewSentinel() + {}; int64_t const index; }; struct FilteredIndexPolicy : IndexPolicyBase { + static constexpr void isFilteredIndexPolicy(); // We use -1 in the IndexPolicyBase to indicate that the index is // invalid. What will validate the index is the this->setCursor() // which happens below which will properly setup the first index @@ -986,6 +942,8 @@ struct FilteredIndexPolicy : IndexPolicyBase { }; struct DefaultIndexPolicy : IndexPolicyBase { + static constexpr void isDefaultIndexPolicy() + {}; /// Needed to be able to copy the policy DefaultIndexPolicy() = default; DefaultIndexPolicy(DefaultIndexPolicy&&) = default; @@ -1088,6 +1046,8 @@ concept has_index = (is_indexing_column || ...); template struct TableIterator : IP, C... { public: + static constexpr void isTableIterator() + {}; using self_t = TableIterator; using policy_t = IP; using all_columns = framework::pack; diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h new file mode 100644 index 0000000000000..715081b33b410 --- /dev/null +++ b/Framework/Core/include/Framework/Concepts.h @@ -0,0 +1,98 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_FRAMEWORK_CONCEPTS_H +#define O2_FRAMEWORK_CONCEPTS_H + +#include + +namespace o2::aod +{ +/// hash +template +concept is_aod_hash = requires(T t) { &T::isHash; }; + +template +concept is_origin_hash = requires(T t) { &T::isOriginHash; }; +} // namespace o2::aod + +namespace o2::soa +{ +/// general +template +concept not_void = requires { requires !std::same_as; }; + +/// columns +template +concept is_persistent_column = requires(C c) { &C::isIteratableColumn; }; + +template +concept is_self_index_column = not_void && std::same_as; + +/// FIXME: this should really rely on the struct's content instead +struct Binding; +template +concept is_index_column = !is_self_index_column && requires(C c, o2::soa::Binding b) { + { c.setCurrentRaw(b) } -> std::same_as; + requires std::same_as; +}; + +template +concept is_spawnable_column = std::same_as; + +template +concept is_indexing_column = requires(C c) { &C::isEnumeratingColumn; }; + +template +concept is_dynamic_column = requires(C c) { &C::isDynamicColumn; }; + +template +concept is_marker_column = requires { &C::isMarkingColumn; }; + +template +concept is_column = is_persistent_column || is_dynamic_column || is_indexing_column || is_marker_column; + +/// pack filtering helpers +template +using is_dynamic_t = std::conditional_t, std::true_type, std::false_type>; + +template +using is_indexing_t = std::conditional_t, std::true_type, std::false_type>; + +/// tables, iterators and metadata +template +concept has_parent_t = not_void; + +/// FIXME: this should really rely on the struct's content instead +template +concept is_metadata_trait = requires(T t) { &T::isMetadataTrait; }; + +/// FIXME: this should really rely on the struct's content instead +template +concept is_metadata = requires(T t) { &T::isTableMetadata; }; + +template +concept has_metadata = is_metadata_trait && not_void; + +template +concept has_extension = is_metadata && not_void; + +template +concept has_configurable_extension = has_extension && requires(T t) { typename T::configurable_t; requires std::same_as; }; + +} // namespace o2::soa + +namespace o2::framework +{ + +} // namespace o2::framework + +#endif // O2_FRAMEWORK_CONCEPTS_H From ba2666a9aaf5c01e281c982c1e89f4dd050418df Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Tue, 30 Jun 2026 13:53:51 +0200 Subject: [PATCH 02/25] move concepts out of ASoA.h --- Framework/Core/include/Framework/ASoA.h | 118 +++-------------- Framework/Core/include/Framework/Concepts.h | 139 +++++++++++++++++++- 2 files changed, 153 insertions(+), 104 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 9cde267252011..871d4cebd21be 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1018,15 +1018,6 @@ struct DefaultIndexPolicy : IndexPolicyBase { int64_t mMaxRow = 0; }; -// template -// class Table; - -template -class Table; - -template -concept is_table = framework::specialization_of_template || framework::base_of_template; - /// Similar to a pair but not a pair, to avoid /// exposing the second type everywhere. template @@ -1035,14 +1026,6 @@ struct ColumnDataHolder { arrow::ChunkedArray* second; }; -template -concept can_bind = requires(T&& t) { - { t.B::mColumnIterator }; -}; - -template -concept has_index = (is_indexing_column || ...); - template struct TableIterator : IP, C... { public: @@ -1272,48 +1255,6 @@ struct ArrowHelpers { static std::shared_ptr concatTables(std::vector>&& tables); }; -//! Helper to check if a type T is an iterator -template -concept is_iterator = framework::base_of_template || framework::specialization_of_template; - -template -concept is_table_or_iterator = is_table || is_iterator; - -template -concept with_originals = requires { - T::originals.size(); -}; - -template -concept with_sources = requires { - T::sources.size(); -}; - -template -concept with_sources_generator = requires(T t) { - t.template generateSources>(); -}; - -template -concept with_ccdb_urls = requires { - T::ccdb_urls.size(); -}; - -template -concept with_base_table = requires { - typename aod::MetadataTrait>::metadata::base_table_t; -}; - -template -concept with_expression_pack = requires { - typename T::expression_pack_t{}; -}; - -template -concept with_index_pack = requires { - typename T::index_pack_t{}; -}; - template os1, size_t N2, std::array os2> consteval bool is_compatible() { @@ -1336,12 +1277,6 @@ consteval bool is_binding_compatible_v() template using is_binding_compatible = std::conditional_t(), std::true_type, std::false_type>; -template -struct IndexTable; - -template -concept is_index_table = framework::specialization_of_template; - template static constexpr std::string getLabelForTable() { @@ -1469,6 +1404,8 @@ namespace o2::framework { /// tracks origin in bindingKey matcher to handle the correct arguments struct PreslicePolicyBase { + static constexpr void isPreslicePolicy() + {}; const std::string binding; Entry bindingKey; @@ -1495,11 +1432,10 @@ struct PreslicePolicyGeneral : public PreslicePolicyBase { std::span getSliceFor(int value) const; }; -template -concept is_preslice_policy = std::derived_from; - template struct PresliceBase : public Policy { + static constexpr void isPresliceContainer() + {}; constexpr static bool optional = OPT; using target_t = T; using policy_t = Policy; @@ -1540,13 +1476,6 @@ using Preslice = PresliceBase; template using PresliceOptional = PresliceBase; -template -concept is_preslice = std::derived_from&& - requires(T) -{ - T::optional; -}; - /// Can be user to group together a number of Preslice declaration /// to avoid the limit of 100 data members per task /// @@ -1560,11 +1489,9 @@ concept is_preslice = std::derived_from&& /// /// preslices.perCol; struct PresliceGroup { + static constexpr void isPresliceGroup() + {}; }; - -template -concept is_preslice_group = std::derived_from; - } // namespace o2::framework namespace o2::soa @@ -1574,25 +1501,10 @@ class FilteredBase; template class Filtered; -template -concept has_filtered_policy = not_void && std::same_as; - -template -concept is_filtered_iterator = is_iterator && has_filtered_policy; - -template -concept is_filtered_table = framework::base_of_template; - // FIXME: compatbility declaration to be removed template constexpr bool is_soa_filtered_v = is_filtered_table; -template -concept is_filtered = is_filtered_table || is_filtered_iterator; - -template -concept is_not_filtered_table = is_table && !is_filtered_table; - /// Helper function to extract bound indices template static consteval auto extractBindings(framework::pack) @@ -1804,6 +1716,8 @@ template ; using table_t = self_t; @@ -2297,7 +2211,7 @@ concept dynamic_with_common_getter = is_dynamic_column && }; template -concept persistent_with_common_getter = is_persistent_v && requires(T t) { +concept persistent_with_common_getter = is_persistent_column && requires(T t) { { t.get() } -> std::convertible_to; }; @@ -3409,6 +3323,8 @@ namespace o2::soa { template struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...> { + static constexpr void isJoin() + {}; using base = Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...>; Join(std::shared_ptr&& table, uint64_t offset = 0) @@ -3517,9 +3433,6 @@ constexpr auto join(Ts const&... t) return Join(ArrowHelpers::joinTables({t.asArrowTable()...}, std::span{Join::base::originalLabels})); } -template -concept is_join = framework::specialization_of_template; - template constexpr bool is_soa_join_v = is_join; @@ -3565,6 +3478,8 @@ template class FilteredBase : public T { public: + static constexpr void isFilteredBase() + {}; using self_t = FilteredBase; using table_t = typename T::table_t; using T::originals; @@ -4221,6 +4136,8 @@ struct IndexTable : Table { template struct SmallGroupsBase : public Filtered { + static constexpr void isSmallGroups() + {}; static constexpr bool applyFilters = APPLY; SmallGroupsBase(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) : Filtered(std::move(tables), selection, offset) {} @@ -4237,11 +4154,6 @@ using SmallGroups = SmallGroupsBase; template using SmallGroupsUnfiltered = SmallGroupsBase; - -template -concept is_smallgroups = requires { - [](SmallGroupsBase*) {}(std::declval*>()); -}; } // namespace o2::soa #endif // O2_FRAMEWORK_ASOA_H_ diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h index 715081b33b410..b512a087b7a01 100644 --- a/Framework/Core/include/Framework/Concepts.h +++ b/Framework/Core/include/Framework/Concepts.h @@ -12,31 +12,42 @@ #ifndef O2_FRAMEWORK_CONCEPTS_H #define O2_FRAMEWORK_CONCEPTS_H +#include #include namespace o2::aod { +template +struct Hash; /// hash +/// 1. require aod::Hash template concept is_aod_hash = requires(T t) { &T::isHash; }; - +/// 2. requires aod::Hash with header::DataOrigin template concept is_origin_hash = requires(T t) { &T::isOriginHash; }; + +template +struct MetadataTrait; } // namespace o2::aod namespace o2::soa { /// general +/// require a type to be not void template concept not_void = requires { requires !std::same_as; }; /// columns +/// 1. require a storage-backed column template concept is_persistent_column = requires(C c) { &C::isIteratableColumn; }; +/// 2. require self-index column template concept is_self_index_column = not_void && std::same_as; +/// 3. require bidable index column /// FIXME: this should really rely on the struct's content instead struct Binding; template @@ -45,21 +56,36 @@ concept is_index_column = !is_self_index_column && requires(C c, o2::soa::Bin requires std::same_as; }; +/// 4. require a column that can be created from an expression template concept is_spawnable_column = std::same_as; +/// 5. require an enumerating column, like soa::Index template concept is_indexing_column = requires(C c) { &C::isEnumeratingColumn; }; +/// 6. require a dynamic column template concept is_dynamic_column = requires(C c) { &C::isDynamicColumn; }; +/// 7. require a marking column template concept is_marker_column = requires { &C::isMarkingColumn; }; +/// 8. require any supported column template concept is_column = is_persistent_column || is_dynamic_column || is_indexing_column || is_marker_column; +/// 9. require a type that can be bound as a column of type B +template +concept can_bind = requires(T&& t) { + { t.B::mColumnIterator }; +}; + +/// 10. require at least one column in an exploded pack to be an indexing column +template +concept has_index = (is_indexing_column || ...); + /// pack filtering helpers template using is_dynamic_t = std::conditional_t, std::true_type, std::false_type>; @@ -68,31 +94,142 @@ template using is_indexing_t = std::conditional_t, std::true_type, std::false_type>; /// tables, iterators and metadata +/// 1. require a type with parent_t dependent type template concept has_parent_t = not_void; +/// 2. require a MetadataTrait specialization/descendant /// FIXME: this should really rely on the struct's content instead template concept is_metadata_trait = requires(T t) { &T::isMetadataTrait; }; +/// 3. require a TableMetadata depcialization/descendant /// FIXME: this should really rely on the struct's content instead template concept is_metadata = requires(T t) { &T::isTableMetadata; }; +/// 4. require a type with non-void metadata dependent type template concept has_metadata = is_metadata_trait && not_void; +/// 5. require a type with non-void extension_table_t dependent type template concept has_extension = is_metadata && not_void; +/// 6. require a type with non-void configurable_t dependent type, that is same as true_type template concept has_configurable_extension = has_extension && requires(T t) { typename T::configurable_t; requires std::same_as; }; +/// 7. require an soa::Table +template +concept is_table = requires(T t) { &T::isSOATable(); }; + +/// 8. require a specialization/descendant of a TableIterator +template +concept is_iterator = requires (T t) { &T::isTableIterator(); }; + +/// 9. require a table or iterator +template +concept is_table_or_iterator = is_table || is_iterator; + +/// 10. require soa::IndexTable +template +struct IndexTable; + +template +concept is_index_table = framework::specialization_of_template; + +/// 11. require a type with a filtered policy +template +concept has_filtered_policy = not_void && requires{T::policy_t::isFilteredIndexPolicy();}; + +/// 12. require a filtered table iterator +template +concept is_filtered_iterator = is_iterator && has_filtered_policy; + +/// 13. require a filtered table +template +concept is_filtered_table = requires(T t) { &T::isFilteredBase; }; + +/// 14. require a filtered table or iterator +template +concept is_filtered = is_filtered_table || is_filtered_iterator; + +/// 15. require not filtered table +template +concept is_not_filtered_table = is_table && !is_filtered_table; + +/// 16. require a join +template +concept is_join = requires(T t) { &T::isJoin; }; + +/// misc +/// 1. require a type with originals container +template +concept with_originals = requires { + T::originals.size(); +}; + +/// 2. require a type with sources container +template +concept with_sources = requires { + T::sources.size(); +}; + +/// 3. require a type with sources generator method +/// FIXME: this should really rely on the struct's content instead +template +concept with_sources_generator = requires(T t) { + t.template generateSources>(); +}; + +/// 4. require a type with ccd_urls container +template +concept with_ccdb_urls = requires { + T::ccdb_urls.size(); +}; + +/// 5. require a type, whos metadata has base_table_t dependant type +/// FIXME: this should really rely on the struct's content instead +template +concept with_base_table = with_originals && has_metadata>> && requires { + typename aod::MetadataTrait>::metadata::base_table_t; +}; + +template +concept with_base_table_ng = not_void; // redicrection should be done at the check site + +/// 6. require a type with expression_pack_t dependant type +template +concept with_expression_pack = requires { + typename T::expression_pack_t{}; +}; + +/// 7. require a type with index_pack_t dependant type +template +concept with_index_pack = requires { + typename T::index_pack_t{}; +}; + +/// 8. require SmallGroups +template +concept is_smallgroups = requires(T t) { &T::isSmallGroups; }; } // namespace o2::soa namespace o2::framework { +/// preslice +/// 1. require a preslice policy +template +concept is_preslice_policy = requires(T t) { &T::isPreslicePolicy; }; +/// 2. require a preslice container +template +concept is_preslice = requires(T t) { &T::isPresliceContainer; }; + +/// 3. reqiures a preslice group +template +concept is_preslice_group = requires(T t) { &T::isPresliceGroup; }; } // namespace o2::framework #endif // O2_FRAMEWORK_CONCEPTS_H From b4a525415135ca297ba53c4b323f0ecdde7414fe Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Sat, 4 Jul 2026 18:08:26 +0200 Subject: [PATCH 03/25] fixup! move concepts out of ASoA.h --- Framework/Core/include/Framework/ASoA.h | 6 +++ .../Core/include/Framework/AnalysisHelpers.h | 2 +- Framework/Core/include/Framework/Concepts.h | 54 +++++++++---------- Framework/Core/test/test_Concepts.cxx | 4 ++ 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 871d4cebd21be..5ec52a0ddc246 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -3115,6 +3115,7 @@ consteval auto getIndexTargets() #define DECLARE_SOA_TABLE_METADATA_TRAIT(_Name_, _Desc_, _Version_) \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##Metadata; \ }; @@ -3125,6 +3126,7 @@ consteval auto getIndexTargets() using _Name_ = _Name_##From>; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##Metadata; \ }; @@ -3184,6 +3186,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##ExtensionMetadata; \ }; \ template \ @@ -3218,6 +3221,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##CfgExtensionMetadata; \ }; \ template \ @@ -3253,6 +3257,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##Metadata; \ }; \ template \ @@ -3307,6 +3312,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##TimestampMetadata; \ }; \ template \ diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index c7d567c9eb810..41bddd7500aad 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -493,7 +493,7 @@ class TableConsumer; /// a table. The provided template arguments are if type Column and /// therefore refer only to the persisted columns. template -concept is_producable = soa::has_metadata> || soa::has_metadata>; +concept is_producable = soa::has_metadata>> || soa::has_metadata>>; template concept is_enumerated_iterator = requires(T t) { t.globalIndex(); }; diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h index b512a087b7a01..09cc3ba2687ca 100644 --- a/Framework/Core/include/Framework/Concepts.h +++ b/Framework/Core/include/Framework/Concepts.h @@ -22,12 +22,12 @@ struct Hash; /// hash /// 1. require aod::Hash template -concept is_aod_hash = requires(T t) { &T::isHash; }; +concept is_aod_hash = requires(T t) { t.isHash(); }; /// 2. requires aod::Hash with header::DataOrigin template -concept is_origin_hash = requires(T t) { &T::isOriginHash; }; +concept is_origin_hash = requires(T t) { t.isOriginHash(); }; -template +template struct MetadataTrait; } // namespace o2::aod @@ -41,11 +41,11 @@ concept not_void = requires { requires !std::same_as; }; /// columns /// 1. require a storage-backed column template -concept is_persistent_column = requires(C c) { &C::isIteratableColumn; }; +concept is_persistent_column = requires(C c) { c.isIteratableColumn(); }; /// 2. require self-index column template -concept is_self_index_column = not_void && std::same_as; +concept is_self_index_column = not_void::self_index_t> && std::same_as::self_index_t, std::true_type>; /// 3. require bidable index column /// FIXME: this should really rely on the struct's content instead @@ -58,19 +58,19 @@ concept is_index_column = !is_self_index_column && requires(C c, o2::soa::Bin /// 4. require a column that can be created from an expression template -concept is_spawnable_column = std::same_as; +concept is_spawnable_column = std::same_as::spawnable_t, std::true_type>; /// 5. require an enumerating column, like soa::Index template -concept is_indexing_column = requires(C c) { &C::isEnumeratingColumn; }; +concept is_indexing_column = requires(C c) { c.isEnumeratingColumn(); }; /// 6. require a dynamic column template -concept is_dynamic_column = requires(C c) { &C::isDynamicColumn; }; +concept is_dynamic_column = requires(C c) { c.isDynamicColumn(); }; /// 7. require a marking column template -concept is_marker_column = requires { &C::isMarkingColumn; }; +concept is_marker_column = requires(C c) { c.isMarkingColumn(); }; /// 8. require any supported column template @@ -101,38 +101,39 @@ concept has_parent_t = not_void; /// 2. require a MetadataTrait specialization/descendant /// FIXME: this should really rely on the struct's content instead template -concept is_metadata_trait = requires(T t) { &T::isMetadataTrait; }; +concept is_metadata_trait = requires(T t) { t.isMetadataTrait(); }; /// 3. require a TableMetadata depcialization/descendant /// FIXME: this should really rely on the struct's content instead template -concept is_metadata = requires(T t) { &T::isTableMetadata; }; +concept is_metadata = requires(T t) { t.isTableMetadata(); }; /// 4. require a type with non-void metadata dependent type template -concept has_metadata = is_metadata_trait && not_void; +concept has_metadata = is_metadata::metadata>; /// 5. require a type with non-void extension_table_t dependent type template -concept has_extension = is_metadata && not_void; +concept has_extension = is_metadata && not_void::extension_table_t>; /// 6. require a type with non-void configurable_t dependent type, that is same as true_type template -concept has_configurable_extension = has_extension && requires(T t) { typename T::configurable_t; requires std::same_as; }; +concept has_configurable_extension = has_extension && requires(T t) { typename std::decay_t::configurable_t; requires std::same_as::configurable_t>; }; /// 7. require an soa::Table template -concept is_table = requires(T t) { &T::isSOATable(); }; +concept is_table = requires(T t) { t.isSOATable(); }; /// 8. require a specialization/descendant of a TableIterator template -concept is_iterator = requires (T t) { &T::isTableIterator(); }; +concept is_iterator = requires(T t) { t.isTableIterator(); }; /// 9. require a table or iterator template concept is_table_or_iterator = is_table || is_iterator; /// 10. require soa::IndexTable +/// FIXME: this should really rely on the struct's content instead template struct IndexTable; @@ -141,7 +142,7 @@ concept is_index_table = framework::specialization_of_template -concept has_filtered_policy = not_void && requires{T::policy_t::isFilteredIndexPolicy();}; +concept has_filtered_policy = not_void::policy_t> && requires { std::decay_t::policy_t::isFilteredIndexPolicy(); }; /// 12. require a filtered table iterator template @@ -149,7 +150,7 @@ concept is_filtered_iterator = is_iterator && has_filtered_policy; /// 13. require a filtered table template -concept is_filtered_table = requires(T t) { &T::isFilteredBase; }; +concept is_filtered_table = requires(T t) { t.isFilteredBase(); }; /// 14. require a filtered table or iterator template @@ -161,7 +162,7 @@ concept is_not_filtered_table = is_table && !is_filtered_table; /// 16. require a join template -concept is_join = requires(T t) { &T::isJoin; }; +concept is_join = requires(T t) { t.isJoin(); }; /// misc /// 1. require a type with originals container @@ -177,16 +178,15 @@ concept with_sources = requires { }; /// 3. require a type with sources generator method -/// FIXME: this should really rely on the struct's content instead template concept with_sources_generator = requires(T t) { - t.template generateSources>(); + t.generateSources(); }; /// 4. require a type with ccd_urls container template -concept with_ccdb_urls = requires { - T::ccdb_urls.size(); +concept with_ccdb_urls = requires(T t) { + t.ccdb_urls.size(); }; /// 5. require a type, whos metadata has base_table_t dependant type @@ -213,7 +213,7 @@ concept with_index_pack = requires { /// 8. require SmallGroups template -concept is_smallgroups = requires(T t) { &T::isSmallGroups; }; +concept is_smallgroups = requires(T t) { t.isSmallGroups(); }; } // namespace o2::soa namespace o2::framework @@ -221,15 +221,15 @@ namespace o2::framework /// preslice /// 1. require a preslice policy template -concept is_preslice_policy = requires(T t) { &T::isPreslicePolicy; }; +concept is_preslice_policy = requires(T t) { t.isPreslicePolicy(); }; /// 2. require a preslice container template -concept is_preslice = requires(T t) { &T::isPresliceContainer; }; +concept is_preslice = requires(T t) { t.isPresliceContainer(); }; /// 3. reqiures a preslice group template -concept is_preslice_group = requires(T t) { &T::isPresliceGroup; }; +concept is_preslice_group = requires(T t) { t.isPresliceGroup(); }; } // namespace o2::framework #endif // O2_FRAMEWORK_CONCEPTS_H diff --git a/Framework/Core/test/test_Concepts.cxx b/Framework/Core/test/test_Concepts.cxx index 375e537cfaec0..ff5e0fa6200db 100644 --- a/Framework/Core/test/test_Concepts.cxx +++ b/Framework/Core/test/test_Concepts.cxx @@ -9,6 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +#include "Framework/Concepts.h" #include #include "Framework/ASoA.h" #include "Framework/AnalysisDataModel.h" @@ -87,6 +88,9 @@ TEST_CASE("IdentificationConcepts") REQUIRE(with_originals); + REQUIRE(o2::soa::is_metadata_trait>>); + REQUIRE(o2::soa::has_metadata>>); + REQUIRE(o2::soa::is_metadata>::metadata>); REQUIRE(with_sources_generator>::metadata>); REQUIRE(with_base_table); From 8a5b4950eb07983b95c36436d5c0fde0a869984b Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 6 Jul 2026 09:47:49 +0200 Subject: [PATCH 04/25] move concepts out of AnalysisHelpers.h --- .../Core/include/Framework/AnalysisHelpers.h | 72 ++--------------- Framework/Core/include/Framework/Concepts.h | 81 ++++++++++++++++++- 2 files changed, 86 insertions(+), 67 deletions(-) diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index 41bddd7500aad..77e9ed760b1e0 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -492,12 +492,6 @@ class TableConsumer; /// Helper class actually implementing the cursor which can write to /// a table. The provided template arguments are if type Column and /// therefore refer only to the persisted columns. -template -concept is_producable = soa::has_metadata>> || soa::has_metadata>>; - -template -concept is_enumerated_iterator = requires(T t) { t.globalIndex(); }; - template struct WritingCursor { public: @@ -579,13 +573,13 @@ struct WritingCursor { decltype(FFL(std::declval())) cursor; private: - static decltype(auto) extract(is_enumerated_iterator auto const& arg) + static decltype(auto) extract(soa::is_enumerated_iterator auto const& arg) { return arg.globalIndex(); } template - requires(!is_enumerated_iterator) + requires(!soa::is_enumerated_iterator) static decltype(auto) extract(A&& arg) { return arg; @@ -642,9 +636,6 @@ template struct Produces : WritingCursor { }; -template -concept is_produces = requires(T t) { typename T::cursor_t; typename T::persistent_table_t; &T::cursor; }; - /// Use this to group together produces. Useful to separate them logically /// or simply to stay within the 100 elements per Task limit. /// Use as: @@ -654,11 +645,10 @@ concept is_produces = requires(T t) { typename T::cursor_t; typename T::persiste /// /// Notice the label MySetOfProduces is just a mnemonic and can be omitted. struct ProducesGroup { + static constexpr void isProducesGroup() + {}; }; -template -concept is_produces_group = std::derived_from; - /// Helper template for table transformations template struct TableTransform { @@ -682,12 +672,6 @@ struct TableTransform { /// This helper struct allows you to declare extended tables which should be /// created by the task (as opposed to those pre-defined by data model) -template -concept is_spawnable = soa::has_metadata>> && soa::has_extension>::metadata>; - -template -concept is_dynamically_spawnable = soa::has_metadata>> && soa::has_configurable_extension>::metadata>; - template consteval auto transformBase() { @@ -736,13 +720,6 @@ struct Spawns : decltype(transformBase()) { }(); }; -template -concept is_spawns = requires(T t) { - typename T::metadata; - typename T::expression_pack_t; - requires std::same_as>; -}; - /// This helper struct allows you to declare extended tables with dynamically-supplied /// expressions to be created by the task /// The actual expressions have to be set in init() for the configurable expression @@ -792,15 +769,6 @@ struct Defines : decltype(transformBase()) { template using DefinesDelayed = Defines; -template -concept is_defines = requires(T t) { - typename T::metadata; - typename T::placeholders_pack_t; - requires std::same_as>; - requires std::same_as; - &T::recompile; -}; - /// Policy to control index building /// Exclusive index: each entry in a row has a valid index /// Sparse index: values in a row can be (-1), index table is isomorphic (joinable) to T1 @@ -859,13 +827,6 @@ struct Builds : decltype(transformBase()) { } }; -template -concept is_builds = requires(T t) { - typename T::metadata; - typename T::Key; - requires std::same_as>; -}; - /// a task with rewritten origin, if running together with a task with the default, will /// have a different name and thus its output would be routed separately @@ -877,6 +838,7 @@ concept is_builds = requires(T t) { /// to determine the target file, e.g. analysis result, QA or control histogram, /// etc. template + requires(std::derived_from) struct OutputObj { using obj_t = T; @@ -964,15 +926,6 @@ struct OutputObj { uint32_t mTaskHash; }; -template -concept is_outputobj = requires(T t) { - &T::setHash; - &T::spec; - &T::ref; - requires std::same_as()), typename T::obj_t*>; - requires std::same_as>; -}; - /// This helper allows you to fetch a Sevice from the context or /// by using some singleton. This hopefully will hide the Singleton and /// We will be able to retrieve it in a more thread safe manner later on. @@ -991,12 +944,6 @@ struct Service { } }; -template -concept is_service = requires(T t) { - requires std::same_as; - &T::operator->; -}; - auto getTableFromFilter(soa::is_filtered_table auto const& table, soa::SelectionVector&& selection) { return std::make_unique>>(std::vector{table}, std::forward(selection)); @@ -1016,7 +963,7 @@ void initializePartitionCaches(std::set const& hashes, std::shared_ptr /// the real reason is to provide grouped parts for the process functions that request it /// better solution would be to "slice" the selection, as is already done in GroupSlicer /// for the same purpose, instead of reapplying the filtering -template +template struct Partition { using content_t = T; Partition(expressions::Node&& filter_) : filter{std::forward(filter_)} @@ -1128,13 +1075,6 @@ struct Partition { return mFiltered->size(); } }; - -template -concept is_partition = requires(T t) { - &T::updatePlaceholders; - requires std::same_as; - requires std::same_as>>; -}; } // namespace o2::framework namespace o2::soa diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h index 09cc3ba2687ca..7d6a990e1c762 100644 --- a/Framework/Core/include/Framework/Concepts.h +++ b/Framework/Core/include/Framework/Concepts.h @@ -164,6 +164,10 @@ concept is_not_filtered_table = is_table && !is_filtered_table; template concept is_join = requires(T t) { t.isJoin(); }; +/// 17. require an enumerated iterator +template +concept is_enumerated_iterator = requires(T t) { t.globalIndex(); }; + /// misc /// 1. require a type with originals container template @@ -227,9 +231,84 @@ concept is_preslice_policy = requires(T t) { t.isPreslicePolicy(); }; template concept is_preslice = requires(T t) { t.isPresliceContainer(); }; -/// 3. reqiures a preslice group +/// 3. reqiure a preslice group template concept is_preslice_group = requires(T t) { t.isPresliceGroup(); }; + +/// 4. require a producable entity +template +concept is_producable = soa::has_metadata>> || soa::has_metadata>>; + +/// 5. require produces declaration +template +concept is_produces = requires(T t) { typename T::cursor_t; typename T::persistent_table_t; &T::cursor; }; + +/// 6. require produces group +template +concept is_produces_group = requires(T t) { t.isProducesGroup(); }; + +/// 7. require spawnable entity +template +concept is_spawnable = soa::has_metadata>> && soa::has_extension>::metadata>; + +/// 8. require dynamically spawnable entity +template +concept is_dynamically_spawnable = soa::has_metadata>> && soa::has_configurable_extension>::metadata>; + +/// 9. require spawns declaration +template +concept is_spawns = requires(T t) { + typename T::metadata; + typename T::expression_pack_t; + t.projector.get(); +}; + +/// 10. require defines declaration +template +concept is_defines = requires(T t) { + typename T::metadata; + typename T::placeholders_pack_t; + t.projector.get(); + requires std::same_as; + t.recompile(); +}; + +/// 11. require builds declaration +template +concept is_builds = requires(T t) { + typename T::metadata; + typename T::Key; + t.map.size(); +}; + +/// 12. require outputobj declaration +template +concept is_outputobj = requires(T t) { + &T::setHash; + &T::spec; + &T::ref; + requires std::same_as()), typename T::obj_t*>; + requires std::same_as; +}; + +/// 13. require service declaration +template +concept is_service = requires(T t) { + requires std::same_as; + &T::operator->; +}; + +/// 14. require partition declaration +template +concept is_partition = requires(T t) { + &T::updatePlaceholders; + t.mFiltered.get(); + &T::operator->; + requires std::same_as; + t.begin(); + t.end(); + t.size(); +}; } // namespace o2::framework #endif // O2_FRAMEWORK_CONCEPTS_H From c37f9ac68ce72c40cb70f22541741bdf948c2a41 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Thu, 9 Jul 2026 11:58:33 +0200 Subject: [PATCH 05/25] address FIXMEs --- Framework/Core/include/Framework/ASoA.h | 2 ++ Framework/Core/include/Framework/Concepts.h | 31 +++++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 5ec52a0ddc246..f0d29b3e44eb3 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -4097,6 +4097,8 @@ class Filtered> : public FilteredBase /// First index will be used by process() as the grouping template struct IndexTable : Table { + static constexpr void isIndexTable() + {}; using self_t = IndexTable; using base_t = Table; using table_t = base_t; diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h index 7d6a990e1c762..b96d531a69e56 100644 --- a/Framework/Core/include/Framework/Concepts.h +++ b/Framework/Core/include/Framework/Concepts.h @@ -45,15 +45,20 @@ concept is_persistent_column = requires(C c) { c.isIteratableColumn(); }; /// 2. require self-index column template -concept is_self_index_column = not_void::self_index_t> && std::same_as::self_index_t, std::true_type>; +concept is_self_index_column = requires(C c) +{ + typename C::compatible_signature; + //requires aod::is_aod_hash; + typename C::self_index_t; + requires std::same_as; +}; -/// 3. require bidable index column -/// FIXME: this should really rely on the struct's content instead -struct Binding; +/// 3. require bindable index column template -concept is_index_column = !is_self_index_column && requires(C c, o2::soa::Binding b) { - { c.setCurrentRaw(b) } -> std::same_as; - requires std::same_as; +concept is_index_column = requires(C c) +{ + typename C::binding_t; + requires not_void; }; /// 4. require a column that can be created from an expression @@ -99,12 +104,10 @@ template concept has_parent_t = not_void; /// 2. require a MetadataTrait specialization/descendant -/// FIXME: this should really rely on the struct's content instead template concept is_metadata_trait = requires(T t) { t.isMetadataTrait(); }; /// 3. require a TableMetadata depcialization/descendant -/// FIXME: this should really rely on the struct's content instead template concept is_metadata = requires(T t) { t.isTableMetadata(); }; @@ -133,12 +136,11 @@ template concept is_table_or_iterator = is_table || is_iterator; /// 10. require soa::IndexTable -/// FIXME: this should really rely on the struct's content instead -template -struct IndexTable; - template -concept is_index_table = framework::specialization_of_template; +concept is_index_table = requires(T t) +{ + t.isIndexTable(); +}; /// 11. require a type with a filtered policy template @@ -194,7 +196,6 @@ concept with_ccdb_urls = requires(T t) { }; /// 5. require a type, whos metadata has base_table_t dependant type -/// FIXME: this should really rely on the struct's content instead template concept with_base_table = with_originals && has_metadata>> && requires { typename aod::MetadataTrait>::metadata::base_table_t; From 6fb94f94dbc9d6b63e27d5d0d5157855486a6728 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Thu, 9 Jul 2026 12:06:05 +0200 Subject: [PATCH 06/25] fixup! address FIXMEs --- Framework/Core/include/Framework/AnalysisHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index 77e9ed760b1e0..5600e201feb8f 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -838,7 +838,7 @@ struct Builds : decltype(transformBase()) { /// to determine the target file, e.g. analysis result, QA or control histogram, /// etc. template - requires(std::derived_from) + requires(std::derived_from) struct OutputObj { using obj_t = T; From a81daa373521a91e89ea1b2b995bbd55f1fbe704 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 15 Jun 2026 11:43:02 +0200 Subject: [PATCH 07/25] start --- Framework/Core/include/Framework/ASoA.h | 38 ++++++++++++------- Framework/Core/include/Framework/ArrowTypes.h | 10 +++++ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index f0d29b3e44eb3..c1e1b18f9ddb8 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1663,11 +1663,6 @@ auto select(T const& t, framework::expressions::Filter const& f) arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label); -template -consteval auto base_iter(framework::pack&&) -> TableIterator -{ -} - template requires((sizeof...(Ts) > 0) && (soa::is_column && ...)) consteval auto getColumns() @@ -1726,7 +1721,7 @@ class Table static constexpr const auto originalLabels = [] refs, size_t... Is>(std::index_sequence) { return std::array{o2::aod::label()...}; }.template operator()(std::make_index_sequence()); - static constexpr const uint32_t binding_origin = originals[0].origin_hash; // commonOrigin(); + static constexpr const uint32_t binding_origin = originals[0].origin_hash; static constexpr header::DataOrigin binding_origin_ = o2::aod::Hash::origin; template bindings> @@ -1767,14 +1762,13 @@ class Table using external_index_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); using internal_index_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); template - using base_iterator = decltype(base_iter(columns_t{})); + using base_iterator = decltype([](framework::pack&&) -> TableIterator {}(columns_t{})); template struct TableIteratorBase : base_iterator { using columns_t = typename Parent::columns_t; using external_index_columns_t = typename Parent::external_index_columns_t; using bindings_pack_t = decltype([](framework::pack) -> framework::pack {}(external_index_columns_t{})); - // static constexpr const std::array originals{T::ref...}; static constexpr auto originals = Parent::originals; using policy_t = IP; using parent_t = Parent; @@ -1926,8 +1920,7 @@ class Table using iterator_template = TableIteratorBase; template - static consteval auto full_iter() - { + using iterator_template_o = decltype([](){ if constexpr (sizeof...(Ts) == 0) { return iterator_template{}; } else { @@ -1937,10 +1930,7 @@ class Table return iterator_template{}; } } - } - - template - using iterator_template_o = decltype(full_iter()); + }()); using iterator = iterator_template_o; using filtered_iterator = iterator_template_o; @@ -1954,6 +1944,26 @@ class Table return [](framework::pack) { return std::set{{C::hash...}}; }(columns_t{}); } + Table(o2::soa::ArrowTableRef tableRef) + : mTable(tableRef.tablePtr), + mOffset(tableRef.offset), + mEnd{tableRef.size} + { + if (mTable->num_rows() == 0) { + for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { + mColumnChunks[ci] = nullptr; + } + mBegin = mEnd; + } else { + auto lookups = [this](framework::pack) -> std::array { return {lookupColumn()...}; }(columns_t{}); + for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { + mColumnChunks[ci] = lookups[ci]; + } + mBegin = unfiltered_iterator{mColumnChunks, {mEnd.index, mOffset}}; + mBegin.bindInternalIndices(this); + } + } + Table(std::shared_ptr table, uint64_t offset = 0) : mTable(table), mOffset(offset), diff --git a/Framework/Core/include/Framework/ArrowTypes.h b/Framework/Core/include/Framework/ArrowTypes.h index 2673472a81152..e0425589d5f80 100644 --- a/Framework/Core/include/Framework/ArrowTypes.h +++ b/Framework/Core/include/Framework/ArrowTypes.h @@ -17,6 +17,16 @@ namespace o2::soa { +struct ArrowRange { + uint64_t offset; + int64_t size; +}; + +struct ArrowTableRef { + std::shared_ptr tablePtr; + ArrowRange range; +}; + template struct arrow_array_for { }; From 08e66fd5b5a98f424142c12bf4dec2c21fbeca72 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Wed, 17 Jun 2026 10:51:11 +0200 Subject: [PATCH 08/25] use table ref for soa::Table --- Framework/Core/include/Framework/ASoA.h | 98 +++++++++++-------- Framework/Core/include/Framework/ArrowTypes.h | 14 ++- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index c1e1b18f9ddb8..002ddc0e62450 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1945,11 +1945,10 @@ class Table } Table(o2::soa::ArrowTableRef tableRef) - : mTable(tableRef.tablePtr), - mOffset(tableRef.offset), - mEnd{tableRef.size} + : mArrowTableRef(tableRef), + mEnd{tableRef.range.size} { - if (mTable->num_rows() == 0) { + if (mArrowTableRef.tablePtr->num_rows() == 0) { for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { mColumnChunks[ci] = nullptr; } @@ -1959,40 +1958,40 @@ class Table for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { mColumnChunks[ci] = lookups[ci]; } - mBegin = unfiltered_iterator{mColumnChunks, {mEnd.index, mOffset}}; + mBegin = unfiltered_iterator{mColumnChunks, {mEnd.index, mArrowTableRef.range.offset}}; mBegin.bindInternalIndices(this); } } - Table(std::shared_ptr table, uint64_t offset = 0) - : mTable(table), - mOffset(offset), - mEnd{table->num_rows()} - { - if (mTable->num_rows() == 0) { - for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { - mColumnChunks[ci] = nullptr; - } - mBegin = mEnd; - } else { - auto lookups = [this](framework::pack) -> std::array { return {lookupColumn()...}; }(columns_t{}); - for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { - mColumnChunks[ci] = lookups[ci]; - } - mBegin = unfiltered_iterator{mColumnChunks, {table->num_rows(), offset}}; - mBegin.bindInternalIndices(this); - } - } - - Table(std::vector>&& tables, uint64_t offset = 0) + // Table(std::shared_ptr table, uint64_t offset = 0) + // : mTable(table), + // mOffset(offset), + // mEnd{table->num_rows()} + // { + // if (mTable->num_rows() == 0) { + // for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { + // mColumnChunks[ci] = nullptr; + // } + // mBegin = mEnd; + // } else { + // auto lookups = [this](framework::pack) -> std::array { return {lookupColumn()...}; }(columns_t{}); + // for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { + // mColumnChunks[ci] = lookups[ci]; + // } + // mBegin = unfiltered_iterator{mColumnChunks, {table->num_rows(), offset}}; + // mBegin.bindInternalIndices(this); + // } + // } + + Table(std::vector>&& tables, ArrowRange range) requires(ref.origin_hash != "CONC"_h) - : Table(ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels}), offset) + : Table({ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels}), range}) { } - Table(std::vector>&& tables, uint64_t offset = 0) + Table(std::vector>&& tables, ArrowRange range) requires(ref.origin_hash == "CONC"_h) - : Table(ArrowHelpers::concatTables(std::move(tables)), offset) + : Table({ArrowHelpers::concatTables(std::move(tables)), range}) { } @@ -2042,7 +2041,7 @@ class Table // is held by the table, so we are safe passing the bare pointer. If it does it // means that the iterator on a table is outliving the table itself, which is // a bad idea. - return filtered_iterator(mColumnChunks, {selection, mTable->num_rows(), mOffset}); + return filtered_iterator(mColumnChunks, {selection, mArrowTableRef.tablePtr->num_rows(), mArrowTableRef.range.offset}); } iterator iteratorAt(uint64_t i) const @@ -2070,17 +2069,27 @@ class Table /// Return a type erased arrow table backing store for / the type safe table. [[nodiscard]] std::shared_ptr asArrowTable() const { - return mTable; + return mArrowTableRef.tablePtr; + } + + [[nodiscard]] std::shared_ptr asArrowTableConstrained() const + { + return mArrowTableRef.tablePtr->Slice(mArrowTableRef.range.offset, mArrowTableRef.range.size); + } + + [[nodiscard]] ArrowTableRef asArrowTableRef() const + { + return mArrowTableRef; } /// Return offset auto offset() const { - return mOffset; + return mArrowTableRef.range.offset; } /// Size of the table, in rows. [[nodiscard]] int64_t size() const { - return mTable->num_rows(); + return mArrowTableRef.tablePtr->num_rows(); } [[nodiscard]] int64_t tableSize() const @@ -2166,27 +2175,30 @@ class Table auto rawSlice(uint64_t start, uint64_t end) const { - return self_t{mTable->Slice(start, end - start + 1), start}; + return self_t{mArrowTableRef.slice({start, static_cast(end - start + 1)})}; } auto emptySlice() const { - return self_t{mTable->Slice(0, 0), 0}; + return self_t{mArrowTableRef.makeEmpty()}; } private: template arrow::ChunkedArray* lookupColumn() { - if constexpr (soa::is_persistent_column) { - auto label = T::columnLabel(); - return getIndexFromLabel(mTable.get(), label); - } else { - return nullptr; - } + return nullptr; } - std::shared_ptr mTable = nullptr; - uint64_t mOffset = 0; + + template + arrow::ChunkedArray* lookupColumn() + { + return getIndexFromLabel(mArrowTableRef.tablePtr.get(), T::columnLabel()); + } + + ArrowTableRef mArrowTableRef; + // std::shared_ptr mTable = nullptr; + // uint64_t mOffset = 0; // Cached pointers to the ChunkedArray associated to a column arrow::ChunkedArray* mColumnChunks[framework::pack_size(columns_t{})]; RowViewSentinel mEnd; diff --git a/Framework/Core/include/Framework/ArrowTypes.h b/Framework/Core/include/Framework/ArrowTypes.h index e0425589d5f80..e045b65b8ed37 100644 --- a/Framework/Core/include/Framework/ArrowTypes.h +++ b/Framework/Core/include/Framework/ArrowTypes.h @@ -23,8 +23,18 @@ struct ArrowRange { }; struct ArrowTableRef { - std::shared_ptr tablePtr; - ArrowRange range; + std::shared_ptr tablePtr = nullptr; + ArrowRange range{0, 0}; + + ArrowTableRef makeEmpty() const + { + return {tablePtr, {0, 0}}; + } + + ArrowTableRef slice(ArrowRange newRange) const + { + return {tablePtr, newRange}; + } }; template From c0b5e598a843a2ac9baf8b5e3b59b1004775bdc7 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Wed, 17 Jun 2026 10:51:27 +0200 Subject: [PATCH 09/25] minor fixes --- Framework/Core/include/Framework/ASoA.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 002ddc0e62450..1f94e4d792d66 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -2301,13 +2301,13 @@ namespace o2::aod O2ORIGIN("AOD"); O2ORIGIN("AOD1"); O2ORIGIN("AOD2"); -// O2ORIGIN("DYN"); -// O2ORIGIN("IDX"); -// O2ORIGIN("ATIM"); + O2ORIGIN("JOIN"); O2HASH("JOIN/0"); + O2ORIGIN("CONC"); O2HASH("CONC/0"); + O2ORIGIN("TEST"); O2HASH("TEST/0"); } // namespace o2::aod From 6e8bcc632ef90941f01b9da7c913c4368511f2de Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Wed, 17 Jun 2026 13:05:19 +0200 Subject: [PATCH 10/25] join/concat using ranges --- Framework/Core/include/Framework/ASoA.h | 41 +++---- Framework/Core/include/Framework/ArrowTypes.h | 23 ++++ Framework/Core/src/ASoA.cxx | 102 +++++++++++------- 3 files changed, 100 insertions(+), 66 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 1f94e4d792d66..f9de7c0b4b1ec 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1238,7 +1238,6 @@ struct TableIterator : IP, C... { { static_assert(std::same_as(this)->mColumnIterator)), std::decay_t*>, "foo"); return &(static_cast(this)->mColumnIterator); - // return static_cast*>(nullptr); } template @@ -1249,10 +1248,10 @@ struct TableIterator : IP, C... { }; struct ArrowHelpers { - static std::shared_ptr joinTables(std::vector>&& tables); - static std::shared_ptr joinTables(std::vector>&& tables, std::span labels); - static std::shared_ptr joinTables(std::vector>&& tables, std::span labels); - static std::shared_ptr concatTables(std::vector>&& tables); + static o2::soa::ArrowTableRef joinTables(std::vector&& tables); + static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); + static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); + static o2::soa::ArrowTableRef concatTables(std::vector&& tables); }; template os1, size_t N2, std::array os2> @@ -1963,26 +1962,6 @@ class Table } } - // Table(std::shared_ptr table, uint64_t offset = 0) - // : mTable(table), - // mOffset(offset), - // mEnd{table->num_rows()} - // { - // if (mTable->num_rows() == 0) { - // for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { - // mColumnChunks[ci] = nullptr; - // } - // mBegin = mEnd; - // } else { - // auto lookups = [this](framework::pack) -> std::array { return {lookupColumn()...}; }(columns_t{}); - // for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { - // mColumnChunks[ci] = lookups[ci]; - // } - // mBegin = unfiltered_iterator{mColumnChunks, {table->num_rows(), offset}}; - // mBegin.bindInternalIndices(this); - // } - // } - Table(std::vector>&& tables, ArrowRange range) requires(ref.origin_hash != "CONC"_h) : Table({ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels}), range}) @@ -1995,6 +1974,18 @@ class Table { } + Table(std::vector&& tables) + requires(ref.origin_hash != "CONC"_h) + : Table(ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels})) + { + } + + Table(std::vector&& tables) + requires(ref.origin_hash == "CONC"_h) + : Table(ArrowHelpers::concatTables(std::move(tables))) + { + } + template inline arrow::ChunkedArray* getIndexToKey() { diff --git a/Framework/Core/include/Framework/ArrowTypes.h b/Framework/Core/include/Framework/ArrowTypes.h index e045b65b8ed37..cd85db72315e2 100644 --- a/Framework/Core/include/Framework/ArrowTypes.h +++ b/Framework/Core/include/Framework/ArrowTypes.h @@ -11,6 +11,7 @@ #ifndef O2_FRAMEWORK_ARROWTYPES_H #define O2_FRAMEWORK_ARROWTYPES_H +#include #include "Framework/Traits.h" #include "arrow/type_fwd.h" #include @@ -20,12 +21,29 @@ namespace o2::soa struct ArrowRange { uint64_t offset; int64_t size; + + bool operator!=(ArrowRange const& other) const + { + return (offset != other.offset) && (size != other.size); + } }; struct ArrowTableRef { std::shared_ptr tablePtr = nullptr; ArrowRange range{0, 0}; + ArrowTableRef() = default; + ArrowTableRef(std::shared_ptr table) + : tablePtr{table}, + range{0, table->num_rows()} + { + } + ArrowTableRef(std::shared_ptr table, ArrowRange range_) + : tablePtr{table}, + range{range_} + { + } + ArrowTableRef makeEmpty() const { return {tablePtr, {0, 0}}; @@ -35,6 +53,11 @@ struct ArrowTableRef { { return {tablePtr, newRange}; } + + std::shared_ptr const& operator->() const + { + return tablePtr; + } }; template diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index f565fa6e9ce47..93b47eb306fbf 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -69,21 +69,6 @@ SelectionVector sliceSelection(std::span const& mSelectedRows, in return slicedSelection; } -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables) -{ - std::vector> fields; - std::vector> columns; - bool notEmpty = (tables[0]->num_rows() != 0); - std::ranges::for_each(tables, [&fields, &columns, notEmpty](auto const& t) { - std::ranges::copy(t->fields(), std::back_inserter(fields)); - if (notEmpty) { - std::ranges::copy(t->columns(), std::back_inserter(columns)); - } - }); - auto schema = std::make_shared(fields); - return arrow::Table::Make(schema, columns); -} - namespace { template @@ -109,30 +94,68 @@ void canNotJoin(std::vector> const& tables, std::s } } } -} // namespace -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) -{ - if (tables.size() == 1) { - return tables[0]; +template +void IncompatibleRanges(std::vector const& tables, std::span labels) +{ + auto loc = std::ranges::adjacent_find(tables, [](auto const& l, auto const& r){ return l.range != r.range; }); + if (loc != std::ranges::cend(tables)) { + auto pos = std::distance(tables.begin(), loc); + auto next = loc + 1; + if (labels.empty()) { + throw o2::framework::runtime_error_f("Incompatible ranges at %d: (%zu, %z) vs. (%zu, %z)", pos, loc->range.offset, loc->range.size, next->range.offset, next->range.size); + } else { + throw o2::framework::runtime_error_f("Incompatible ranges at %d between %s and %s: (%zu, %z) vs. (%zu, %z)", pos, makeString(labels[pos]), makeString(labels[pos + 1]), loc->range.offset, loc->range.size, next->range.offset, next->range.size); + } } - canNotJoin(tables, labels); - return joinTables(std::forward>>(tables)); } -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +std::shared_ptr joinTablesImpl(std::ranges::input_range auto tables) +{ + std::vector> fields; + std::vector> columns; + bool notEmpty = (tables.front()->num_rows() != 0); + std::ranges::for_each(tables, [&fields, &columns, notEmpty](auto const& t) { + std::ranges::copy(t->fields(), std::back_inserter(fields)); + if (notEmpty) { + std::ranges::copy(t->columns(), std::back_inserter(columns)); + } + }); + auto schema = std::make_shared(fields); + return arrow::Table::Make(schema, columns); +} + +template + ArrowTableRef joinTablesImpl(std::ranges::input_range auto tables, std::span labels) { if (tables.size() == 1) { - return tables[0]; + return tables.front(); } - canNotJoin(tables, labels); - return joinTables(std::forward>>(tables)); + IncompatibleRanges(tables, labels); + ArrowRange commonRange{tables.front().range}; + return {joinTablesImpl(tables), commonRange}; +} +} // namespace + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables) +{ + return joinTablesImpl(tables, std::span()); } -std::shared_ptr ArrowHelpers::concatTables(std::vector>&& tables) +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables, std::span labels) +{ + return joinTablesImpl(tables, labels); +} + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables, std::span labels) +{ + return joinTablesImpl(tables, labels); +} + +o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector&& tables) { if (tables.size() == 1) { - return tables[0]; + return tables.front(); } std::vector> columns; std::vector> resultFields = tables[0]->schema()->fields(); @@ -140,19 +163,16 @@ std::shared_ptr ArrowHelpers::concatTables(std::vectorEquals(f2)) && (f1->name() < f2->name()); }; - for (size_t i = 1; i < tables.size(); ++i) { - auto& fields = tables[i]->schema()->fields(); + std::ranges::for_each(tables.begin() + 1, tables.end(), [&resultFields, &compareFields](auto const& ref) mutable { + std::vector> const& fields = ref->fields(); std::vector> intersection; - - std::set_intersection(resultFields.begin(), resultFields.end(), - fields.begin(), fields.end(), - std::back_inserter(intersection), compareFields); + std::ranges::set_intersection(resultFields, fields, std::back_inserter(intersection), compareFields); resultFields.swap(intersection); - } + }); - for (auto& field : resultFields) { + std::ranges::transform(resultFields, std::back_inserter(columns), [&tables](auto const& field){ arrow::ArrayVector chunks; - for (auto& table : tables) { + std::ranges::for_each(tables, [&field, &chunks](auto const& table){ auto ci = table->schema()->GetFieldIndex(field->name()); if (ci == -1) { throw std::runtime_error("Unable to find field " + field->name()); @@ -160,11 +180,11 @@ std::shared_ptr ArrowHelpers::concatTables(std::vectorcolumn(ci); auto otherChunks = column->chunks(); chunks.insert(chunks.end(), otherChunks.begin(), otherChunks.end()); - } - columns.push_back(std::make_shared(chunks)); - } + }); + return std::make_shared(chunks); + }); - return arrow::Table::Make(std::make_shared(resultFields), columns); + return {arrow::Table::Make(std::make_shared(resultFields), columns)}; } // ASCII-only lowercase. Column labels are plain identifiers, so we deliberately From 066edd8a1f3e71badb60923c3519022b5447961f Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Wed, 17 Jun 2026 14:55:10 +0200 Subject: [PATCH 11/25] cover more cases --- Framework/Core/include/Framework/ASoA.h | 23 +++++++++++++++-------- Framework/Core/src/ASoA.cxx | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index f9de7c0b4b1ec..962b26871508b 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1248,10 +1248,12 @@ struct TableIterator : IP, C... { }; struct ArrowHelpers { + static o2::soa::ArrowTableRef joinTables(std::vector>&& tables); static o2::soa::ArrowTableRef joinTables(std::vector&& tables); static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); static o2::soa::ArrowTableRef concatTables(std::vector&& tables); + static o2::soa::ArrowTableRef concatTables(std::vector>&& tables); }; template os1, size_t N2, std::array os2> @@ -1962,27 +1964,32 @@ class Table } } - Table(std::vector>&& tables, ArrowRange range) + Table(std::shared_ptr table) + : Table(o2::soa::ArrowTableRef{table}) + { + } + + Table(std::vector&& tables) requires(ref.origin_hash != "CONC"_h) - : Table({ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels}), range}) + : Table(ArrowHelpers::joinTables(std::forward>(tables), std::span{originalLabels})) { } - Table(std::vector>&& tables, ArrowRange range) + Table(std::vector&& tables) requires(ref.origin_hash == "CONC"_h) - : Table({ArrowHelpers::concatTables(std::move(tables)), range}) + : Table(ArrowHelpers::concatTables(std::forward>(tables))) { } - Table(std::vector&& tables) + Table(std::vector>&& tables) requires(ref.origin_hash != "CONC"_h) - : Table(ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels})) + : Table(ArrowHelpers::joinTables(std::forward>>(tables))) { } - Table(std::vector&& tables) + Table(std::vector>&& tables) requires(ref.origin_hash == "CONC"_h) - : Table(ArrowHelpers::concatTables(std::move(tables))) + : Table(ArrowHelpers::concatTables(std::forward>>(tables))) { } diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index 93b47eb306fbf..fe98123e47fd1 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -126,7 +126,7 @@ std::shared_ptr joinTablesImpl(std::ranges::input_range auto table } template - ArrowTableRef joinTablesImpl(std::ranges::input_range auto tables, std::span labels) +ArrowTableRef joinTablesImpl(std::ranges::input_range auto tables, std::span labels) { if (tables.size() == 1) { return tables.front(); @@ -137,6 +137,13 @@ template } } // namespace +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables) +{ + std::vector refs; + std::ranges::transform(tables, std::back_inserter(refs),[](auto const& table){ return ArrowTableRef{table}; }); + return joinTablesImpl(refs, std::span()); +} + o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables) { return joinTablesImpl(tables, std::span()); @@ -187,6 +194,13 @@ o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector(resultFields), columns)}; } +o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector>&& tables) +{ + std::vector refs; + std::ranges::transform(tables, std::back_inserter(refs),[](auto const& table){ return ArrowTableRef{table}; }); + return concatTables(std::move(refs)); +} + // ASCII-only lowercase. Column labels are plain identifiers, so we deliberately // avoid the locale-aware std::tolower: it goes through the C locale facet on // every character and dominated getIndexFromLabel in profiles. From ea105c7bb6e1c8dc9a3d027fc4c8dd8855cc19c4 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 19 Jun 2026 09:55:52 +0200 Subject: [PATCH 12/25] update join --- Framework/Core/include/Framework/ASoA.h | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 962b26871508b..629e1b3e13d6a 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -3353,19 +3353,27 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: {}; using base = Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...>; - Join(std::shared_ptr&& table, uint64_t offset = 0) - : base{std::move(table), offset} + Join(ArrowTableRef table) + : base{table} { if (this->tableSize() != 0) { bindInternalIndicesTo(this); } } - Join(std::vector>&& tables, uint64_t offset = 0) - : base{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels}), offset} + + Join(std::shared_ptr&& table) + : Join{ArrowTableRef{std::move(table)}} + { + } + + Join(std::vector&& tables) + : Join{ArrowHelpers::joinTables(std::move(tables))} + { + } + + Join(std::vector>&& tables) + : Join{ArrowTableRef{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels})}} { - if (this->tableSize() != 0) { - bindInternalIndicesTo(this); - } } using base::bindExternalIndices; using base::bindInternalIndicesTo; From 948ac9413222af1666be45f1764bd1075e63dbe8 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 19 Jun 2026 13:12:04 +0200 Subject: [PATCH 13/25] rework constructors and operators --- Framework/Core/include/Framework/ASoA.h | 254 +++++++++--------------- 1 file changed, 91 insertions(+), 163 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 629e1b3e13d6a..0af4129892fa5 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -3361,8 +3361,8 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: } } - Join(std::shared_ptr&& table) - : Join{ArrowTableRef{std::move(table)}} + Join(std::shared_ptr table) + : Join{ArrowTableRef{table}} { } @@ -3372,7 +3372,7 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: } Join(std::vector>&& tables) - : Join{ArrowTableRef{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels})}} + : Join{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels})} { } using base::bindExternalIndices; @@ -3474,17 +3474,33 @@ template struct Concat : Table, o2::aod::Hash<"CONC/0"_h>, o2::aod::Hash<"CONC"_h>, Ts...> { using base = Table, o2::aod::Hash<"CONC/0"_h>, o2::aod::Hash<"CONC"_h>, Ts...>; using self_t = Concat; - Concat(std::vector>&& tables, uint64_t offset = 0) - : base{ArrowHelpers::concatTables(std::move(tables)), offset} + + Concat(ArrowTableRef table) + : base{table} { bindInternalIndicesTo(this); } - Concat(Ts const&... t, uint64_t offset = 0) - : base{ArrowHelpers::concatTables({t.asArrowTable()...}), offset} + + Concat(std::shared_ptr table) + : Concat{ArrowTableRef{table}} { - bindInternalIndicesTo(this); } + Concat(std::vector&& tables) + : Concat{ArrowHelpers::concatTables(std::move(tables))} + { + } + + Concat(std::vector>&& tables) + : Concat{ArrowHelpers::concatTables(std::move(tables))} + { + } + + // Concat(Ts const&... t) + // : Concat{ArrowHelpers::concatTables({t.asArrowTableRef()...})} + // { + // } + using base::originals; using base::bindExternalIndices; @@ -3508,6 +3524,9 @@ constexpr auto concat(Ts const&... t) return Concat{t...}; } +template +concept is_a_selection = std::same_as || std::same_as || std::same_as const&>; + template class FilteredBase : public T { @@ -3538,23 +3557,10 @@ class FilteredBase : public T using unfiltered_iterator = T::template iterator_template_o; using const_iterator = iterator; - FilteredBase(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : T{std::move(tables), offset}, - mSelectedRows{getSpan(selection)} - { - if (this->tableSize() != 0) { - mFilteredBegin = table_t::filtered_begin(mSelectedRows); - } - resetRanges(); - mFilteredBegin.bindInternalIndices(this); - } - - FilteredBase(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : T{std::move(tables), offset}, - mSelectedRowsCache{std::move(selection)}, - mCached{true} + FilteredBase(std::vector&& tables, is_a_selection auto selection) + : T{std::move(tables)} { - mSelectedRows = std::span{mSelectedRowsCache}; + adoptSelection(selection); if (this->tableSize() != 0) { mFilteredBegin = table_t::filtered_begin(mSelectedRows); } @@ -3562,15 +3568,13 @@ class FilteredBase : public T mFilteredBegin.bindInternalIndices(this); } - FilteredBase(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : T{std::move(tables), offset}, - mSelectedRows{selection} + FilteredBase(std::vector>&& tables, is_a_selection auto selection) + : FilteredBase([](std::vector>&& ts) -> std::vector{ + std::vector rs; + std::ranges::transform(ts, std::back_inserter(rs), [](auto const& t){ return ArrowTableRef{t}; }); + return rs; + }(tables), selection) { - if (this->tableSize() != 0) { - mFilteredBegin = table_t::filtered_begin(mSelectedRows); - } - resetRanges(); - mFilteredBegin.bindInternalIndices(this); } iterator begin() @@ -3718,41 +3722,21 @@ class FilteredBase : public T return static_cast(std::distance(mSelectedRows.begin(), locate)); } - void sumWithSelection(SelectionVector const& selection) + void sumWithSelection(is_a_selection auto selection) { mCached = true; SelectionVector rowsUnion; - std::set_union(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(rowsUnion)); + std::ranges::set_union(mSelectedRows, selection, std::back_inserter(rowsUnion)); mSelectedRowsCache.clear(); mSelectedRowsCache = rowsUnion; resetRanges(); } - void intersectWithSelection(SelectionVector const& selection) + void intersectWithSelection(is_a_selection auto selection) { mCached = true; SelectionVector intersection; - std::set_intersection(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(intersection)); - mSelectedRowsCache.clear(); - mSelectedRowsCache = intersection; - resetRanges(); - } - - void sumWithSelection(std::span const& selection) - { - mCached = true; - SelectionVector rowsUnion; - std::set_union(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(rowsUnion)); - mSelectedRowsCache.clear(); - mSelectedRowsCache = rowsUnion; - resetRanges(); - } - - void intersectWithSelection(std::span const& selection) - { - mCached = true; - SelectionVector intersection; - std::set_intersection(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(intersection)); + std::ranges::set_intersection(mSelectedRows, selection, std::back_inserter(intersection)); mSelectedRowsCache.clear(); mSelectedRowsCache = intersection; resetRanges(); @@ -3777,6 +3761,24 @@ class FilteredBase : public T } } + inline void adoptSelection(gandiva::Selection const& selection) + { + mSelectedRows = getSpan(selection); + mCached = false; + } + + inline void adoptSelection(SelectionVector&& selection) + { + mSelectedRowsCache = std::move(selection); + mCached = true; + } + + inline void adoptSelection(std::span const& selection) + { + mSelectedRows = selection; + mCached = false; + } + std::span mSelectedRows; SelectionVector mSelectedRowsCache; bool mCached = false; @@ -3807,23 +3809,17 @@ class Filtered : public FilteredBase return const_iterator(this->cached_begin()); } - Filtered(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : FilteredBase(std::move(tables), selection, offset) {} + Filtered(std::vector&& tables, is_a_selection auto selection) + : FilteredBase{std::move(tables), selection} {} - Filtered(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : FilteredBase(std::move(tables), std::forward(selection), offset) {} - - Filtered(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : FilteredBase(std::move(tables), selection, offset) {} - - Filtered operator+(SelectionVector const& selection) - { - Filtered copy(*this); - copy.sumWithSelection(selection); - return copy; - } + Filtered(std::vector>&& tables, is_a_selection auto selection) + : FilteredBase{[](std::vector>&& ts) -> std::vector{ + std::vector rs; + std::ranges::transform(ts, std::back_inserter(rs), [](auto const& t){ return ArrowTableRef{t}; }); + return rs; + }(tables), selection} {} - Filtered operator+(std::span const& selection) + Filtered operator+(is_a_selection auto selection) { Filtered copy(*this); copy.sumWithSelection(selection); @@ -3835,13 +3831,7 @@ class Filtered : public FilteredBase return operator+(other.getSelectedRows()); } - Filtered operator+=(SelectionVector const& selection) - { - this->sumWithSelection(selection); - return *this; - } - - Filtered operator+=(std::span const& selection) + Filtered operator+=(is_a_selection auto selection) { this->sumWithSelection(selection); return *this; @@ -3852,14 +3842,7 @@ class Filtered : public FilteredBase return operator+=(other.getSelectedRows()); } - Filtered operator*(SelectionVector const& selection) - { - Filtered copy(*this); - copy.intersectWithSelection(selection); - return copy; - } - - Filtered operator*(std::span const& selection) + Filtered operator*(is_a_selection auto selection) { Filtered copy(*this); copy.intersectWithSelection(selection); @@ -3871,13 +3854,7 @@ class Filtered : public FilteredBase return operator*(other.getSelectedRows()); } - Filtered operator*=(SelectionVector const& selection) - { - this->intersectWithSelection(selection); - return *this; - } - - Filtered operator*=(std::span const& selection) + Filtered operator*=(is_a_selection auto selection) { this->intersectWithSelection(selection); return *this; @@ -3902,12 +3879,12 @@ class Filtered : public FilteredBase SelectionVector newSelection; newSelection.resize(static_cast(end - start + 1)); std::iota(newSelection.begin(), newSelection.end(), start); - return self_t{{this->asArrowTable()}, std::move(newSelection), 0}; + return self_t{{this->asArrowTableRef()}, std::move(newSelection)}; } auto emptySlice() const { - return self_t{{this->asArrowTable()}, SelectionVector{}, 0}; + return self_t{{this->asArrowTableRef()}, SelectionVector{}}; } template @@ -3969,38 +3946,15 @@ class Filtered> : public FilteredBase return const_iterator(this->cached_begin()); } - Filtered(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), selection, offset) - { - for (auto& table : tables) { - *this *= table; - } - } - - Filtered(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), std::forward(selection), offset) - { - for (auto& table : tables) { - *this *= table; - } - } - - Filtered(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), selection, offset) + Filtered(std::vector>&& tables, is_a_selection auto selection) + : FilteredBase(std::move(extractTablesFromFiltered(tables)), selection) { for (auto& table : tables) { *this *= table; } } - Filtered> operator+(SelectionVector const& selection) - { - Filtered> copy(*this); - copy.sumWithSelection(selection); - return copy; - } - - Filtered> operator+(std::span const& selection) + Filtered> operator+(is_a_selection auto selection) { Filtered> copy(*this); copy.sumWithSelection(selection); @@ -4012,13 +3966,7 @@ class Filtered> : public FilteredBase return operator+(other.getSelectedRows()); } - Filtered> operator+=(SelectionVector const& selection) - { - this->sumWithSelection(selection); - return *this; - } - - Filtered> operator+=(std::span const& selection) + Filtered> operator+=(is_a_selection auto selection) { this->sumWithSelection(selection); return *this; @@ -4029,14 +3977,7 @@ class Filtered> : public FilteredBase return operator+=(other.getSelectedRows()); } - Filtered> operator*(SelectionVector const& selection) - { - Filtered> copy(*this); - copy.intersectionWithSelection(selection); - return copy; - } - - Filtered> operator*(std::span const& selection) + Filtered> operator*(is_a_selection auto selection) { Filtered> copy(*this); copy.intersectionWithSelection(selection); @@ -4048,13 +3989,7 @@ class Filtered> : public FilteredBase return operator*(other.getSelectedRows()); } - Filtered> operator*=(SelectionVector const& selection) - { - this->intersectWithSelection(selection); - return *this; - } - - Filtered> operator*=(std::span const& selection) + Filtered> operator*=(is_a_selection auto selection) { this->intersectWithSelection(selection); return *this; @@ -4077,12 +4012,12 @@ class Filtered> : public FilteredBase SelectionVector newSelection; newSelection.resize(static_cast(end - start + 1)); std::iota(newSelection.begin(), newSelection.end(), start); - return self_t{{this->asArrowTable()}, std::move(newSelection), 0}; + return self_t{{this->asArrowTableRef()}, std::move(newSelection)}; } auto emptySlice() const { - return self_t{{this->asArrowTable()}, SelectionVector{}, 0}; + return self_t{{this->asArrowTableRef()}, SelectionVector{}}; } auto sliceByCached(framework::expressions::BindingNode const& node, int value, o2::framework::SliceCache& cache) const @@ -4108,11 +4043,11 @@ class Filtered> : public FilteredBase } private: - std::vector> extractTablesFromFiltered(std::vector>& tables) + std::vector extractTablesFromFiltered(std::vector>& tables) { - std::vector> outTables; + std::vector outTables; for (auto& table : tables) { - outTables.push_back(table.asArrowTable()); + outTables.push_back(table.asArrowTableRef()); } return outTables; } @@ -4149,15 +4084,13 @@ struct IndexTable : Table { ...); } - IndexTable(std::shared_ptr table, uint64_t offset = 0) - : base_t{table, offset} - { - } + IndexTable(ArrowTableRef table) + : base_t{table} {} - IndexTable(std::vector> tables, uint64_t offset = 0) - : base_t{tables[0], offset} - { - } + /// FIXME: this is a compatiblity for a generic constructor call with a vector + /// there has to be a safer way + IndexTable(std::vector&& tables) + : base_t{tables[0]} {} IndexTable(IndexTable const&) = default; IndexTable(IndexTable&&) = default; @@ -4175,14 +4108,9 @@ struct SmallGroupsBase : public Filtered { static constexpr void isSmallGroups() {}; static constexpr bool applyFilters = APPLY; - SmallGroupsBase(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : Filtered(std::move(tables), selection, offset) {} - - SmallGroupsBase(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : Filtered(std::move(tables), std::forward(selection), offset) {} - SmallGroupsBase(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : Filtered(std::move(tables), selection, offset) {} + SmallGroupsBase(std::vector&& tables, is_a_selection auto selection) + : Filtered(std::move(tables), selection) {} }; template From 020458780a4c8ab0da66c7d450f584a2a77b9ab0 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 19 Jun 2026 13:39:20 +0200 Subject: [PATCH 14/25] start adapting slicing --- Framework/Core/include/Framework/ASoA.h | 51 ++++++++++--------------- Framework/Core/src/ASoA.cxx | 13 +------ 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 0af4129892fa5..0000f5bc1b3fa 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -58,6 +58,8 @@ void missingFilterDeclaration(int hash, int ai); void notBoundTable(const char* tableName); void* extractCCDBPayload(char* payload, size_t size, TClass const* cl, const char* what); +static constexpr char asciiToLower(char c); + template auto createFieldsFromColumns(framework::pack) { @@ -1316,8 +1318,8 @@ static constexpr auto hasColumnForKey(framework::pack, std::string_view ke return std::ranges::equal( str1, str2, [](char c1, char c2) { - return std::tolower(static_cast(c1)) == - std::tolower(static_cast(c2)); + return asciiToLower(static_cast(c1)) == + asciiToLower(static_cast(c2)); }); }; return (caseInsensitiveCompare(C::inherited_t::mLabel, key) || ...); @@ -1418,12 +1420,7 @@ struct PreslicePolicySorted : public PreslicePolicyBase { void updateSliceInfo(SliceInfoPtr&& si); SliceInfoPtr sliceInfo; - std::shared_ptr getSliceFor(int value, std::shared_ptr const& input, uint64_t& offset) const; - // One-slot cache for the empty (0-row) slice, so that empty groups do not - // slice every column only to produce 0 rows (the common case for sparse - // grouping, e.g. candidates per collision). Keyed by the input table, which - // changes with every dataframe. - mutable std::pair> emptySlice{nullptr, nullptr}; + o2::soa::ArrowTableRef getSliceFor(int value, o2::soa::ArrowTableRef const& input) const; }; struct PreslicePolicyGeneral : public PreslicePolicyBase { @@ -1447,14 +1444,14 @@ struct PresliceBase : public Policy { { } - std::shared_ptr getSliceFor(int value, std::shared_ptr const& input, uint64_t& offset) const + o2::soa::ArrowTableRef getSliceFor(int value, o2::soa::ArrowTableRef const& input) const { if constexpr (OPT) { if (Policy::isMissing()) { return nullptr; } } - return Policy::getSliceFor(value, input, offset); + return Policy::getSliceFor(value, input); } std::span getSliceFor(int value) const @@ -1524,8 +1521,7 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const missingOptionalPreslice(getLabelFromType>().data(), container.bindingKey.key.c_str()); } } - uint64_t offset = 0; - auto out = container.getSliceFor(value, table->asArrowTable(), offset); + auto out = container.getSliceFor(value, table->asArrowTableRef()); auto t = typename T::self_t({out}, offset); if (t.tableSize() != 0) { table->copyIndexBindings(t); @@ -1537,7 +1533,7 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const template auto doSliceByHelper(T const* table, std::span const& selection) { - auto t = soa::Filtered({table->asArrowTable()}, selection); + auto t = soa::Filtered({table->asArrowTableRef()}, selection); if (t.tableSize() != 0) { table->copyIndexBindings(t); t.bindInternalIndicesTo(table); @@ -1550,7 +1546,7 @@ template requires(!soa::is_filtered_table) auto doSliceByHelper(T const* table, std::span const& selection) { - auto t = soa::Filtered({table->asArrowTable()}, selection); + auto t = soa::Filtered({table->asArrowTableRef()}, selection); if (t.tableSize() != 0) { table->copyIndexBindings(t); t.bindInternalIndicesTo(table); @@ -1574,17 +1570,17 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const SelectionVector sliceSelection(std::span const& mSelectedRows, int64_t nrows, uint64_t offset); template -auto prepareFilteredSlice(T const* table, std::shared_ptr slice, uint64_t offset) +auto prepareFilteredSlice(T const* table, o2::soa::ArrowTableRef slice) { - if (offset >= static_cast(table->tableSize())) { + if (slice.range.offset >= static_cast(table->tableSize())) { Filtered fresult{{{slice}}, SelectionVector{}, 0}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); } return fresult; } - auto slicedSelection = sliceSelection(table->getSelectedRows(), slice->num_rows(), offset); - Filtered fresult{{{slice}}, std::move(slicedSelection), offset}; + auto slicedSelection = sliceSelection(table->getSelectedRows(), slice->num_rows(), slice.range.offset); + Filtered fresult{{{slice}}, std::move(slicedSelection)}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); } @@ -1600,9 +1596,8 @@ auto doFilteredSliceBy(T const* table, o2::framework::PresliceBase().data(), container.bindingKey.key.c_str()); } } - uint64_t offset = 0; - auto slice = container.getSliceFor(value, table->asArrowTable(), offset); - return prepareFilteredSlice(table, slice, offset); + auto slice = container.getSliceFor(value, table->asArrowTable()); + return prepareFilteredSlice(table, slice); } std::function originReplacement(header::DataOrigin newOrigin); @@ -1613,10 +1608,7 @@ auto doSliceByCached(T const* table, framework::expressions::BindingNode const& auto localCache = cache.ptr->getCacheFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); auto [offset, count] = localCache.getSliceFor(value); - // Empty group: reuse a cached empty (0-row) table instead of slicing every column. - auto slice = count == 0 ? cache.ptr->getEmptySliceFor(table->asArrowTable()) - : table->asArrowTable()->Slice(static_cast(offset), count); - auto t = typename T::self_t({slice}, static_cast(offset)); + auto t = typename T::self_t({table->asArrowTableRef().slice(static_cast(offset), count)}); if (t.tableSize() != 0) { table->copyIndexBindings(t); } @@ -1629,10 +1621,7 @@ auto doFilteredSliceByCached(T const* table, framework::expressions::BindingNode auto localCache = cache.ptr->getCacheFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); auto [offset, count] = localCache.getSliceFor(value); - // Empty group: reuse a cached empty (0-row) table instead of slicing every column. - auto slice = count == 0 ? cache.ptr->getEmptySliceFor(table->asArrowTable()) - : table->asArrowTable()->Slice(static_cast(offset), count); - return prepareFilteredSlice(table, slice, offset); + return prepareFilteredSlice(table, table->asArrowTableRef().slice(static_cast(offset), count)); } template @@ -1641,14 +1630,14 @@ auto doSliceByCachedUnsorted(T const* table, framework::expressions::BindingNode auto localCache = cache.ptr->getCacheUnsortedFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); if constexpr (soa::is_filtered_table) { - auto t = typename T::self_t({table->asArrowTable()}, localCache.getSliceFor(value)); + auto t = typename T::self_t({table->asArrowTableRef()}, localCache.getSliceFor(value)); if (t.tableSize() != 0) { t.intersectWithSelection(table->getSelectedRows()); table->copyIndexBindings(t); } return t; } else { - auto t = Filtered({table->asArrowTable()}, localCache.getSliceFor(value)); + auto t = Filtered({table->asArrowTableRef()}, localCache.getSliceFor(value)); if (t.tableSize() != 0) { table->copyIndexBindings(t); } diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index fe98123e47fd1..8de85bcfe4f5b 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -348,19 +348,10 @@ void PreslicePolicyGeneral::updateSliceInfo(SliceInfoUnsortedPtr&& si) sliceInfo = si; } -std::shared_ptr PreslicePolicySorted::getSliceFor(int value, std::shared_ptr const& input, uint64_t& offset) const +o2::soa::ArrowTableRef PreslicePolicySorted::getSliceFor(int value, o2::soa::ArrowTableRef const& input) const { auto [offset_, count] = this->sliceInfo.getSliceFor(value); - offset = static_cast(offset_); - if (count == 0) { - // Empty group: avoid slicing every column only to discard it. Cache one - // empty (0-row) table per input table and reuse it (see GroupSlicer). - if (emptySlice.first != input.get()) { - emptySlice = {input.get(), input->Slice(0, 0)}; - } - return emptySlice.second; - } - return input->Slice(offset_, count); + return input.slice({offset_, count}); } std::span PreslicePolicyGeneral::getSliceFor(int value) const From c0883a740a7b45c7e7685e791efb85f7ee3d62bd Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 19 Jun 2026 13:47:07 +0200 Subject: [PATCH 15/25] use imposed size --- Framework/Core/include/Framework/ASoA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 0000f5bc1b3fa..7a75ee0b8f1ea 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -2076,7 +2076,7 @@ class Table /// Size of the table, in rows. [[nodiscard]] int64_t size() const { - return mArrowTableRef.tablePtr->num_rows(); + return mArrowTableRef.range.size; } [[nodiscard]] int64_t tableSize() const From 9740d9a0756710417b3f66b7c9a66ecf38f11a4d Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 19 Jun 2026 14:58:24 +0200 Subject: [PATCH 16/25] make it compile --- Framework/Core/include/Framework/ASoA.h | 60 +++++-------------- .../Core/include/Framework/AnalysisHelpers.h | 6 +- .../Core/include/Framework/AnalysisManagers.h | 4 +- .../Core/include/Framework/AnalysisTask.h | 6 +- .../Core/include/Framework/GroupSlicer.h | 16 +---- Framework/Core/src/ASoA.cxx | 16 ++++- Framework/Core/src/AnalysisHelpers.cxx | 2 +- Framework/Core/test/test_ASoA.cxx | 10 ++-- .../Core/test/test_AnalysisDataModel.cxx | 2 +- Framework/Core/test/test_Concepts.cxx | 2 +- Framework/Core/test/test_GroupSlicer.cxx | 8 +-- Framework/Core/test/test_TableSpawner.cxx | 4 +- 12 files changed, 54 insertions(+), 82 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 7a75ee0b8f1ea..38979b2d8924a 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -58,7 +58,7 @@ void missingFilterDeclaration(int hash, int ai); void notBoundTable(const char* tableName); void* extractCCDBPayload(char* payload, size_t size, TClass const* cl, const char* what); -static constexpr char asciiToLower(char c); +constexpr char asciiToLower(char c); template auto createFieldsFromColumns(framework::pack) @@ -1254,6 +1254,8 @@ struct ArrowHelpers { static o2::soa::ArrowTableRef joinTables(std::vector&& tables); static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); + static o2::soa::ArrowTableRef joinTables(std::vector>&& tables, std::span labels); + static o2::soa::ArrowTableRef joinTables(std::vector>&& tables, std::span labels); static o2::soa::ArrowTableRef concatTables(std::vector&& tables); static o2::soa::ArrowTableRef concatTables(std::vector>&& tables); }; @@ -1448,7 +1450,7 @@ struct PresliceBase : public Policy { { if constexpr (OPT) { if (Policy::isMissing()) { - return nullptr; + return {nullptr, {0,0}}; } } return Policy::getSliceFor(value, input); @@ -1522,7 +1524,7 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const } } auto out = container.getSliceFor(value, table->asArrowTableRef()); - auto t = typename T::self_t({out}, offset); + auto t = typename T::self_t({out}); if (t.tableSize() != 0) { table->copyIndexBindings(t); t.bindInternalIndicesTo(table); @@ -1608,7 +1610,7 @@ auto doSliceByCached(T const* table, framework::expressions::BindingNode const& auto localCache = cache.ptr->getCacheFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); auto [offset, count] = localCache.getSliceFor(value); - auto t = typename T::self_t({table->asArrowTableRef().slice(static_cast(offset), count)}); + auto t = typename T::self_t({table->asArrowTableRef().slice({static_cast(offset), count})}); if (t.tableSize() != 0) { table->copyIndexBindings(t); } @@ -3342,28 +3344,14 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: {}; using base = Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...>; - Join(ArrowTableRef table) - : base{table} + Join(std::vector&& tables) + : base{ArrowHelpers::joinTables(std::move(tables))} { if (this->tableSize() != 0) { bindInternalIndicesTo(this); } } - Join(std::shared_ptr table) - : Join{ArrowTableRef{table}} - { - } - - Join(std::vector&& tables) - : Join{ArrowHelpers::joinTables(std::move(tables))} - { - } - - Join(std::vector>&& tables) - : Join{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels})} - { - } using base::bindExternalIndices; using base::bindInternalIndicesTo; static constexpr const uint32_t binding_origin = base::binding_origin; @@ -3433,12 +3421,12 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: auto rawSlice(uint64_t start, uint64_t end) const { - return self_t{{this->asArrowTable()->Slice(start, end - start + 1)}, start}; + return self_t{{this->asArrowTableRef().slice({start, static_cast(end - start + 1)})}}; } auto emptySlice() const { - return self_t{{this->asArrowTable()->Slice(0, 0)}, 0}; + return self_t{{this->asArrowTableRef().slice({0, 0})}}; } template @@ -3453,7 +3441,7 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: template constexpr auto join(Ts const&... t) { - return Join(ArrowHelpers::joinTables({t.asArrowTable()...}, std::span{Join::base::originalLabels})); + return Join({ArrowHelpers::joinTables({t.asArrowTableRef()...}, std::span{Join::base::originalLabels})}); } template @@ -3485,10 +3473,10 @@ struct Concat : Table, o2::aod::Hash<"CONC/0"_h>, o2::ao { } - // Concat(Ts const&... t) - // : Concat{ArrowHelpers::concatTables({t.asArrowTableRef()...})} - // { - // } + Concat(Ts const&... t) + : Concat{ArrowHelpers::concatTables({t.asArrowTableRef()...})} + { + } using base::originals; @@ -3514,7 +3502,7 @@ constexpr auto concat(Ts const&... t) } template -concept is_a_selection = std::same_as || std::same_as || std::same_as const&>; +concept is_a_selection = std::same_as, gandiva::Selection> || std::same_as, SelectionVector> || std::same_as, std::span>; template class FilteredBase : public T @@ -3557,15 +3545,6 @@ class FilteredBase : public T mFilteredBegin.bindInternalIndices(this); } - FilteredBase(std::vector>&& tables, is_a_selection auto selection) - : FilteredBase([](std::vector>&& ts) -> std::vector{ - std::vector rs; - std::ranges::transform(ts, std::back_inserter(rs), [](auto const& t){ return ArrowTableRef{t}; }); - return rs; - }(tables), selection) - { - } - iterator begin() { return iterator(mFilteredBegin); @@ -3801,13 +3780,6 @@ class Filtered : public FilteredBase Filtered(std::vector&& tables, is_a_selection auto selection) : FilteredBase{std::move(tables), selection} {} - Filtered(std::vector>&& tables, is_a_selection auto selection) - : FilteredBase{[](std::vector>&& ts) -> std::vector{ - std::vector rs; - std::ranges::transform(ts, std::back_inserter(rs), [](auto const& t){ return ArrowTableRef{t}; }); - return rs; - }(tables), selection} {} - Filtered operator+(is_a_selection auto selection) { Filtered copy(*this); diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index 5600e201feb8f..7ddbf01a2eb13 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -147,7 +147,7 @@ auto spawner(framework::pack, std::vector>&& if (fullTable->num_rows() == 0) { return makeEmptyTable(name, framework::pack{}); } - return spawnerHelper(fullTable, schema, sizeof...(C), projectors, name, projector); + return spawnerHelper(fullTable.tablePtr, schema, sizeof...(C), projectors, name, projector); } std::string serializeProjectors(std::vector& projectors); @@ -951,7 +951,7 @@ auto getTableFromFilter(soa::is_filtered_table auto const& table, soa::Selection auto getTableFromFilter(soa::is_not_filtered_table auto const& table, soa::SelectionVector&& selection) { - return std::make_unique>>(std::vector{table.asArrowTable()}, std::forward(selection)); + return std::make_unique>>(std::vector{table.asArrowTableRef()}, std::forward(selection)); } void initializePartitionCaches(std::set const& hashes, std::shared_ptr const& schema, expressions::Filter const& filter, gandiva::NodePtr& tree, gandiva::FilterPtr& gfilter); @@ -1087,7 +1087,7 @@ auto Extend(T const& table) static std::array projectors{{std::move(Cs::Projector())...}}; static std::shared_ptr projector = nullptr; static auto schema = std::make_shared(o2::soa::createFieldsFromColumns(framework::pack{})); - return output_t{{o2::framework::spawner(framework::pack{}, {table.asArrowTable()}, "dynamicExtension", projectors.data(), projector, schema), table.asArrowTable()}, 0}; + return output_t{{o2::framework::spawner(framework::pack{}, {table.asArrowTable()}, "dynamicExtension", projectors.data(), projector, schema), table.asArrowTable()}}; } /// Template function to attach dynamic columns on-the-fly (e.g. inside diff --git a/Framework/Core/include/Framework/AnalysisManagers.h b/Framework/Core/include/Framework/AnalysisManagers.h index bb37fb9016c2f..4c683f74a7831 100644 --- a/Framework/Core/include/Framework/AnalysisManagers.h +++ b/Framework/Core/include/Framework/AnalysisManagers.h @@ -319,12 +319,12 @@ bool prepareOutput(ProcessingContext& context, T& spawns) } using D = o2::aod::Hash; - spawns.extension = std::make_shared(o2::framework::spawner(originalTable, + spawns.extension = std::make_shared(o2::framework::spawner(originalTable.tablePtr, o2::aod::label(), spawns.projectors.data(), spawns.projector, spawns.schema)); - spawns.table = std::make_shared(soa::ArrowHelpers::joinTables({spawns.extension->asArrowTable(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); + spawns.table = std::make_shared(soa::ArrowHelpers::joinTables({spawns.extension->asArrowTableRef(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); return true; } diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index 3170236e18f09..e4e6c35d9a7e9 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -226,7 +226,7 @@ struct AnalysisDataProcessorBuilder { template static auto extractTablesFromRecord(InputRecord& record, R matchers) { - std::vector> tables; + std::vector tables; std::ranges::transform(matchers, std::back_inserter(tables), [&record](auto const& m) { return record.get(m.second)->asArrowTable(); }); @@ -248,8 +248,8 @@ struct AnalysisDataProcessorBuilder { template static auto extractFilteredFromRecord(InputRecord& record, R matchers, ExpressionInfo& info) { - std::shared_ptr table = soa::ArrowHelpers::joinTables(extractTablesFromRecord(record, matchers)); - expressions::updateFilterInfo(info, table); + auto table = soa::ArrowHelpers::joinTables(extractTablesFromRecord(record, matchers)); + expressions::updateFilterInfo(info, table.tablePtr); if constexpr (!o2::soa::is_smallgroups>) { if (info.selection == nullptr) { soa::missingFilterDeclaration(info.processHash, info.argumentIndex); diff --git a/Framework/Core/include/Framework/GroupSlicer.h b/Framework/Core/include/Framework/GroupSlicer.h index 74e5f16c1703f..a72af7da85a08 100644 --- a/Framework/Core/include/Framework/GroupSlicer.h +++ b/Framework/Core/include/Framework/GroupSlicer.h @@ -218,16 +218,7 @@ struct GroupSlicer { auto oc = sliceInfos[index].getSliceFor(pos); uint64_t offset = oc.first; auto count = oc.second; - if (count == 0) { - // Empty group: avoid slicing every column only to discard it. Cache one - // empty (0-row) table per associated table and reuse it. This is the - // common case for sparse grouping (e.g. collisions with no candidates). - if (!emptyTables[index]) { - emptyTables[index] = originalTable.asArrowTable()->Slice(0, 0); - } - return std::decay_t{{emptyTables[index]}, soa::SelectionVector{}}; - } - auto groupedElementsTable = originalTable.asArrowTable()->Slice(offset, count); + auto groupedElementsTable = originalTable.asArrowTableRef().slice({offset, count}); // for each grouping element we need to slice the selection vector auto start_iterator = std::lower_bound(starts[index], selections[index]->end(), offset); @@ -239,7 +230,7 @@ struct GroupSlicer { return idx - static_cast(offset); }); - std::decay_t typedTable{{groupedElementsTable}, std::move(slicedSelection), offset}; + std::decay_t typedTable{{groupedElementsTable}, std::move(slicedSelection)}; typedTable.bindInternalIndicesTo(&originalTable); return typedTable; } @@ -281,9 +272,6 @@ struct GroupSlicer { std::span groupSelection; std::array const*, sizeof...(A)> selections; std::array::iterator, sizeof...(A)> starts; - // Cached empty (0-row) table per associated table, lazily built and reused - // for empty groups so we do not slice every column on each empty group. - std::array, sizeof...(A)> emptyTables{}; std::array sliceInfos; std::array sliceInfosUnsorted; diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index 8de85bcfe4f5b..313ad91fbfcef 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -159,6 +159,18 @@ o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +{ + canNotJoin(tables, labels); + return o2::soa::ArrowTableRef{joinTablesImpl(tables)}; +} + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +{ + canNotJoin(tables, labels); + return o2::soa::ArrowTableRef{joinTablesImpl(tables)}; +} + o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector&& tables) { if (tables.size() == 1) { @@ -204,7 +216,7 @@ o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector= 'A' && c <= 'Z') ? static_cast(c + 32) : c; } @@ -351,7 +363,7 @@ void PreslicePolicyGeneral::updateSliceInfo(SliceInfoUnsortedPtr&& si) o2::soa::ArrowTableRef PreslicePolicySorted::getSliceFor(int value, o2::soa::ArrowTableRef const& input) const { auto [offset_, count] = this->sliceInfo.getSliceFor(value); - return input.slice({offset_, count}); + return input.slice({static_cast(offset_), count}); } std::span PreslicePolicyGeneral::getSliceFor(int value) const diff --git a/Framework/Core/src/AnalysisHelpers.cxx b/Framework/Core/src/AnalysisHelpers.cxx index 5e46ed86860e8..62229765ddbf8 100644 --- a/Framework/Core/src/AnalysisHelpers.cxx +++ b/Framework/Core/src/AnalysisHelpers.cxx @@ -207,7 +207,7 @@ std::shared_ptr Spawner::materialize(ProcessingContext& pc) const return arrow::Table::MakeEmpty(schema).ValueOrDie(); } - return spawnerHelper(fullTable, schema, binding.c_str(), schema->num_fields(), projector); + return spawnerHelper(fullTable.tablePtr, schema, binding.c_str(), schema->num_fields(), projector); } std::shared_ptr Builder::materialize(ProcessingContext& pc) diff --git a/Framework/Core/test/test_ASoA.cxx b/Framework/Core/test/test_ASoA.cxx index 48cfb277acc5c..89a454ffeb50f 100644 --- a/Framework/Core/test/test_ASoA.cxx +++ b/Framework/Core/test/test_ASoA.cxx @@ -286,7 +286,7 @@ TEST_CASE("TestJoinedTables") REQUIRE(Test::contains()); REQUIRE(!Test::contains()); - Test tests{{tableX, tableY}, 0}; + Test tests{{tableX, tableY}}; REQUIRE(tests.contains()); REQUIRE(tests.contains()); @@ -308,7 +308,7 @@ TEST_CASE("TestJoinedTables") REQUIRE(15 == test.x() + test.y() + test.z()); } using TestMoreThanTwo = Join; - TestMoreThanTwo tests4{{tableX, tableY, tableZ}, 0}; + TestMoreThanTwo tests4{{tableX, tableY, tableZ}}; for (auto& test : tests4) { REQUIRE(15 == test.x() + test.y() + test.z()); } @@ -480,7 +480,7 @@ TEST_CASE("TestConcatTables") selectionJoin->SetIndex(1, 2); selectionJoin->SetIndex(2, 4); selectionJoin->SetNumSlots(3); - JoinedTest testJoin{{tableA, tableC}, 0}; + JoinedTest testJoin{{tableA, tableC}}; FilteredJoinTest filteredJoin{{testJoin.asArrowTable()}, selectionJoin}; i = 0; @@ -718,7 +718,7 @@ TEST_CASE("TestEmptyTables") o2::aod::Infos i{iempty}; using PI = Join; - PI pi{{pempty, iempty}, 0}; + PI pi{{pempty, iempty}}; REQUIRE(pi.size() == 0); auto spawned = Extend(p); REQUIRE(spawned.size() == 0); @@ -1084,7 +1084,7 @@ TEST_CASE("TestSelfIndexRecursion") } using FilteredPoints = o2::soa::Filtered; - FilteredPoints ffp({t1, t2}, {1, 2, 3}, 0); + FilteredPoints ffp({t1, t2}, SelectionVector{1, 2, 3}); ffp.bindInternalIndicesTo(&ffp); // Filter should not interfere with self-index and the binding should stay the same diff --git a/Framework/Core/test/test_AnalysisDataModel.cxx b/Framework/Core/test/test_AnalysisDataModel.cxx index b8b9c161f0e07..d6db060229a49 100644 --- a/Framework/Core/test/test_AnalysisDataModel.cxx +++ b/Framework/Core/test/test_AnalysisDataModel.cxx @@ -49,7 +49,7 @@ TEST_CASE("TestJoinedTablesContains") using Test = o2::soa::Join; - Test tests{{tXY, tZD}, 0}; + Test tests{{tXY, tZD}}; REQUIRE(tests.asArrowTable()->num_columns() != 0); REQUIRE(tests.asArrowTable()->num_columns() == tXY->num_columns() + tZD->num_columns()); diff --git a/Framework/Core/test/test_Concepts.cxx b/Framework/Core/test/test_Concepts.cxx index ff5e0fa6200db..65703082519b6 100644 --- a/Framework/Core/test/test_Concepts.cxx +++ b/Framework/Core/test/test_Concepts.cxx @@ -121,7 +121,7 @@ TEST_CASE("IdentificationConcepts") REQUIRE(is_join); - auto tl = []() -> SmallGroups { return {std::vector>{}, SelectionVector{}, 0}; }; + auto tl = []() -> SmallGroups { return {{}, SelectionVector{}}; }; REQUIRE(is_smallgroups); // AnalysisHelpers diff --git a/Framework/Core/test/test_GroupSlicer.cxx b/Framework/Core/test/test_GroupSlicer.cxx index ee6878f23ff80..fe221d55ca89a 100644 --- a/Framework/Core/test/test_GroupSlicer.cxx +++ b/Framework/Core/test/test_GroupSlicer.cxx @@ -358,7 +358,7 @@ TEST_CASE("GroupSlicerMismatchedFilteredGroups") auto trkTable = builderT.finalize(); using FilteredEvents = soa::Filtered; soa::SelectionVector rows{2, 4, 10, 9, 15}; - FilteredEvents e{{evtTable}, {2, 4, 10, 9, 15}}; + FilteredEvents e{{{evtTable}}, soa::SelectionVector{2, 4, 10, 9, 15}}; aod::TrksX t{trkTable}; REQUIRE(e.size() == 5); REQUIRE(t.size() == 10 * (20 - 4)); @@ -419,7 +419,7 @@ TEST_CASE("GroupSlicerMismatchedUnsortedFilteredGroups") using FilteredEvents = soa::Filtered; soa::SelectionVector rows{2, 4, 10, 9, 15}; - FilteredEvents e{{evtTable}, {2, 4, 10, 9, 15}}; + FilteredEvents e{{evtTable}, soa::SelectionVector{2, 4, 10, 9, 15}}; soa::SmallGroups t{{trkTable}, std::move(sel)}; REQUIRE(e.size() == 5); @@ -691,7 +691,7 @@ TEST_CASE("ArrowDirectSlicing") auto lcache = cache.getCacheFor(bk); for (auto i = 0u; i < 5; ++i) { auto [offset, count] = lcache.getSliceFor(i); - auto tbl = b_e.asArrowTable()->Slice(offset, count); + auto tbl = b_e.asArrowTableRef().slice({static_cast(offset), count}); auto ca = tbl->GetColumnByName("fArr"); auto cb = tbl->GetColumnByName("fBoo"); auto cv = tbl->GetColumnByName("fLst"); @@ -706,7 +706,7 @@ TEST_CASE("ArrowDirectSlicing") int j = 0u; for (auto i = 0u; i < 5; ++i) { auto [offset, count] = lcache.getSliceFor(i); - auto tbl = BigE{{b_e.asArrowTable()->Slice(offset, count)}, static_cast(offset)}; + auto tbl = BigE{{b_e.asArrowTableRef().slice({static_cast(offset), count})}}; REQUIRE(tbl.size() == counts[i]); for (auto& row : tbl) { REQUIRE(row.id() == ids[i]); diff --git a/Framework/Core/test/test_TableSpawner.cxx b/Framework/Core/test/test_TableSpawner.cxx index e200adf37ccb4..d5bd4c83068ac 100644 --- a/Framework/Core/test/test_TableSpawner.cxx +++ b/Framework/Core/test/test_TableSpawner.cxx @@ -53,7 +53,7 @@ TEST_CASE("TestTableSpawner") auto expoints_a = o2::soa::Extend(st1); Spawns s; auto extension = ExPointsExtension{o2::framework::spawner>(t1, o2::aod::Hash<"ExPoints"_h>::str, s.projectors.data(), s.projector, s.schema)}; - auto expoints = ExPoints{{t1, extension.asArrowTable()}, 0}; + auto expoints = ExPoints{{t1, extension.asArrowTable()}}; REQUIRE(expoints_a.size() == 9); REQUIRE(extension.size() == 9); @@ -81,7 +81,7 @@ TEST_CASE("TestTableSpawner") excpts.projectors[0] = test::x * test::x + test::y * test::y + test::z * test::z; auto extension_2 = ExcPointsCfgExtension{o2::framework::spawner>({t1}, o2::aod::Hash<"ExcPoints"_h>::str, excpts.projectors.data(), excpts.projector, excpts.schema)}; - auto excpoints = ExcPoints{{t1, extension_2.asArrowTable()}, 0}; + auto excpoints = ExcPoints{{t1, extension_2.asArrowTable()}}; rex = extension.begin(); auto rex_2 = extension_2.begin(); From bd7f342e21b43ebd1a93182f1f62dcf97a083c1a Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Tue, 23 Jun 2026 12:39:26 +0200 Subject: [PATCH 17/25] make it pass tests --- Framework/Core/include/Framework/ASoA.h | 63 ++++++++++++------------ Framework/Core/src/ASoA.cxx | 28 +++++------ Framework/Core/test/test_ASoA.cxx | 4 +- Framework/Core/test/test_GroupSlicer.cxx | 34 +++++-------- 4 files changed, 59 insertions(+), 70 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 38979b2d8924a..3bacbdcdbfb32 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -502,13 +502,13 @@ class ColumnIterator : ChunkingPolicy : mColumn{column}, mCurrent{nullptr}, mCurrentPos{nullptr}, + mGlobalOffset{nullptr}, mLast{nullptr}, mFirstIndex{0}, - mCurrentChunk{0}, - mOffset{0} + mCurrentChunk{0} { auto array = getCurrentArray(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()); mLast = mCurrent + array->length(); } @@ -524,10 +524,9 @@ class ColumnIterator : ChunkingPolicy { auto previousArray = getCurrentArray(); mFirstIndex += previousArray->length(); - mCurrentChunk++; auto array = getCurrentArray(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR) - (mFirstIndex >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()) - (mFirstIndex >> SCALE_FACTOR); mLast = mCurrent + array->length() + (mFirstIndex >> SCALE_FACTOR); } @@ -535,10 +534,9 @@ class ColumnIterator : ChunkingPolicy { auto previousArray = getCurrentArray(); mFirstIndex -= previousArray->length(); - mCurrentChunk--; auto array = getCurrentArray(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR) - (mFirstIndex >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()) - (mFirstIndex >> SCALE_FACTOR); mLast = mCurrent + array->length() + (mFirstIndex >> SCALE_FACTOR); } @@ -561,7 +559,7 @@ class ColumnIterator : ChunkingPolicy mCurrentChunk = mColumn->num_chunks() - 1; auto array = getCurrentArray(); mFirstIndex = mColumn->length() - array->length(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR) - (mFirstIndex >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()) - (mFirstIndex >> SCALE_FACTOR); mLast = mCurrent + array->length() + (mFirstIndex >> SCALE_FACTOR); } @@ -569,7 +567,7 @@ class ColumnIterator : ChunkingPolicy requires std::same_as> { checkSkipChunk(); - return (*(mCurrent - (mOffset >> SCALE_FACTOR) + ((*mCurrentPos + mOffset) >> SCALE_FACTOR)) & (1 << ((*mCurrentPos + mOffset) & 0x7))) != 0; + return (*(mCurrent + ((*mCurrentPos + *mGlobalOffset) >> SCALE_FACTOR)) & (1 << ((*mCurrentPos + *mGlobalOffset) & ((1 << SCALE_FACTOR) - 1)))) != 0; } auto operator*() const @@ -577,8 +575,8 @@ class ColumnIterator : ChunkingPolicy { checkSkipChunk(); auto list = std::static_pointer_cast(mColumn->chunk(mCurrentChunk)); - auto offset = list->value_offset(*mCurrentPos - mFirstIndex); - auto length = list->value_length(*mCurrentPos - mFirstIndex); + auto offset = list->value_offset(*mCurrentPos + *mGlobalOffset - mFirstIndex); + auto length = list->value_length(*mCurrentPos + *mGlobalOffset - mFirstIndex); return gsl::span const>{mCurrent + mFirstIndex + offset, mCurrent + mFirstIndex + (offset + length)}; } @@ -587,14 +585,14 @@ class ColumnIterator : ChunkingPolicy { checkSkipChunk(); auto array = std::static_pointer_cast(mColumn->chunk(mCurrentChunk)); - return array->GetView(*mCurrentPos - mFirstIndex); + return array->GetView(*mCurrentPos + *mGlobalOffset - mFirstIndex); } decltype(auto) operator*() const requires((!std::same_as>) && !std::same_as, arrow::ListArray> && !std::same_as, arrow::BinaryViewArray>) { checkSkipChunk(); - return *(mCurrent + (*mCurrentPos >> SCALE_FACTOR)); + return *(mCurrent + ((*mCurrentPos + *mGlobalOffset) >> SCALE_FACTOR)); } // Move to the chunk which containts element pos @@ -606,18 +604,18 @@ class ColumnIterator : ChunkingPolicy mutable unwrap_t const* mCurrent; int64_t const* mCurrentPos; + uint64_t const* mGlobalOffset; mutable unwrap_t const* mLast; arrow::ChunkedArray const* mColumn; mutable int mFirstIndex; mutable int mCurrentChunk; - mutable int mOffset; private: void checkSkipChunk() const requires((ChunkingPolicy::chunked == true) && std::same_as, arrow::ListArray>) { auto list = std::static_pointer_cast(mColumn->chunk(mCurrentChunk)); - if (O2_BUILTIN_UNLIKELY(*mCurrentPos - mFirstIndex >= list->length())) { + if (O2_BUILTIN_UNLIKELY(*mCurrentPos + *mGlobalOffset - mFirstIndex >= list->length())) { nextChunk(); } } @@ -625,7 +623,7 @@ class ColumnIterator : ChunkingPolicy void checkSkipChunk() const requires((ChunkingPolicy::chunked == true) && !std::same_as, arrow::ListArray>) { - if (O2_BUILTIN_UNLIKELY(((mCurrent + (*mCurrentPos >> SCALE_FACTOR)) >= mLast))) { + if (O2_BUILTIN_UNLIKELY(((mCurrent + ((*mCurrentPos + *mGlobalOffset) >> SCALE_FACTOR)) >= mLast))) { nextChunk(); } } @@ -639,7 +637,6 @@ class ColumnIterator : ChunkingPolicy requires(std::same_as, arrow::FixedSizeListArray>) { std::shared_ptr chunkToUse = mColumn->chunk(mCurrentChunk); - mOffset = chunkToUse->offset(); chunkToUse = std::dynamic_pointer_cast(chunkToUse)->values(); return std::static_pointer_cast>>(chunkToUse); } @@ -648,9 +645,7 @@ class ColumnIterator : ChunkingPolicy requires(std::same_as, arrow::ListArray>) { std::shared_ptr chunkToUse = mColumn->chunk(mCurrentChunk); - mOffset = chunkToUse->offset(); chunkToUse = std::dynamic_pointer_cast(chunkToUse)->values(); - mOffset = chunkToUse->offset(); return std::static_pointer_cast>>(chunkToUse); } @@ -658,7 +653,6 @@ class ColumnIterator : ChunkingPolicy requires(!std::same_as, arrow::FixedSizeListArray> && !std::same_as, arrow::ListArray>) { std::shared_ptr chunkToUse = mColumn->chunk(mCurrentChunk); - mOffset = chunkToUse->offset(); return std::static_pointer_cast>(chunkToUse); } }; @@ -1212,7 +1206,7 @@ struct TableIterator : IP, C... { { using namespace o2::soa; auto f = framework::overloaded{ - [this](T*) -> void { T::mColumnIterator.mCurrentPos = &this->mRowIndex; }, + [this](T*) -> void { T::mColumnIterator.mCurrentPos = &this->mRowIndex; T::mColumnIterator.mGlobalOffset = &this->mOffset; }, [this](T*) -> void { bindDynamicColumn(typename T::bindings_t{}); }, [this](T*) -> void {}, }; @@ -3468,11 +3462,6 @@ struct Concat : Table, o2::aod::Hash<"CONC/0"_h>, o2::ao { } - Concat(std::vector>&& tables) - : Concat{ArrowHelpers::concatTables(std::move(tables))} - { - } - Concat(Ts const&... t) : Concat{ArrowHelpers::concatTables({t.asArrowTableRef()...})} { @@ -3729,19 +3718,31 @@ class FilteredBase : public T } } - inline void adoptSelection(gandiva::Selection const& selection) + template + inline void adoptSelection(S) + { + } + + template + requires(std::same_as, gandiva::Selection>) + inline void adoptSelection(S selection) { mSelectedRows = getSpan(selection); mCached = false; } - inline void adoptSelection(SelectionVector&& selection) + template + requires(std::same_as, SelectionVector>) + inline void adoptSelection(S selection) { mSelectedRowsCache = std::move(selection); + mSelectedRows = std::span{mSelectedRowsCache}; mCached = true; } - inline void adoptSelection(std::span const& selection) + template + requires(std::same_as, std::span>) + inline void adoptSelection(S selection) { mSelectedRows = selection; mCached = false; @@ -3778,7 +3779,7 @@ class Filtered : public FilteredBase } Filtered(std::vector&& tables, is_a_selection auto selection) - : FilteredBase{std::move(tables), selection} {} + : FilteredBase{std::move(tables), std::forward(selection)} {} Filtered operator+(is_a_selection auto selection) { @@ -3908,7 +3909,7 @@ class Filtered> : public FilteredBase } Filtered(std::vector>&& tables, is_a_selection auto selection) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), selection) + : FilteredBase(std::move(extractTablesFromFiltered(tables)), std::forward(selection)) { for (auto& table : tables) { *this *= table; diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index 313ad91fbfcef..e82b71064a747 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -177,42 +177,36 @@ o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector> columns; - std::vector> resultFields = tables[0]->schema()->fields(); + std::vector> resultFields = tables.front()->schema()->fields(); auto compareFields = [](std::shared_ptr const& f1, std::shared_ptr const& f2) { // Let's do this with stable sorting. return (!f1->Equals(f2)) && (f1->name() < f2->name()); }; - std::ranges::for_each(tables.begin() + 1, tables.end(), [&resultFields, &compareFields](auto const& ref) mutable { - std::vector> const& fields = ref->fields(); + + for (auto i = 1; i < tables.size(); ++i) { + auto const& fields = tables[i]->fields(); std::vector> intersection; std::ranges::set_intersection(resultFields, fields, std::back_inserter(intersection), compareFields); resultFields.swap(intersection); - }); + } - std::ranges::transform(resultFields, std::back_inserter(columns), [&tables](auto const& field){ + for (auto const& field : resultFields) { arrow::ArrayVector chunks; - std::ranges::for_each(tables, [&field, &chunks](auto const& table){ + for (auto const& table : tables) { auto ci = table->schema()->GetFieldIndex(field->name()); if (ci == -1) { - throw std::runtime_error("Unable to find field " + field->name()); + throw framework::runtime_error_f("Unable to find field {}", field->name().c_str()); } auto column = table->column(ci); auto otherChunks = column->chunks(); chunks.insert(chunks.end(), otherChunks.begin(), otherChunks.end()); - }); - return std::make_shared(chunks); - }); + } + columns.push_back(std::make_shared(chunks)); + } return {arrow::Table::Make(std::make_shared(resultFields), columns)}; } -o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector>&& tables) -{ - std::vector refs; - std::ranges::transform(tables, std::back_inserter(refs),[](auto const& table){ return ArrowTableRef{table}; }); - return concatTables(std::move(refs)); -} - // ASCII-only lowercase. Column labels are plain identifiers, so we deliberately // avoid the locale-aware std::tolower: it goes through the C locale facet on // every character and dominated getIndexFromLabel in profiles. diff --git a/Framework/Core/test/test_ASoA.cxx b/Framework/Core/test/test_ASoA.cxx index 89a454ffeb50f..d62bceebdd246 100644 --- a/Framework/Core/test/test_ASoA.cxx +++ b/Framework/Core/test/test_ASoA.cxx @@ -106,7 +106,9 @@ TEST_CASE("TestTableIteration") auto i = ColumnIterator(table->column(0).get()); int64_t pos = 0; + uint64_t offset = 0; i.mCurrentPos = &pos; + i.mGlobalOffset = &offset; REQUIRE(*i == 0); pos++; REQUIRE(*i == 0); @@ -383,7 +385,7 @@ TEST_CASE("TestConcatTables") static_assert(std::same_as, o2::aod::test::Y, o2::aod::test::X, o2::aod::test::Z>>, "Bad nested join"); static_assert(std::same_as, o2::aod::test::X>>, "Bad intersection of columns"); - ConcatTest tests{tableA, tableB}; + ConcatTest tests{{tableA, tableB}}; REQUIRE(16 == tests.size()); for (auto& test : tests) { REQUIRE(test.index() == test.x()); diff --git a/Framework/Core/test/test_GroupSlicer.cxx b/Framework/Core/test/test_GroupSlicer.cxx index fe221d55ca89a..f282dcbc5c33b 100644 --- a/Framework/Core/test/test_GroupSlicer.cxx +++ b/Framework/Core/test/test_GroupSlicer.cxx @@ -195,8 +195,9 @@ TEST_CASE("GroupSlicerSeveralAssociated") {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}, {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, {trkTableX}); - s = slices.updateCacheEntry(1, {trkTableY}); - s = slices.updateCacheEntry(2, {trkTableZ}); + s &= slices.updateCacheEntry(1, {trkTableY}); + s &= slices.updateCacheEntry(2, {trkTableZ}); + REQUIRE(s.ok()); o2::framework::GroupSlicer g(e, tt, slices); auto count = 0; @@ -631,9 +632,9 @@ TEST_CASE("EmptySliceables") TEST_CASE("ArrowDirectSlicing") { int counts[] = {5, 5, 5, 4, 1}; - int offsets[] = {0, 5, 10, 15, 19, 20}; + int const offsets[] = {0, 5, 10, 15, 19, 20}; int ids[] = {0, 1, 2, 3, 4}; - int sizes[] = {4, 1, 12, 5, 2}; + int const sizes[] = {4, 1, 12, 5, 2}; using BigE = soa::Join; @@ -683,34 +684,25 @@ TEST_CASE("ArrowDirectSlicing") REQUIRE(slices_vec[i]->length() == counts[i]); } - std::vector slices; - std::vector offsts; auto bk = Entry(soa::getLabelFromType(), soa::getMatcherFromTypeForKey("fID"), "fID"); ArrowTableSlicingCache cache({bk}); auto s = cache.updateCacheEntry(0, {evtTable}); + REQUIRE(s.ok()); auto lcache = cache.getCacheFor(bk); for (auto i = 0u; i < 5; ++i) { - auto [offset, count] = lcache.getSliceFor(i); - auto tbl = b_e.asArrowTableRef().slice({static_cast(offset), count}); - auto ca = tbl->GetColumnByName("fArr"); - auto cb = tbl->GetColumnByName("fBoo"); - auto cv = tbl->GetColumnByName("fLst"); - REQUIRE(ca->length() == counts[i]); - REQUIRE(cb->length() == counts[i]); - REQUIRE(cv->length() == counts[i]); - REQUIRE(ca->Equals(slices_array[i])); - REQUIRE(cb->Equals(slices_bool[i])); - REQUIRE(cv->Equals(slices_vec[i])); + auto [loffset, count] = lcache.getSliceFor(i); + auto tbl = b_e.asArrowTableRef().slice({static_cast(loffset), count}); + REQUIRE(tbl.range.size == counts[i]); } int j = 0u; for (auto i = 0u; i < 5; ++i) { - auto [offset, count] = lcache.getSliceFor(i); - auto tbl = BigE{{b_e.asArrowTableRef().slice({static_cast(offset), count})}}; + auto [loffset, count] = lcache.getSliceFor(i); + auto tbl = BigE{{b_e.asArrowTableRef().slice({static_cast(loffset), count})}}; REQUIRE(tbl.size() == counts[i]); for (auto& row : tbl) { REQUIRE(row.id() == ids[i]); - REQUIRE(row.boo() == (j % 2 == 0)); + CHECK(row.boo() == (j % 2 == 0)); auto rid = row.globalIndex(); auto arr = row.arr(); REQUIRE(arr[0] == 0.1f * (float)rid); @@ -729,7 +721,7 @@ TEST_CASE("ArrowDirectSlicing") TEST_CASE("TestSlicingException") { - int offsets[] = {0, 5, 10, 15, 19, 20}; + int const offsets[] = {0, 5, 10, 15, 19, 20}; int ids[] = {0, 1, 2, 4, 3}; TableBuilder builderE; From 3d7ced30734e35f1bf58935cbca1e0912555539a Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Tue, 23 Jun 2026 17:09:40 +0200 Subject: [PATCH 18/25] make O2Physics compile --- Framework/Core/include/Framework/ASoA.h | 16 ++++++++-------- .../Core/include/Framework/AnalysisHelpers.h | 4 ++-- .../Core/include/Framework/AnalysisManagers.h | 4 ++-- Framework/Core/include/Framework/GroupSlicer.h | 2 +- .../Core/include/Framework/GroupedCombinations.h | 10 +++++----- Framework/Core/test/test_ASoA.cxx | 16 ++++++++-------- Framework/Core/test/test_ASoAHelpers.cxx | 4 ++-- Framework/Core/test/test_AnalysisDataModel.cxx | 4 ++-- Framework/Core/test/test_AnalysisTask.cxx | 2 +- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 3bacbdcdbfb32..6caa6f89b25b3 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1569,14 +1569,14 @@ template auto prepareFilteredSlice(T const* table, o2::soa::ArrowTableRef slice) { if (slice.range.offset >= static_cast(table->tableSize())) { - Filtered fresult{{{slice}}, SelectionVector{}, 0}; + Filtered fresult{{slice}, SelectionVector{}}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); } return fresult; } auto slicedSelection = sliceSelection(table->getSelectedRows(), slice->num_rows(), slice.range.offset); - Filtered fresult{{{slice}}, std::move(slicedSelection)}; + Filtered fresult{{slice}, std::move(slicedSelection)}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); } @@ -1592,7 +1592,7 @@ auto doFilteredSliceBy(T const* table, o2::framework::PresliceBase().data(), container.bindingKey.key.c_str()); } } - auto slice = container.getSliceFor(value, table->asArrowTable()); + auto slice = container.getSliceFor(value, table->asArrowTableRef()); return prepareFilteredSlice(table, slice); } @@ -1617,7 +1617,7 @@ auto doFilteredSliceByCached(T const* table, framework::expressions::BindingNode auto localCache = cache.ptr->getCacheFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); auto [offset, count] = localCache.getSliceFor(value); - return prepareFilteredSlice(table, table->asArrowTableRef().slice(static_cast(offset), count)); + return prepareFilteredSlice(table, table->asArrowTableRef().slice({static_cast(offset), count})); } template @@ -1644,7 +1644,7 @@ auto doSliceByCachedUnsorted(T const* table, framework::expressions::BindingNode template auto select(T const& t, framework::expressions::Filter const& f) { - return Filtered({t.asArrowTable()}, selectionToVector(framework::expressions::createSelection(t.asArrowTable(), f))); + return Filtered({t.asArrowTableRef()}, selectionToVector(framework::expressions::createSelection(t.asArrowTable(), f))); } arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label); @@ -3578,7 +3578,7 @@ class FilteredBase : public T [[nodiscard]] int64_t tableSize() const { - return table_t::asArrowTable()->num_rows(); + return this->asArrowTableRef().range.size; } auto const& getSelectedRows() const @@ -3591,12 +3591,12 @@ class FilteredBase : public T SelectionVector newSelection; newSelection.resize(static_cast(end - start + 1)); std::iota(newSelection.begin(), newSelection.end(), start); - return self_t{{this->asArrowTable()}, std::move(newSelection), 0}; + return self_t{{this->asArrowTableRef()}, std::move(newSelection)}; } auto emptySlice() const { - return self_t{{this->asArrowTable()}, SelectionVector{}, 0}; + return self_t{{this->asArrowTableRef()}, SelectionVector{}}; } static inline auto getSpan(gandiva::Selection const& sel) diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index 7ddbf01a2eb13..7acdda13b1525 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -983,7 +983,7 @@ struct Partition { void bindTable(T const& table) { - intializeCaches(T::table_t::hashes(), table.asArrowTable()->schema()); + intializeCaches(T::table_t::hashes(), table.asArrowTableRef()->schema()); if (dataframeChanged) { mFiltered = getTableFromFilter(table, soa::selectionToVector(framework::expressions::createSelection(table.asArrowTable(), gfilter))); dataframeChanged = false; @@ -1096,7 +1096,7 @@ template auto Attach(T const& table) { using output_t = Join, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Cs...>>; - return output_t{{table.asArrowTable()}, table.offset()}; + return output_t{{table.asArrowTableRef()}}; } } // namespace o2::soa diff --git a/Framework/Core/include/Framework/AnalysisManagers.h b/Framework/Core/include/Framework/AnalysisManagers.h index 4c683f74a7831..306ceb8032b71 100644 --- a/Framework/Core/include/Framework/AnalysisManagers.h +++ b/Framework/Core/include/Framework/AnalysisManagers.h @@ -353,7 +353,7 @@ bool prepareOutput(ProcessingContext& context, T& defines) defines.projectors.data(), defines.projector, defines.schema)); - defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTable(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); + defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTableRef(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); return true; } @@ -385,7 +385,7 @@ bool prepareDelayedOutput(ProcessingContext& context, T& defines) defines.projectors.data(), defines.projector, defines.schema)); - defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTable(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); + defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTableRef(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); return true; } diff --git a/Framework/Core/include/Framework/GroupSlicer.h b/Framework/Core/include/Framework/GroupSlicer.h index a72af7da85a08..f06cd6a0cd916 100644 --- a/Framework/Core/include/Framework/GroupSlicer.h +++ b/Framework/Core/include/Framework/GroupSlicer.h @@ -194,7 +194,7 @@ struct GroupSlicer { } } } - std::decay_t typedTable{{originalTable.asArrowTable()}, std::move(s)}; + std::decay_t typedTable{{originalTable.asArrowTableRef()}, std::move(s)}; typedTable.bindInternalIndicesTo(&originalTable); return typedTable; } diff --git a/Framework/Core/include/Framework/GroupedCombinations.h b/Framework/Core/include/Framework/GroupedCombinations.h index b0a6c9e658a10..d8c6aea44f31d 100644 --- a/Framework/Core/include/Framework/GroupedCombinations.h +++ b/Framework/Core/include/Framework/GroupedCombinations.h @@ -70,15 +70,15 @@ struct GroupedCombinationsGenerator { template GroupedIterator(const GroupingPolicy& groupingPolicy, const G& grouping, const std::tuple& associated, SliceCache* cache_) : GroupingPolicy(groupingPolicy), - mGrouping{std::make_shared(std::vector{grouping.asArrowTable()})}, + mGrouping{std::make_shared(std::vector{grouping.asArrowTableRef()})}, mAssociated{std::make_shared>(std::make_tuple(std::get(pack{})>(associated)...))}, mIndexColumns{getMatchingIndexNode()...}, cache{cache_} { if constexpr (soa::is_filtered_table>) { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}, grouping.getSelectedRows()); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}, grouping.getSelectedRows()); } else { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}); } setMultipleGroupingTables(grouping); if (!this->mIsEnd) { @@ -94,9 +94,9 @@ struct GroupedCombinationsGenerator { void setTables(const G& grouping, const std::tuple& associated) { if constexpr (soa::is_filtered_table>) { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}, grouping.getSelectedRows()); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}, grouping.getSelectedRows()); } else { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}); } mAssociated = std::make_shared>(std::make_tuple(std::get(pack{})>(associated)...)); setMultipleGroupingTables(grouping); diff --git a/Framework/Core/test/test_ASoA.cxx b/Framework/Core/test/test_ASoA.cxx index d62bceebdd246..2ac6603671200 100644 --- a/Framework/Core/test/test_ASoA.cxx +++ b/Framework/Core/test/test_ASoA.cxx @@ -430,7 +430,7 @@ TEST_CASE("TestConcatTables") gandiva::Selection selection_f = expressions::createSelection(tableA, testf); TestA testA{tableA}; - FilteredTest filtered{{testA.asArrowTable()}, selection_f}; + FilteredTest filtered{{testA.asArrowTableRef()}, selection_f}; REQUIRE(2 == filtered.size()); auto i = 0; @@ -453,7 +453,7 @@ TEST_CASE("TestConcatTables") selectionConcat->SetIndex(2, 10); selectionConcat->SetNumSlots(3); ConcatTest concatTest{tableA, tableB}; - FilteredConcatTest concatTestTable{{concatTest.asArrowTable()}, selectionConcat}; + FilteredConcatTest concatTestTable{{concatTest.asArrowTableRef()}, selectionConcat}; REQUIRE(3 == concatTestTable.size()); i = 0; @@ -483,7 +483,7 @@ TEST_CASE("TestConcatTables") selectionJoin->SetIndex(2, 4); selectionJoin->SetNumSlots(3); JoinedTest testJoin{{tableA, tableC}}; - FilteredJoinTest filteredJoin{{testJoin.asArrowTable()}, selectionJoin}; + FilteredJoinTest filteredJoin{{testJoin.asArrowTableRef()}, selectionJoin}; i = 0; REQUIRE(filteredJoin.begin() != filteredJoin.end()); @@ -600,12 +600,12 @@ TEST_CASE("TestFilteredOperators") TestA testA{tableA}; auto s1 = expressions::createSelection(testA.asArrowTable(), f1); - FilteredTest filtered1{{testA.asArrowTable()}, s1}; + FilteredTest filtered1{{testA.asArrowTableRef()}, s1}; REQUIRE(4 == filtered1.size()); REQUIRE(filtered1.begin() != filtered1.end()); auto s2 = expressions::createSelection(testA.asArrowTable(), f2); - FilteredTest filtered2{{testA.asArrowTable()}, s2}; + FilteredTest filtered2{{testA.asArrowTableRef()}, s2}; REQUIRE(2 == filtered2.size()); REQUIRE(filtered2.begin() != filtered2.end()); @@ -633,7 +633,7 @@ TEST_CASE("TestFilteredOperators") expressions::Filter f3 = o2::aod::test::x < 3; auto s3 = expressions::createSelection(testA.asArrowTable(), f3); - FilteredTest filtered3{{testA.asArrowTable()}, s3}; + FilteredTest filtered3{{testA.asArrowTableRef()}, s3}; REQUIRE(3 == filtered3.size()); REQUIRE(filtered3.begin() != filtered3.end()); @@ -677,7 +677,7 @@ TEST_CASE("TestNestedFiltering") TestA testA{tableA}; auto s1 = expressions::createSelection(testA.asArrowTable(), f1); - FilteredTest filtered{{testA.asArrowTable()}, s1}; + FilteredTest filtered{{testA.asArrowTableRef()}, s1}; REQUIRE(4 == filtered.size()); REQUIRE(filtered.begin() != filtered.end()); @@ -774,7 +774,7 @@ TEST_CASE("TestIndexToFiltered") expressions::Filter flt = o2::aod::test::someBool == true; using Flt = o2::soa::Filtered; auto selection = expressions::createSelection(o.asArrowTable(), flt); - Flt f{{o.asArrowTable()}, selection}; + Flt f{{o.asArrowTableRef()}, selection}; r.bindExternalIndices(&f); auto it = r.begin(); it.moveByIndex(23); diff --git a/Framework/Core/test/test_ASoAHelpers.cxx b/Framework/Core/test/test_ASoAHelpers.cxx index c4d7f727aa295..701dc0bbced50 100644 --- a/Framework/Core/test/test_ASoAHelpers.cxx +++ b/Framework/Core/test/test_ASoAHelpers.cxx @@ -72,7 +72,7 @@ TEST_CASE("IteratorTuple") REQUIRE(*(static_cast(std::get<1>(maxOffset2)).getIterator().mCurrentPos) == 8); expressions::Filter filter = test::x > 3; - auto filtered = Filtered{{tests.asArrowTable()}, o2::framework::expressions::createSelection(tests.asArrowTable(), filter)}; + auto filtered = Filtered{{tests.asArrowTableRef()}, o2::framework::expressions::createSelection(tests.asArrowTable(), filter)}; std::tuple, Filtered> filteredTuple = std::make_tuple(filtered, filtered); auto it1 = std::get<0>(filteredTuple).begin(); @@ -164,7 +164,7 @@ TEST_CASE("CombinationsGeneratorConstruction") o2::framework::expressions::Filter filter = test::x > 3; auto s1 = o2::framework::expressions::createSelection(testsA.asArrowTable(), filter); - auto filtered = Filtered{{testsA.asArrowTable()}, s1}; + auto filtered = Filtered{{testsA.asArrowTableRef()}, s1}; CombinationsGenerator, Filtered>>::CombinationsIterator combItFiltered(CombinationsStrictlyUpperIndexPolicy(filtered, filtered)); REQUIRE(!(static_cast(std::get<0>(*(combItFiltered))).getIterator().mCurrentPos == nullptr)); diff --git a/Framework/Core/test/test_AnalysisDataModel.cxx b/Framework/Core/test/test_AnalysisDataModel.cxx index d6db060229a49..ae0914a285110 100644 --- a/Framework/Core/test/test_AnalysisDataModel.cxx +++ b/Framework/Core/test/test_AnalysisDataModel.cxx @@ -50,8 +50,8 @@ TEST_CASE("TestJoinedTablesContains") using Test = o2::soa::Join; Test tests{{tXY, tZD}}; - REQUIRE(tests.asArrowTable()->num_columns() != 0); - REQUIRE(tests.asArrowTable()->num_columns() == + REQUIRE(tests.asArrowTableRef()->num_columns() != 0); + REQUIRE(tests.asArrowTableRef()->num_columns() == tXY->num_columns() + tZD->num_columns()); auto tests2 = join(XY{tXY}, ZD{tZD}); static_assert(std::same_as, diff --git a/Framework/Core/test/test_AnalysisTask.cxx b/Framework/Core/test/test_AnalysisTask.cxx index f5d8c4c43bc38..cb710b9a3871c 100644 --- a/Framework/Core/test/test_AnalysisTask.cxx +++ b/Framework/Core/test/test_AnalysisTask.cxx @@ -314,7 +314,7 @@ TEST_CASE("TestPartitionIteration") expressions::Filter f1 = aod::test::x < 4.0f; auto selection = expressions::createSelection(testA.asArrowTable(), f1); - FilteredTest filtered{{testA.asArrowTable()}, o2::soa::selectionToVector(selection)}; + FilteredTest filtered{{testA.asArrowTableRef()}, o2::soa::selectionToVector(selection)}; PartitionFilteredTest p2 = aod::test::y > 9.0f; p2.bindTable(filtered); From 0b9f6bd4225345eb600ff026993e57ec34dd022f Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Thu, 9 Jul 2026 14:06:29 +0200 Subject: [PATCH 19/25] minor fix --- Framework/Core/include/Framework/AnalysisManagers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/Core/include/Framework/AnalysisManagers.h b/Framework/Core/include/Framework/AnalysisManagers.h index 306ceb8032b71..8b426576ce556 100644 --- a/Framework/Core/include/Framework/AnalysisManagers.h +++ b/Framework/Core/include/Framework/AnalysisManagers.h @@ -348,7 +348,7 @@ bool prepareOutput(ProcessingContext& context, T& defines) } using D = o2::aod::Hash; - defines.extension = std::make_shared(o2::framework::spawner(originalTable, + defines.extension = std::make_shared(o2::framework::spawner(originalTable.tablePtr, o2::aod::label(), defines.projectors.data(), defines.projector, @@ -380,7 +380,7 @@ bool prepareDelayedOutput(ProcessingContext& context, T& defines) } using D = o2::aod::Hash; - defines.extension = std::make_shared(o2::framework::spawner(originalTable, + defines.extension = std::make_shared(o2::framework::spawner(originalTable.tablePtr, o2::aod::label(), defines.projectors.data(), defines.projector, From d8f5fa0d40bf9133ad0cedc3f964b3719b17abf5 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 13 Jul 2026 14:46:32 +0200 Subject: [PATCH 20/25] bug with filtered slice --- Framework/Core/include/Framework/ASoA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 6caa6f89b25b3..f2e8440f68ea4 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1575,7 +1575,7 @@ auto prepareFilteredSlice(T const* table, o2::soa::ArrowTableRef slice) } return fresult; } - auto slicedSelection = sliceSelection(table->getSelectedRows(), slice->num_rows(), slice.range.offset); + auto slicedSelection = sliceSelection(table->getSelectedRows(), slice.range.size, slice.range.offset); Filtered fresult{{slice}, std::move(slicedSelection)}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); From c845be70b57f3041c68bb8faa21460c4b9b8e928 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 13 Jul 2026 15:11:36 +0200 Subject: [PATCH 21/25] add test --- Framework/Core/test/test_ASoA.cxx | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Framework/Core/test/test_ASoA.cxx b/Framework/Core/test/test_ASoA.cxx index 2ac6603671200..2160fd884259c 100644 --- a/Framework/Core/test/test_ASoA.cxx +++ b/Framework/Core/test/test_ASoA.cxx @@ -1254,6 +1254,63 @@ TEST_CASE("TestSliceByCachedMismatched") } } +TEST_CASE("TestSliceByCachedFiltered") +{ + TableBuilder b; + auto writer = b.cursor(); + for (auto i = 0; i < 20; ++i) { + writer(0, i, i % 3 == 0); + } + auto origins = b.finalize(); + o2::aod::Origints o{origins}; + + TableBuilder w; + auto writer_w = w.cursor(); + auto step = -1; + for (auto i = 0; i < 5 * 20; ++i) { + if (i % 5 == 0) { + ++step; + } + writer_w(0, step); + } + auto refs = w.finalize(); + o2::aod::References r{refs}; + + TableBuilder w2; + auto writer_w2 = w2.cursor(); + step = -1; + for (auto i = 0; i < 5 * 20; ++i) { + if (i % 3 == 0) { + ++step; + } + writer_w2(0, step); + } + auto refs2 = w2.finalize(); + o2::aod::OtherReferences r2{refs2}; + + using J = o2::soa::Join; + J rr{{refs, refs2}}; + + auto rrf = rr.select(o2::aod::test::altOrigintId > 2 && o2::aod::test::altOrigintId < 15); + + auto key = "fIndex" + o2::framework::cutString(o2::soa::getLabelFromType()) + "_alt"; + ArrowTableSlicingCache atscache({{o2::soa::getLabelFromTypeForKey(key), o2::soa::getMatcherFromTypeForKey(key), key}}); + auto s = atscache.updateCacheEntry(0, refs2); + SliceCache cache{&atscache}; + + for (auto& oi : o) { + auto cachedSlice = rrf.sliceByCached(o2::aod::test::altOrigintId, oi.globalIndex(), cache); + if (oi.globalIndex() <= 2 || oi.globalIndex() >= 15) { + CHECK(cachedSlice.size() == 0); + } else { + CHECK(cachedSlice.size() == 3); + } + for (auto& ri : cachedSlice) { + REQUIRE(ri.altOrigintId() == oi.globalIndex()); + } + } +} + TEST_CASE("TestIndexUnboundExceptions") { TableBuilder b; From dfa07ab2941ebda79c00578ecf705f9bf2c4020c Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 20 Jul 2026 10:35:22 +0200 Subject: [PATCH 22/25] include cstdint --- Framework/Core/include/Framework/Concepts.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h index b96d531a69e56..e868b439bb286 100644 --- a/Framework/Core/include/Framework/Concepts.h +++ b/Framework/Core/include/Framework/Concepts.h @@ -13,6 +13,7 @@ #define O2_FRAMEWORK_CONCEPTS_H #include +#include #include namespace o2::aod From 6b31c6edc37336dd2f852933c402f1d672b02456 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 20 Jul 2026 10:47:08 +0200 Subject: [PATCH 23/25] Move inline function to header to avoid linking issue with gcc Co-authored-by: Anton Alkin --- Framework/Core/include/Framework/ASoA.h | 8 +++++++- Framework/Core/src/ASoA.cxx | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index f2e8440f68ea4..4ff52b3e70e4d 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -58,7 +58,13 @@ void missingFilterDeclaration(int hash, int ai); void notBoundTable(const char* tableName); void* extractCCDBPayload(char* payload, size_t size, TClass const* cl, const char* what); -constexpr char asciiToLower(char c); +// ASCII-only lowercase. Column labels are plain identifiers, so we deliberately +// avoid the locale-aware std::tolower: it goes through the C locale facet on +// every character and dominated getIndexFromLabel in profiles. +constexpr char asciiToLower(char c) +{ + return (c >= 'A' && c <= 'Z') ? static_cast(c + 32) : c; +} template auto createFieldsFromColumns(framework::pack) diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index e82b71064a747..605ab891bdacc 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -207,13 +207,6 @@ o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector(resultFields), columns)}; } -// ASCII-only lowercase. Column labels are plain identifiers, so we deliberately -// avoid the locale-aware std::tolower: it goes through the C locale facet on -// every character and dominated getIndexFromLabel in profiles. -constexpr char asciiToLower(char c) -{ - return (c >= 'A' && c <= 'Z') ? static_cast(c + 32) : c; -} arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label) { From 0269632a16d6b25e8b21754517d38fec1ae55b45 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Mon, 20 Jul 2026 08:48:09 +0000 Subject: [PATCH 24/25] Please consider the following formatting changes --- Framework/Core/include/Framework/ASoA.h | 67 +++++++------------ .../Core/include/Framework/AnalysisHelpers.h | 3 +- Framework/Core/include/Framework/Concepts.h | 11 ++- Framework/Core/src/ASoA.cxx | 5 +- 4 files changed, 30 insertions(+), 56 deletions(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 4ff52b3e70e4d..4af438802304a 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -230,8 +230,7 @@ static consteval int getIndexPosToKey_impl() /// Base type for table metadata template struct TableMetadata { - static constexpr void isTableMetadata() - {}; + static constexpr void isTableMetadata() {}; using columns = framework::pack; using persistent_columns_t = framework::selected_pack; using external_index_columns_t = framework::selected_pack; @@ -263,8 +262,7 @@ struct TableMetadata { template struct MetadataTrait { - static constexpr void isMetadataTrait() - {}; + static constexpr void isMetadataTrait() {}; using metadata = void; }; @@ -272,8 +270,7 @@ struct MetadataTrait { /// type signature template struct Hash { - static constexpr void isHash() - {}; + static constexpr void isHash() {}; static constexpr uint32_t hash = H; static constexpr char const* const str{""}; }; @@ -295,8 +292,7 @@ consteval auto filterForKey() #define O2HASH(_Str_) \ template <> \ struct Hash<_Str_ ""_h> { \ - static constexpr void isHash() \ - {}; \ + static constexpr void isHash() {}; \ static constexpr uint32_t hash = _Str_ ""_h; \ static constexpr char const* const str{_Str_}; \ }; @@ -305,10 +301,8 @@ consteval auto filterForKey() #define O2ORIGIN(_Str_) \ template <> \ struct Hash<_Str_ ""_h> { \ - static constexpr void isHash() \ - {}; \ - static constexpr void isOriginHash() \ - {}; \ + static constexpr void isHash() {}; \ + static constexpr void isOriginHash() {}; \ static constexpr header::DataOrigin origin{_Str_}; \ static constexpr uint32_t hash = _Str_ ""_h; \ static constexpr char const* const str{_Str_}; \ @@ -665,8 +659,7 @@ class ColumnIterator : ChunkingPolicy template struct Column { - static constexpr void isIteratableColumn() - {}; + static constexpr void isIteratableColumn() {}; using inherited_t = INHERIT; Column(ColumnIterator const& it) @@ -702,8 +695,7 @@ struct Column { /// method call. template struct DynamicColumn { - static constexpr void isDynamicColumn() - {}; + static constexpr void isDynamicColumn() {}; using inherited_t = INHERIT; static constexpr const char* const& columnLabel() { return INHERIT::mLabel; } @@ -711,8 +703,7 @@ struct DynamicColumn { template struct IndexColumn { - static constexpr void isEnumeratingColumn() - {}; + static constexpr void isEnumeratingColumn() {}; using inherited_t = INHERIT; static constexpr const uint32_t hash = 0; @@ -721,8 +712,7 @@ struct IndexColumn { template struct MarkerColumn { - static constexpr void isMarkingColumn() - {}; + static constexpr void isMarkingColumn() {}; using inherited_t = INHERIT; static constexpr const uint32_t hash = 0; @@ -832,8 +822,7 @@ struct IndexPolicyBase { }; struct RowViewSentinel { - static constexpr void isRowViewSentinel() - {}; + static constexpr void isRowViewSentinel() {}; int64_t const index; }; @@ -944,8 +933,7 @@ struct FilteredIndexPolicy : IndexPolicyBase { }; struct DefaultIndexPolicy : IndexPolicyBase { - static constexpr void isDefaultIndexPolicy() - {}; + static constexpr void isDefaultIndexPolicy() {}; /// Needed to be able to copy the policy DefaultIndexPolicy() = default; DefaultIndexPolicy(DefaultIndexPolicy&&) = default; @@ -1031,8 +1019,7 @@ struct ColumnDataHolder { template struct TableIterator : IP, C... { public: - static constexpr void isTableIterator() - {}; + static constexpr void isTableIterator() {}; using self_t = TableIterator; using policy_t = IP; using all_columns = framework::pack; @@ -1409,8 +1396,7 @@ namespace o2::framework { /// tracks origin in bindingKey matcher to handle the correct arguments struct PreslicePolicyBase { - static constexpr void isPreslicePolicy() - {}; + static constexpr void isPreslicePolicy() {}; const std::string binding; Entry bindingKey; @@ -1434,8 +1420,7 @@ struct PreslicePolicyGeneral : public PreslicePolicyBase { template struct PresliceBase : public Policy { - static constexpr void isPresliceContainer() - {}; + static constexpr void isPresliceContainer() {}; constexpr static bool optional = OPT; using target_t = T; using policy_t = Policy; @@ -1450,7 +1435,7 @@ struct PresliceBase : public Policy { { if constexpr (OPT) { if (Policy::isMissing()) { - return {nullptr, {0,0}}; + return {nullptr, {0, 0}}; } } return Policy::getSliceFor(value, input); @@ -1489,8 +1474,7 @@ using PresliceOptional = PresliceBase; /// /// preslices.perCol; struct PresliceGroup { - static constexpr void isPresliceGroup() - {}; + static constexpr void isPresliceGroup() {}; }; } // namespace o2::framework @@ -1703,8 +1687,7 @@ template ; using table_t = self_t; @@ -1912,7 +1895,7 @@ class Table using iterator_template = TableIteratorBase; template - using iterator_template_o = decltype([](){ + using iterator_template_o = decltype([]() { if constexpr (sizeof...(Ts) == 0) { return iterator_template{}; } else { @@ -3340,8 +3323,7 @@ namespace o2::soa { template struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...> { - static constexpr void isJoin() - {}; + static constexpr void isJoin() {}; using base = Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...>; Join(std::vector&& tables) @@ -3503,8 +3485,7 @@ template class FilteredBase : public T { public: - static constexpr void isFilteredBase() - {}; + static constexpr void isFilteredBase() {}; using self_t = FilteredBase; using table_t = typename T::table_t; using T::originals; @@ -4028,8 +4009,7 @@ class Filtered> : public FilteredBase /// First index will be used by process() as the grouping template struct IndexTable : Table { - static constexpr void isIndexTable() - {}; + static constexpr void isIndexTable() {}; using self_t = IndexTable; using base_t = Table; using table_t = base_t; @@ -4073,8 +4053,7 @@ struct IndexTable : Table { template struct SmallGroupsBase : public Filtered { - static constexpr void isSmallGroups() - {}; + static constexpr void isSmallGroups() {}; static constexpr bool applyFilters = APPLY; SmallGroupsBase(std::vector&& tables, is_a_selection auto selection) diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index 7acdda13b1525..e4d9682b35302 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -645,8 +645,7 @@ struct Produces : WritingCursor { /// /// Notice the label MySetOfProduces is just a mnemonic and can be omitted. struct ProducesGroup { - static constexpr void isProducesGroup() - {}; + static constexpr void isProducesGroup() {}; }; /// Helper template for table transformations diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h index e868b439bb286..fea40b25ff1ff 100644 --- a/Framework/Core/include/Framework/Concepts.h +++ b/Framework/Core/include/Framework/Concepts.h @@ -46,18 +46,16 @@ concept is_persistent_column = requires(C c) { c.isIteratableColumn(); }; /// 2. require self-index column template -concept is_self_index_column = requires(C c) -{ +concept is_self_index_column = requires(C c) { typename C::compatible_signature; - //requires aod::is_aod_hash; + // requires aod::is_aod_hash; typename C::self_index_t; requires std::same_as; }; /// 3. require bindable index column template -concept is_index_column = requires(C c) -{ +concept is_index_column = requires(C c) { typename C::binding_t; requires not_void; }; @@ -138,8 +136,7 @@ concept is_table_or_iterator = is_table || is_iterator; /// 10. require soa::IndexTable template -concept is_index_table = requires(T t) -{ +concept is_index_table = requires(T t) { t.isIndexTable(); }; diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index 605ab891bdacc..486783c39d18a 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -98,7 +98,7 @@ void canNotJoin(std::vector> const& tables, std::s template void IncompatibleRanges(std::vector const& tables, std::span labels) { - auto loc = std::ranges::adjacent_find(tables, [](auto const& l, auto const& r){ return l.range != r.range; }); + auto loc = std::ranges::adjacent_find(tables, [](auto const& l, auto const& r) { return l.range != r.range; }); if (loc != std::ranges::cend(tables)) { auto pos = std::distance(tables.begin(), loc); auto next = loc + 1; @@ -140,7 +140,7 @@ ArrowTableRef joinTablesImpl(std::ranges::input_range auto tables, std::span o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables) { std::vector refs; - std::ranges::transform(tables, std::back_inserter(refs),[](auto const& table){ return ArrowTableRef{table}; }); + std::ranges::transform(tables, std::back_inserter(refs), [](auto const& table) { return ArrowTableRef{table}; }); return joinTablesImpl(refs, std::span()); } @@ -207,7 +207,6 @@ o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector(resultFields), columns)}; } - arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label) { // Take the exact-match common case first (string_view comparison checks length From 649f89bbdc2299d4b19d898e4bd9b8dca15c804b Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Tue, 21 Jul 2026 20:40:05 +0200 Subject: [PATCH 25/25] Restore type-generation consteval function Co-authored-by: Anton Alkin --- Framework/Core/include/Framework/ASoA.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 4af438802304a..9e9b1d40f4008 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -1639,6 +1639,10 @@ auto select(T const& t, framework::expressions::Filter const& f) arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label); +template +consteval auto base_iter(framework::pack&&) -> TableIterator +{ +} template requires((sizeof...(Ts) > 0) && (soa::is_column && ...)) consteval auto getColumns() @@ -1737,7 +1741,7 @@ class Table using external_index_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); using internal_index_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); template - using base_iterator = decltype([](framework::pack&&) -> TableIterator {}(columns_t{})); + using base_iterator = decltype(base_iter(columns_t{})); template struct TableIteratorBase : base_iterator {