From 9b4b3d616e8995abe05917e1452035c171ec6c92 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:50:10 +0200 Subject: [PATCH] DPL CCDB Analysis: add ability to specify run dependent queries Certain objects need to be queried via their run, not the timestamp. This allows using the uniform column as key for the run based query. --- .../CCDBSupport/src/AnalysisCCDBHelpers.cxx | 23 +++- Framework/Core/include/Framework/ASoA.h | 108 ++++++++++-------- .../Core/include/Framework/AnalysisHelpers.h | 6 + 3 files changed, 83 insertions(+), 54 deletions(-) diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 935262e7c0508..8812d61e7d369 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -42,8 +42,6 @@ O2_DECLARE_DYNAMIC_LOG(ccdb); - - namespace o2::framework { // Fill valid routes. Notice that for analysis the timestamps are associated to @@ -84,8 +82,13 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) // device's options. Here we just read the final value — honouring any further // runtime override supplied via CLI or JSON config. std::unordered_map ccdbUrls; + std::unordered_map runDependent; for (auto& input : dec.analysisCCDBInputs) { for (auto& m : input.metadata) { + if (m.name.starts_with("ccdb-run-dependent:")) { + runDependent.emplace(m.name, m.defaultValue.asString()); + continue; + } if (!m.name.starts_with("ccdb:") || ccdbUrls.count(m.name)) { continue; } @@ -120,6 +123,8 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) auto fieldMetadata = std::make_shared(); auto it = ccdbUrls.find(m.name); fieldMetadata->Append("url", it != ccdbUrls.end() ? it->second : m.defaultValue.asString()); + auto runDep = runDependent.find("ccdb-run-dependent:" + m.name.substr(strlen("ccdb:"))); + fieldMetadata->Append("runDependent", runDep != runDependent.end() ? runDep->second : "0"); auto columnName = m.name.substr(strlen("ccdb:")); fields.emplace_back(std::make_shared(columnName, soa::asArrowDataType(), false, fieldMetadata)); } @@ -280,12 +285,22 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) for (auto& field : schema->fields()) { auto const& url = pathTables[i][fi++].resolve(uniformityKey, field->name()); // Time to actually populate the blob + // A run-dependent object is queried with the run number rather than by + // timestamp alone. The run comes from the uniformity value, so the column's + // table has to be uniform in the run number for this to mean anything. + int const fieldRunDependent = field->metadata()->Contains("runDependent") + ? std::stoi(*field->metadata()->Get("runDependent")) + : 0; + if (fieldRunDependent != 0 && uniformityColumnName != "fRunNumber") { + LOGP(fatal, R"(Column "{}" of {} is declared run-dependent, but its table is uniform in "{}" rather than fRunNumber, so no run number is available to query with. Declare the table with DECLARE_SOA_UNIFORM_TABLE(..., aod::BCs, o2::aod::bc::RunNumber, ...).)", + field->name(), outBinding, uniformityColumnName); + } ops.push_back({ .spec = spec, .url = url, .timestamp = timestamp, - .runNumber = 1, - .runDependent = 0, + .runNumber = fieldRunDependent != 0 ? static_cast(uniformityKey) : 1, + .runDependent = fieldRunDependent, .queryRate = 0, }); } diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 2d880648c42f4..4f1ef6bdeda57 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -2370,10 +2370,15 @@ consteval static std::string_view namespace_prefix() }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } -#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_, ...) \ +#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_, _RunDependent_, ...) \ struct _Name_ : o2::soa::Column { \ static constexpr const char* mLabel = _Label_; \ static constexpr const char* query = _CCDBQuery_; \ + /* How the object is keyed in CCDB: 0 queries by timestamp alone, 1 additionally sends */ \ + /* the run number as "runNumber" metadata (o2::ccdb run-dependent objects), 2 uses the */ \ + /* run number in place of the timestamp. A non-zero value needs the column's table to */ \ + /* be uniform in the run number, since that is where the run comes from. */ \ + static constexpr int run_dependent = _RunDependent_; \ static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ static constexpr bool needs_ptr_rec = true; \ /* Post-deserialisation fixup for objects which are not usable straight out of the ROOT */ \ @@ -2438,8 +2443,8 @@ consteval static std::string_view namespace_prefix() for DECLARE_SOA_CCDB_COLUMN_FULL when it needs finalising first — a FlatObject whose pointers must be rectified, say. Its finaliser is the trailing argument, so commas in a lambda body are absorbed by __VA_ARGS__. */ -#define DECLARE_SOA_CCDB_COLUMN(_Name_, _Getter_, _ConcreteType_, _CCDBQuery_) \ - DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, "f" #_Name_, _Getter_, _ConcreteType_, _CCDBQuery_, \ +#define DECLARE_SOA_CCDB_COLUMN(_Name_, _Getter_, _ConcreteType_, _CCDBQuery_) \ + DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, "f" #_Name_, _Getter_, _ConcreteType_, _CCDBQuery_, 0, \ [](_ConcreteType_* ccdbObject) { return ccdbObject; }) #define DECLARE_SOA_COLUMN(_Name_, _Getter_, _Type_) \ @@ -3336,53 +3341,56 @@ consteval auto getIndexTargets() // The columns of this table have to be CCDB_COLUMNS so that for each timestamp, we get a row // which points to the specified CCDB objectes described by those columns. #define DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, _Label_, _TimestampSource_, _TimestampColumn_, _UniformitySource_, _UniformityColumn_, _Version_, _Desc_, ...) \ - O2HASH(_Desc_ "/" #_Version_); \ - template \ - using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ - using _Name_##Timestamp = _Name_##TimestampFrom>; \ - struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ - template > \ - using base_table_t = _TimestampSource_##From; \ - template > \ - using extension_table_t = _Name_##TimestampFrom; \ - static constexpr const auto ccdb_urls = [](framework::pack) { \ - return std::array{Cs::query...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - static constexpr const auto ccdb_bindings = [](framework::pack) { \ - return std::array{Cs::mLabel...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - /* The uniformity column may live in a table other than the timestamp source (the run */ \ - /* number is on aod::BCs, the timestamp on aod::Timestamps). Both are handed to the */ \ - /* fetcher, which reads them positionally — sound because the two are row-aligned. */ \ - /* Row alignment cannot be checked here: ASoA encodes no type-level relation between */ \ - /* two tables that happen to have equal row counts (aod::BCs and aod::Timestamps have */ \ - /* disjoint originals). The CCDB fetcher verifies the lengths match before reading. */ \ - static constexpr auto N = o2::soa::mergeOriginals<_TimestampSource_, _UniformitySource_>().size(); \ - template > \ - static consteval auto generateSources() \ - { \ - return o2::soa::mergeOriginals<_TimestampSource_##From, _UniformitySource_##From>(); \ - } \ - static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ - /* Rows sharing a uniformity value resolve to the same CCDB object, so the fetcher */ \ - /* need only query once per distinct value. Defaults to the timestamp column, i.e. */ \ - /* every distinct timestamp may yield a different object — the pre-existing behaviour.*/ \ - static constexpr auto uniformity_column_label = _UniformityColumn_::mLabel; \ - /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ - }; \ - template <> \ - struct MetadataTrait> { \ - static constexpr void isMetadataTrait() {}; \ - using metadata = _Name_##TimestampMetadata; \ - }; \ - template \ - using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ - using _Name_ = _Name_##From \ + using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ + using _Name_##Timestamp = _Name_##TimestampFrom>; \ + struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ + template > \ + using base_table_t = _TimestampSource_##From; \ + template > \ + using extension_table_t = _Name_##TimestampFrom; \ + static constexpr const auto ccdb_urls = [](framework::pack) { \ + return std::array{Cs::query...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr const auto ccdb_bindings = [](framework::pack) { \ + return std::array{Cs::mLabel...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr const auto ccdb_run_dependent = [](framework::pack) { \ + return std::array{Cs::run_dependent...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + /* The uniformity column may live in a table other than the timestamp source (the run */ \ + /* number is on aod::BCs, the timestamp on aod::Timestamps). Both are handed to the */ \ + /* fetcher, which reads them positionally — sound because the two are row-aligned. */ \ + /* Row alignment cannot be checked here: ASoA encodes no type-level relation between */ \ + /* two tables that happen to have equal row counts (aod::BCs and aod::Timestamps have */ \ + /* disjoint originals). The CCDB fetcher verifies the lengths match before reading. */ \ + static constexpr auto N = o2::soa::mergeOriginals<_TimestampSource_, _UniformitySource_>().size(); \ + template > \ + static consteval auto generateSources() \ + { \ + return o2::soa::mergeOriginals<_TimestampSource_##From, _UniformitySource_##From>(); \ + } \ + static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ + /* Rows sharing a uniformity value resolve to the same CCDB object, so the fetcher */ \ + /* need only query once per distinct value. Defaults to the timestamp column, i.e. */ \ + /* every distinct timestamp may yield a different object — the pre-existing behaviour.*/ \ + static constexpr auto uniformity_column_label = _UniformityColumn_::mLabel; \ + /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ + }; \ + template <> \ + struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ + using metadata = _Name_##TimestampMetadata; \ + }; \ + template \ + using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ + using _Name_ = _Name_##From>; /* Uniformity defaults to the timestamp column of the timestamp source: each distinct diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index 6e046e4de0311..6071e8291e387 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -266,6 +266,12 @@ inline constexpr auto getCCDBUrls() framework::VariantType::String, T::ccdb_urls[i], {"\"\""}}); + // How this object is keyed in CCDB; the fetcher turns a non-zero value into a + // run-number-qualified query rather than a plain timestamp one. + result.push_back({std::string{"ccdb-run-dependent:"} + std::string{T::ccdb_bindings[i]}, + framework::VariantType::Int, + T::ccdb_run_dependent[i], + {"\"\""}}); } return result; }