From 6da2fc01b776e7b4fe59dc09c68e34757063a2f7 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:43:27 +0200 Subject: [PATCH] DPL: allow for configurable CCDB paths --- .../CCDBSupport/src/AnalysisCCDBHelpers.cxx | 46 +++++++++++++++++-- .../Core/include/Framework/Configurable.h | 20 ++++++++ Framework/Core/src/ArrowSupport.cxx | 30 ++++++++++++ .../TestWorkflows/src/o2TestAnalysisCCDB.cxx | 5 +- 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 3892f200645f6..b4bd5e94b4bf6 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -21,7 +21,8 @@ #include "Framework/Signpost.h" #include "Framework/DanglingEdgesContext.h" #include "Framework/ConfigContext.h" -#include "Framework/ConfigContext.h" +#include "Framework/RunningWorkflowInfo.h" +#include "Framework/ConfigParamsHelper.h" #include #include #include @@ -72,6 +73,45 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) { return adaptStateful([](ConfigParamRegistry const& options, DeviceSpec const& spec, InitContext& ic) { auto& dec = ic.services().get(); + auto& rwi = ic.services().get(); + + // Build a map from ccdb: option name -> URL, consulting the RunningWorkflow + // so that a Configurable{"ccdb:fXxx", ...} declared in any + // analysis task is honoured. The CCDB device's own option (registered via + // ArrowSupport topology adjustment) takes precedence as the runtime override, + // falling back to the task device's declared value, then to the compile-time + // default stored in the InputSpec metadata. + std::unordered_map ccdbUrls; + for (auto& input : dec.analysisCCDBInputs) { + for (auto& m : input.metadata) { + if (!m.name.starts_with("ccdb:") || ccdbUrls.count(m.name)) { + continue; + } + // Start with the compile-time default from the column declaration macro + std::string url = m.defaultValue.asString(); + // Prefer the value from whichever task device has this option registered + // (i.e. declared a Configurable{"ccdb:fXxx", ...}) + for (auto& device : rwi.devices) { + if (device.name == spec.name) { + continue; // skip the CCDB device itself + } + for (auto& opt : device.options) { + if (opt.name == m.name) { + url = opt.defaultValue.asString(); + break; + } + } + } + // Allow runtime override via the CCDB device's own registered option + // (set with --internal-dpl-aod-ccdb.ccdb:fXxx or JSON config) + if (ConfigParamsHelper::hasOption(spec.options, m.name)) { + url = options.get(m.name.c_str()); + } + LOGP(info, "CCDB path resolved for {}: {}", m.name, url); + ccdbUrls.emplace(m.name, std::move(url)); + } + } + std::vector> schemas; auto schemaMetadata = std::make_shared(); @@ -92,9 +132,9 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) if (!m.name.starts_with("ccdb:")) { continue; } - // Create the schema of the output + // Create the schema of the output using the resolved URL auto metadata = std::make_shared(); - metadata->Append("url", m.defaultValue.asString()); + metadata->Append("url", ccdbUrls.at(m.name)); auto columnName = m.name.substr(strlen("ccdb:")); fields.emplace_back(std::make_shared(columnName, arrow::binary_view(), false, metadata)); } diff --git a/Framework/Core/include/Framework/Configurable.h b/Framework/Core/include/Framework/Configurable.h index 0931884da1ff7..44b89a5e0406f 100644 --- a/Framework/Core/include/Framework/Configurable.h +++ b/Framework/Core/include/Framework/Configurable.h @@ -83,6 +83,26 @@ struct Configurable : IP { template using MutableConfigurable = Configurable>; +/// Convenience wrapper for overriding the CCDB path of a CCDB column declared +/// with DECLARE_SOA_CCDB_COLUMN / DECLARE_SOA_CCDB_COLUMN_FULL. +/// +/// The option name, default value, and help string are all derived automatically +/// from the column type: name = "ccdb:" + Column::mLabel, default = Column::query. +/// +/// Example: +/// struct MyTask { +/// ConfigurableCCDBPath lhcPhasePath; +/// }; +template +struct ConfigurableCCDBPath : Configurable { + ConfigurableCCDBPath() + : Configurable{std::string{"ccdb:"} + Column::mLabel, + std::string{Column::query}, + std::string{"CCDB path for "} + Column::mLabel + " (default: " + Column::query + ")"} + { + } +}; + template concept is_configurable = requires(T t) { requires std::same_as; diff --git a/Framework/Core/src/ArrowSupport.cxx b/Framework/Core/src/ArrowSupport.cxx index 780c836437c2b..ceff978ef3110 100644 --- a/Framework/Core/src/ArrowSupport.cxx +++ b/Framework/Core/src/ArrowSupport.cxx @@ -34,6 +34,7 @@ #include "Framework/ServiceRegistryHelpers.h" #include "Framework/Signpost.h" #include "Framework/DefaultsHelpers.h" +#include "Framework/ConfigParamsHelper.h" #include "CommonMessageBackendsHelpers.h" #include @@ -637,6 +638,35 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() analysisCCDB->outputs.clear(); analysisCCDB->inputs.clear(); AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedDYNs, *analysisCCDB); + // Register each ccdb: column path as an actual device option on the CCDB + // device so it can be read from ConfigParamRegistry at runtime. + // If any analysis task declared a Configurable with the same + // "ccdb:fXxx" name, prefer its default over the compile-time ::query value. + // First encountered wins (addOptionIfMissing semantics); log a warning if + // two tasks declare conflicting defaults for the same path. + for (auto& input : dec.analysisCCDBInputs) { + for (auto& m : input.metadata) { + if (!m.name.starts_with("ccdb:")) { + continue; + } + ConfigParamSpec effective = m; // start with compile-time default + for (auto& d : workflow | views::exclude_by_name(analysisCCDB->name)) { + for (auto& opt : d.options) { + if (opt.name == m.name) { + if (opt.defaultValue.asString() != effective.defaultValue.asString()) { + LOGP(warn, "Task '{}' declares Configurable '{}' = '{}' which conflicts " + "with an earlier value '{}'; earlier value will be used.", + d.name, opt.name, opt.defaultValue.asString(), + effective.defaultValue.asString()); + } + effective = opt; // task Configurable wins (first one found) + break; + } + } + } + ConfigParamsHelper::addOptionIfMissing(analysisCCDB->options, effective); + } + } // load real AlgorithmSpec before deployment analysisCCDB->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "AnalysisCCDBFetcherPlugin", ctx); } diff --git a/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx b/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx index f9684762539f7..5dffa0af2d651 100644 --- a/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx +++ b/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx @@ -51,8 +51,11 @@ struct DummyTimestampsTable { }; struct SimpleCCDBConsumer { + ConfigurableCCDBPath lhcPhasePath; + void process(o2::aod::TOFCalibrationObjects const& ccdbObjectsForAllTimestamps) { + LOGP(info, "LHCphase CCDB path configurable value: {}", lhcPhasePath.value); LOGP(info, "Looking at all the LHCphases associated to the timestamps"); for (auto& object : ccdbObjectsForAllTimestamps) { std::cout << object.lhcPhase().getStartValidity() << " " << object.lhcPhase().getEndValidity() << std::endl; @@ -64,6 +67,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) { return WorkflowSpec{ adaptAnalysisTask(cfgc), - adaptAnalysisTask(cfgc, TaskName{"simple-ccdb-cunsumer"}), + adaptAnalysisTask(cfgc, TaskName{"simple-ccdb-consumer"}), }; }