diff options
| author | Copilot <[email protected]> | 2026-01-14 11:52:07 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-14 11:52:07 +0300 |
| commit | 2b1de06f31ceb240facce913ec47d504032c409a (patch) | |
| tree | 31cef97c76df9967ef78c5be43b5dc5accc6cf45 | |
| parent | 1b5cf02b9804fb36dec35cf90b3ac29c82bd04c9 (diff) | |
Unify time interval format across YDB CLI commands (#31593)
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: asmyasnikov <[email protected]>
Co-authored-by: pnv1 <[email protected]>
Co-authored-by: Aleksey Myasnikov <[email protected]>
Co-authored-by: Copilot <[email protected]>
| -rw-r--r-- | ydb/apps/ydb/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_benchmark.cpp | 9 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_command.cpp | 8 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_command.h | 9 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_common.h | 10 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_service_import.cpp | 6 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp | 7 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_service_topic.cpp | 24 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_workload.cpp | 23 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/commands/ydb_workload.h | 6 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/common/describe.cpp | 40 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/common/duration.cpp | 70 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/common/duration.h | 41 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/common/duration_ut.cpp | 135 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/common/ut/ya.make | 1 | ||||
| -rw-r--r-- | ydb/public/lib/ydb_cli/common/ya.make | 1 |
16 files changed, 296 insertions, 95 deletions
diff --git a/ydb/apps/ydb/CHANGELOG.md b/ydb/apps/ydb/CHANGELOG.md index ca2b2a814e5..d6dc8d185ea 100644 --- a/ydb/apps/ydb/CHANGELOG.md +++ b/ydb/apps/ydb/CHANGELOG.md @@ -1,6 +1,7 @@ * Added the `Created by`, `Create time` and `End time` fields to the "build index" and the "execute script" operations in the `ydb operation` subcommands. * Added the `--include-index-data` option to the `ydb export s3` command, enabling index data export. * Added the `--index-population-mode` option to the `ydb import s3` command, allowing selection of the index population mode (e.g. build or import). +* Added unified time interval format support across CLI commands. Options accepting time durations now support explicit time units (e.g., `5s`, `2m`, `1h`) while maintaining backward compatibility with plain numbers interpreted using their original default units. ## 2.28.0 ## diff --git a/ydb/public/lib/ydb_cli/commands/ydb_benchmark.cpp b/ydb/public/lib/ydb_cli/commands/ydb_benchmark.cpp index f7090dec0de..2648decd1de 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_benchmark.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_benchmark.cpp @@ -3,6 +3,7 @@ #include <ydb/public/lib/ydb_cli/common/format.h> #include <ydb/public/lib/ydb_cli/common/plan2svg.h> #include <ydb/public/lib/ydb_cli/common/pretty_table.h> +#include <ydb/public/lib/ydb_cli/common/duration.h> #include <library/cpp/json/json_writer.h> #include <util/stream/null.h> #include <util/string/printf.h> @@ -90,11 +91,11 @@ void TWorkloadCommandBenchmark::Config(TConfig& config) { config.Opts->AddLongOption('v', "verbose", "Verbose output").NoArgument().StoreValue(&VerboseLevel, 1); - config.Opts->AddLongOption("global-timeout", "Global timeout for all requests") - .StoreResult(&GlobalTimeout); + config.Opts->AddLongOption("global-timeout", "Global timeout for all requests. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds.") + .StoreMappedResult(&GlobalTimeout, &ParseDurationMilliseconds); - config.Opts->AddLongOption("request-timeout", "Timeout for each iteration of each request") - .StoreResult(&RequestTimeout); + config.Opts->AddLongOption("request-timeout", "Timeout for each iteration of each request. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds.") + .StoreMappedResult(&RequestTimeout, &ParseDurationMilliseconds); config.Opts->AddLongOption('t', "threads", "Number of parallel threads in workload") .StoreResult(&Threads).DefaultValue(Threads).RequiredArgument("COUNT"); diff --git a/ydb/public/lib/ydb_cli/commands/ydb_command.cpp b/ydb/public/lib/ydb_cli/commands/ydb_command.cpp index b8972e3ef87..b7494a79d2d 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_command.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_command.cpp @@ -41,8 +41,8 @@ void TYdbSimpleCommand::Config(TConfig& config) { TClientCommand::Config(config); TClientCommandOptions& opts = *config.Opts; - opts.AddLongOption("timeout", "Client timeout. There is no point waiting for the result after this long.") - .RequiredArgument("ms").StoreResult(&ClientTimeout); + opts.AddLongOption("timeout", "Client timeout. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds.") + .RequiredArgument("DURATION").StoreResult(&ClientTimeout); } TYdbOperationCommand::TYdbOperationCommand(const TString& name, const std::initializer_list<TString>& aliases, const TString& description) @@ -53,9 +53,9 @@ void TYdbOperationCommand::Config(TConfig& config) { TYdbCommand::Config(config); TClientCommandOptions& opts = *config.Opts; - opts.AddLongOption("timeout", "Operation timeout. Operation should be executed on server within this timeout. " + opts.AddLongOption("timeout", "Operation timeout. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds. " "There could also be a delay up to 200ms to receive timeout error from server.") - .RequiredArgument("ms").StoreResult(&OperationTimeout); + .RequiredArgument("DURATION").StoreResult(&OperationTimeout); } } // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/ydb_command.h b/ydb/public/lib/ydb_cli/commands/ydb_command.h index 60e62b906d7..1157b77bfc0 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_command.h +++ b/ydb/public/lib/ydb_cli/commands/ydb_command.h @@ -1,6 +1,7 @@ #pragma once #include <ydb/public/lib/ydb_cli/common/command.h> +#include <ydb/public/lib/ydb_cli/commands/ydb_common.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/driver/driver.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/draft/ydb_scripting.h> @@ -49,7 +50,7 @@ protected: template<typename TSettingsType> TSettingsType&& FillSettings(TSettingsType&& settings) { if (ClientTimeout) { - settings.ClientTimeout(TDuration::MilliSeconds(FromString<ui64>(ClientTimeout))); + settings.ClientTimeout(ParseDurationMilliseconds(ClientTimeout)); } return std::forward<TSettingsType>(settings); } @@ -70,9 +71,9 @@ protected: template<typename TSettingsType> TSettingsType&& FillSettings(TSettingsType&& settings) { if (OperationTimeout) { - ui64 operationTimeout = FromString<ui64>(OperationTimeout); - settings.OperationTimeout(TDuration::MilliSeconds(operationTimeout)); - settings.ClientTimeout(TDuration::MilliSeconds(operationTimeout + 200)); + TDuration timeout = ParseDurationMilliseconds(OperationTimeout); + settings.OperationTimeout(timeout); + settings.ClientTimeout(timeout + TDuration::MilliSeconds(200)); } return std::forward<TSettingsType>(settings); } diff --git a/ydb/public/lib/ydb_cli/commands/ydb_common.h b/ydb/public/lib/ydb_cli/commands/ydb_common.h index 2af6a3d8a32..ec6c6377c1c 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_common.h +++ b/ydb/public/lib/ydb_cli/commands/ydb_common.h @@ -3,7 +3,7 @@ #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/driver/driver.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/types/operation/operation.h> #include <ydb/public/lib/ydb_cli/common/command.h> -#include <util/string/strip.h> +#include <ydb/public/lib/ydb_cli/common/duration.h> namespace NYdb::NConsoleClient { @@ -14,12 +14,4 @@ inline void ThrowOnError(const NYdb::TOperation& operation) { NStatusHelpers::ThrowOnError(operation.Status()); } -inline TDuration ParseDuration(TStringBuf str) { - StripInPlace(str); - if (!str.empty() && !IsAsciiAlpha(str.back())) { - throw TMisuseException() << "Duration must end with a unit name (ex. 'h' for hours, 's' for seconds)"; - } - return TDuration::Parse(str); -} - } // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/ydb_service_import.cpp b/ydb/public/lib/ydb_cli/commands/ydb_service_import.cpp index 9060f30dfcb..aed83dfca66 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_service_import.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_service_import.cpp @@ -428,9 +428,9 @@ void TCommandImportFileBase::Config(TConfig& config) { config.Opts->GetOpts().SetTrailingArgTitle("<input files...>", "One or more file paths to import from. If a directory is provided, all files in the directory with supported extension will be imported."); - config.Opts->AddLongOption("timeout", "Operation timeout. Operation should be executed on server within this timeout. " - "There could also be a delay up to 200ms to receive timeout error from server") - .RequiredArgument("VAL").StoreResult(&OperationTimeout).DefaultValue(TDuration::Seconds(5 * 60)); + config.Opts->AddLongOption("timeout", "Operation timeout. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds. " + "There could also be a delay up to 200ms to receive timeout error from server.") + .RequiredArgument("DURATION").StoreMappedResult(&OperationTimeout, &ParseDurationMilliseconds).DefaultValue(TDuration::Seconds(5 * 60)); config.Opts->AddLongOption('p', "path", "Database path to table") .Required().RequiredArgument("STRING").StoreResult(&Path); diff --git a/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp b/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp index 6c987df8f07..5c18b458de0 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_service_table.cpp @@ -1,4 +1,5 @@ #include "ydb_service_table.h" +#include "ydb_common.h" #include <ydb/public/lib/json_value/ydb_json_value.h> #include <ydb/public/lib/ydb_cli/common/colors.h> @@ -1492,10 +1493,8 @@ void TCommandTtlSet::Config(TConfig& config) { ColumnUnit = value; }); - config.Opts->AddLongOption("run-interval", "[Advanced] How often to run cleanup operation on the same partition.") - .RequiredArgument("SECONDS").GetOpt().Handler1T<TDuration::TValue>([this](const TDuration::TValue& arg) { - RunInterval = TDuration::Seconds(arg); - }); + config.Opts->AddLongOption("run-interval", "[Advanced] How often to run cleanup operation on the same partition. Supports time units (e.g., '5s', '1m'). Plain number interpreted as seconds.") + .RequiredArgument("DURATION").StoreMappedResult(&RunInterval, &ParseDurationSeconds); config.SetFreeArgsNum(1); SetFreeArgTitle(0, "<table path>", "Path to a table"); diff --git a/ydb/public/lib/ydb_cli/commands/ydb_service_topic.cpp b/ydb/public/lib/ydb_cli/commands/ydb_service_topic.cpp index d6fd757a170..851c66849a2 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_service_topic.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_service_topic.cpp @@ -167,20 +167,6 @@ namespace NYdb::NConsoleClient { return exists->second; } - - TDuration ParseDurationHours(const TStringBuf str) { - double hours = 0; - if (!TryFromString(str, hours)) { - throw TMisuseException() << "Invalid hours duration '" << str << "'"; - } - if (hours < 0) { - throw TMisuseException() << "Duration must be non-negative"; - } - if (!std::isfinite(hours)) { - throw TMisuseException() << "Duration must be finite"; - } - return TDuration::Seconds(hours * 3600); // using floating-point ctor with saturation - } } void TCommandWithSupportedCodecs::AddAllowedCodecs(TClientCommand::TConfig& config, const TVector<NYdb::NTopic::ECodec>& supportedCodecs) { @@ -363,7 +349,7 @@ namespace NYdb::NConsoleClient { .StoreResult(&MinActivePartitions_) .DefaultValue(1); config.Opts->AddLongOption("retention-period-hours", TStringBuilder() - << "Duration in hours for which data in topic is stored " + << "Duration in hours for which data in topic is stored. Supports time units (e.g., '72h', '1440m'). Plain number interpreted as hours " << "(default: " << NColorizer::StdOut().CyanColor() << RetentionPeriod_.Hours() << NColorizer::StdOut().OldColor() << ")") .Hidden() .Optional() @@ -463,7 +449,7 @@ namespace NYdb::NConsoleClient { config.Opts->AddLongOption("partitions-count", "Initial and minimum number of partitions for topic") .Optional() .StoreResult(&MinActivePartitions_); - config.Opts->AddLongOption("retention-period-hours", "Duration in hours for which data in topic is stored") + config.Opts->AddLongOption("retention-period-hours", "Duration in hours for which data in topic is stored. Supports time units (e.g., '72h', '1440m'). Plain number interpreted as hours.") .Hidden() .Optional() .RequiredArgument("HOURS") @@ -948,7 +934,7 @@ namespace NYdb::NConsoleClient { config.Opts->AddLongOption('f', "file", "File to write data to. In not specified, data is written to the standard output.") .Optional() .StoreResult(&File_); - config.Opts->AddLongOption("idle-timeout", "Max wait duration for the first message. Topic is considered empty if no new messages arrive within this period.") + config.Opts->AddLongOption("idle-timeout", "Max wait duration for the first message. Topic is considered empty if no new messages arrive within this period. Value must include a time unit suffix (e.g., '5s', '1m').") .Optional() .DefaultValue(DefaultIdleTimeout) .StoreResult(&IdleTimeout_); @@ -1176,10 +1162,10 @@ namespace NYdb::NConsoleClient { .Optional() .RequiredArgument("INDEX") .StoreResult(&PartitionId_); - config.Opts->AddLongOption("init-seqno-timeout", "Max wait duration for initial seqno") + config.Opts->AddLongOption("init-seqno-timeout", "Max wait duration for initial seqno. Supports time units (e.g., '5s', '1m'). Plain number interpreted as seconds.") .Optional() .Hidden() - .Handler([this](const TString& arg) { MessagesWaitTimeout_ = TDuration::Seconds(FromString<ui8>(arg)); }); + .StoreMappedResult(&MessagesWaitTimeout_, &ParseDurationSeconds); AddTransform(config); } diff --git a/ydb/public/lib/ydb_cli/commands/ydb_workload.cpp b/ydb/public/lib/ydb_cli/commands/ydb_workload.cpp index 3afc524eb36..70b551849a4 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_workload.cpp +++ b/ydb/public/lib/ydb_cli/commands/ydb_workload.cpp @@ -84,9 +84,6 @@ TWorkloadCommand::TWorkloadCommand(const TString& name, const std::initializer_l , TotalSec(0) , Threads(0) , Rate(0) - , ClientTimeoutMs(0) - , OperationTimeoutMs(0) - , CancelAfterTimeoutMs(0) , WindowSec(0) , Quiet(false) , Verbose(false) @@ -123,12 +120,12 @@ void TWorkloadCommand::Config(TConfig& config) { .StoreTrue(&Quiet); config.Opts->AddLongOption("print-timestamp", "Print timestamp each second with statistics.") .StoreTrue(&PrintTimestamp); - config.Opts->AddLongOption("client-timeout", "Client timeout in ms.") - .DefaultValue(1000).StoreResult(&ClientTimeoutMs); - config.Opts->AddLongOption("operation-timeout", "Operation timeout in ms.") - .DefaultValue(800).StoreResult(&OperationTimeoutMs); - config.Opts->AddLongOption("cancel-after", "Cancel after timeout in ms.") - .DefaultValue(800).StoreResult(&CancelAfterTimeoutMs); + config.Opts->AddLongOption("client-timeout", "Client timeout. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds.") + .DefaultValue("1000").StoreResult(&ClientTimeoutStr); + config.Opts->AddLongOption("operation-timeout", "Operation timeout. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds.") + .DefaultValue("800").StoreResult(&OperationTimeoutStr); + config.Opts->AddLongOption("cancel-after", "Cancel after timeout. Supports time units (e.g., '5s', '1m'). Plain number interpreted as milliseconds.") + .DefaultValue("800").StoreResult(&CancelAfterTimeoutStr); config.Opts->AddLongOption("window", "Window duration in seconds.") .DefaultValue(1).StoreResult(&WindowSec); config.Opts->AddLongOption("executer", "Query executer type (data or generic).") @@ -170,11 +167,11 @@ void TWorkloadCommand::PrepareForRun(TConfig& config) { void TWorkloadCommand::WorkerFn(int taskId, NYdbWorkload::IWorkloadQueryGenerator& workloadGen, const int type) { const auto dataQuerySettings = NYdb::NTable::TExecDataQuerySettings() .KeepInQueryCache(true) - .OperationTimeout(TDuration::MilliSeconds(OperationTimeoutMs)) - .ClientTimeout(TDuration::MilliSeconds(ClientTimeoutMs)) - .CancelAfter(TDuration::MilliSeconds(CancelAfterTimeoutMs)); + .OperationTimeout(ParseDurationMilliseconds(OperationTimeoutStr)) + .ClientTimeout(ParseDurationMilliseconds(ClientTimeoutStr)) + .CancelAfter(ParseDurationMilliseconds(CancelAfterTimeoutStr)); const auto genericQuerySettings = NYdb::NQuery::TExecuteQuerySettings() - .ClientTimeout(TDuration::MilliSeconds(ClientTimeoutMs)); + .ClientTimeout(ParseDurationMilliseconds(ClientTimeoutStr)); int retryCount = -1; NYdbWorkload::TQueryInfo queryInfo; diff --git a/ydb/public/lib/ydb_cli/commands/ydb_workload.h b/ydb/public/lib/ydb_cli/commands/ydb_workload.h index 271fce87676..cd51f7c4e33 100644 --- a/ydb/public/lib/ydb_cli/commands/ydb_workload.h +++ b/ydb/public/lib/ydb_cli/commands/ydb_workload.h @@ -44,9 +44,9 @@ protected: size_t TotalSec; size_t Threads; ui64 Rate; - unsigned int ClientTimeoutMs; - unsigned int OperationTimeoutMs; - unsigned int CancelAfterTimeoutMs; + TString ClientTimeoutStr; + TString OperationTimeoutStr; + TString CancelAfterTimeoutStr; unsigned int WindowSec; bool Quiet; bool Verbose; diff --git a/ydb/public/lib/ydb_cli/common/describe.cpp b/ydb/public/lib/ydb_cli/common/describe.cpp index bbe06bc6282..8ce8ffcf866 100644 --- a/ydb/public/lib/ydb_cli/common/describe.cpp +++ b/ydb/public/lib/ydb_cli/common/describe.cpp @@ -21,37 +21,13 @@ namespace NYdb::NConsoleClient { namespace { -struct TPrettyDurationFormatParameters { - double EntitiesPerSecond; - TStringBuf EntityName; - TStringBuf ZeroString; - TStringBuf MaxString; - int MaxPrecision; -}; - -constexpr TPrettyDurationFormatParameters PRETTY_HOURS_DEFAULT{ - .EntitiesPerSecond = 3600, - .EntityName = "hours", - .ZeroString = "0 hours", - .MaxString = "+infinity", - .MaxPrecision = 3, -}; - -constexpr TPrettyDurationFormatParameters PRETTY_HOURS_NON_ZERO{ - .EntitiesPerSecond = 3600, - .EntityName = "hours", - .ZeroString = "", - .MaxString = "+infinity", - .MaxPrecision = 3, -}; - -TString PrettyDurationString(TDuration duration, const TPrettyDurationFormatParameters& paramerters) { +// Helper function to format duration in human-readable format +// Returns empty string for zero duration, otherwise returns duration in format like "2h", "5m 30s", etc. +TString HumanReadableDurationOrEmpty(TDuration duration) { if (duration == TDuration::Zero()) { - return ToString(paramerters.ZeroString); - } else if (duration == TDuration::Max()) { - return ToString(paramerters.MaxString); + return ""; } - return TStringBuilder() << Prec(duration.MillisecondsFloat() / (paramerters.EntitiesPerSecond * 1000.0), PREC_POINT_DIGITS_STRIP_ZEROES, paramerters.MaxPrecision) << " " << paramerters.EntityName; + return ToString(HumanReadable(duration)); } TString FormatCodecs(const std::vector<NYdb::NTopic::ECodec>& codecs) { @@ -69,7 +45,7 @@ void PrintTopicConsumers(const std::vector<NYdb::NTopic::TConsumer>& consumers, .Column(1, FormatCodecs(c.GetSupportedCodecs())) .Column(2, c.GetReadFrom().ToRfc822StringLocal()) .Column(3, c.GetImportant() ? "Yes" : "No") - .Column(4, PrettyDurationString(c.GetAvailabilityPeriod(), PRETTY_HOURS_NON_ZERO)); + .Column(4, HumanReadableDurationOrEmpty(c.GetAvailabilityPeriod())); } out << Endl << "Consumers: " << Endl; out << table; @@ -88,7 +64,7 @@ void PrintStatistics(const NTopic::TTopicDescription& topicDescription, IOutputS void PrintMain(const NTopic::TTopicDescription& topicDescription, IOutputStream& out) { out << Endl << "Main:"; - out << Endl << "RetentionPeriod: " << PrettyDurationString(topicDescription.GetRetentionPeriod(), PRETTY_HOURS_DEFAULT); + out << Endl << "RetentionPeriod: " << HumanReadable(topicDescription.GetRetentionPeriod()); if (topicDescription.GetRetentionStorageMb().has_value()) { out << Endl << "StorageRetention: " << *topicDescription.GetRetentionStorageMb() << " MB"; } @@ -171,7 +147,7 @@ int PrintPrettyDescribeConsumerResult(const NYdb::NTopic::TConsumerDescription& const NYdb::NTopic::TConsumer& consumer = description.GetConsumer(); out << "Consumer " << consumer.GetConsumerName() << ": " << Endl; out << "Important: " << (consumer.GetImportant() ? "Yes" : "No") << Endl; - if (const auto availabilityPeriodStr = PrettyDurationString(consumer.GetAvailabilityPeriod(), PRETTY_HOURS_NON_ZERO)) { + if (const auto availabilityPeriodStr = HumanReadableDurationOrEmpty(consumer.GetAvailabilityPeriod())) { out << "Availability period: " << availabilityPeriodStr << Endl; } if (const TInstant& readFrom = consumer.GetReadFrom()) { diff --git a/ydb/public/lib/ydb_cli/common/duration.cpp b/ydb/public/lib/ydb_cli/common/duration.cpp new file mode 100644 index 00000000000..01e85be02d3 --- /dev/null +++ b/ydb/public/lib/ydb_cli/common/duration.cpp @@ -0,0 +1,70 @@ +#include "duration.h" + +namespace NYdb::NConsoleClient { + +TDuration ParseDuration(TStringBuf str) { + StripInPlace(str); + if (str.empty()) { + throw TMisuseException() << "Duration cannot be empty"; + } + if (!IsAsciiAlpha(str.back())) { + throw TMisuseException() << "Duration must end with a unit name (ex. 'h' for hours, 's' for seconds)"; + } + return TDuration::Parse(str); +} + +TDuration ParseDurationWithDefaultUnit(TStringBuf str, TDuration (*defaultUnit)(double)) { + StripInPlace(str); + if (str.empty()) { + throw TMisuseException() << "Duration cannot be empty"; + } + + // Check if the string ends with a unit (alphabetic character) + if (IsAsciiAlpha(str.back())) { + // New format with explicit unit (e.g., "5s", "2m", "1h") + // TDuration::Parse already supports fractional values + return TDuration::Parse(str); + } + + // Old format: plain number - interpret using default unit + double value = 0; + if (!TryFromString(str, value)) { + throw TMisuseException() << "Invalid duration value '" << str << "'"; + } + if (value < 0) { + throw TMisuseException() << "Duration must be non-negative"; + } + if (!std::isfinite(value)) { + throw TMisuseException() << "Duration must be finite"; + } + return defaultUnit(value); +} + +TDuration ParseDurationMicroseconds(TStringBuf str) { + return ParseDurationWithDefaultUnit(str, [](double us) { + return TDuration::MicroSeconds(static_cast<ui64>(std::round(us))); + }); +} + +TDuration ParseDurationMilliseconds(TStringBuf str) { + return ParseDurationWithDefaultUnit(str, [](double ms) { + // Convert milliseconds to microseconds and round to preserve precision + return TDuration::MicroSeconds(static_cast<ui64>(std::round(ms * 1000.0))); + }); +} + +TDuration ParseDurationSeconds(TStringBuf str) { + return ParseDurationWithDefaultUnit(str, [](double sec) { + // Convert seconds to microseconds and round to preserve precision + return TDuration::MicroSeconds(static_cast<ui64>(std::round(sec * 1000000.0))); + }); +} + +TDuration ParseDurationHours(TStringBuf str) { + return ParseDurationWithDefaultUnit(str, [](double hours) { + // Convert hours to microseconds and round to preserve precision + return TDuration::MicroSeconds(static_cast<ui64>(std::round(hours * 3600000000.0))); + }); +} + +} // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/common/duration.h b/ydb/public/lib/ydb_cli/common/duration.h new file mode 100644 index 00000000000..73e44a67eb5 --- /dev/null +++ b/ydb/public/lib/ydb_cli/common/duration.h @@ -0,0 +1,41 @@ +#pragma once + +#include <ydb/public/lib/ydb_cli/common/command.h> +#include <util/datetime/base.h> +#include <util/string/strip.h> + +#include <cmath> + +namespace NYdb::NConsoleClient { + +// Parse duration with a default unit for backward compatibility +// If the input is a plain number, it's interpreted using the defaultUnit +// Otherwise, it's parsed as a duration string (e.g., "5s", "2m", "1h") +// Supports fractional values: 0.5s = 500ms, 0.5m = 30s, 0.5h = 30m +TDuration ParseDurationWithDefaultUnit(TStringBuf str, TDuration (*defaultUnit)(double)); + +// Parse duration in microseconds with support for new format (e.g., "5s", "100ms", "500us") +// If input is a plain number, it's interpreted as microseconds (for backward compatibility) +// Supports fractional values: 0.5ms = 500us +TDuration ParseDurationMicroseconds(TStringBuf str); + +// Parse duration in milliseconds with support for new format (e.g., "5s", "100ms") +// If input is a plain number, it's interpreted as milliseconds (for backward compatibility) +// Supports fractional values: 0.5s = 500ms +TDuration ParseDurationMilliseconds(TStringBuf str); + +// Parse duration in seconds with support for new format (e.g., "5s", "100ms", "2m") +// If input is a plain number, it's interpreted as seconds (for backward compatibility) +// Supports fractional values: 0.5m = 30s +TDuration ParseDurationSeconds(TStringBuf str); + +// Parse duration in hours with support for new format (e.g., "2h", "30m", "1d") +// If input is a plain number, it's interpreted as hours (for backward compatibility) +// Supports fractional values: 0.5h = 30m, 0.5d = 12h +TDuration ParseDurationHours(TStringBuf str); + +// Parse duration requiring explicit unit (e.g., "5s", "2m", "1h") +// Throws exception if no unit is specified +TDuration ParseDuration(TStringBuf str); + +} // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/common/duration_ut.cpp b/ydb/public/lib/ydb_cli/common/duration_ut.cpp new file mode 100644 index 00000000000..051d849aeac --- /dev/null +++ b/ydb/public/lib/ydb_cli/common/duration_ut.cpp @@ -0,0 +1,135 @@ +#include <ydb/public/lib/ydb_cli/common/duration.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NYdb::NConsoleClient; + +Y_UNIT_TEST_SUITE(ParseDurationTests) { + Y_UNIT_TEST(ParseDuration_WithUnit) { + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("5s"), TDuration::Seconds(5)); + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("100ms"), TDuration::MilliSeconds(100)); + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("2m"), TDuration::Minutes(2)); + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("1h"), TDuration::Hours(1)); + } + + Y_UNIT_TEST(ParseDuration_ThrowsOnPlainNumber) { + UNIT_ASSERT_EXCEPTION(ParseDuration("100"), TMisuseException); + } + + Y_UNIT_TEST(ParseDuration_FractionalValues) { + // TDuration::Parse supports fractional values + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("0.5s"), TDuration::MilliSeconds(500)); + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("0.5m"), TDuration::Seconds(30)); + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("1.5h"), TDuration::Minutes(90)); + UNIT_ASSERT_VALUES_EQUAL(ParseDuration("0.5d"), TDuration::Hours(12)); + } + + Y_UNIT_TEST(ParseDurationMilliseconds_WithUnit) { + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("5s"), TDuration::Seconds(5)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("100ms"), TDuration::MilliSeconds(100)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("2m"), TDuration::Minutes(2)); + } + + Y_UNIT_TEST(ParseDurationMilliseconds_PlainNumber) { + // Plain number should be interpreted as milliseconds + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("1000"), TDuration::MilliSeconds(1000)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("500"), TDuration::MilliSeconds(500)); + } + + Y_UNIT_TEST(ParseDurationMilliseconds_FractionalPlainNumber) { + // Fractional plain number in milliseconds (rounds to microseconds) + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("500.5"), TDuration::MicroSeconds(500500)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("100.4"), TDuration::MicroSeconds(100400)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMilliseconds("1.0001"), TDuration::MicroSeconds(1000)); + } + + Y_UNIT_TEST(ParseDurationSeconds_WithUnit) { + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("5s"), TDuration::Seconds(5)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("100ms"), TDuration::MilliSeconds(100)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("2m"), TDuration::Minutes(2)); + } + + Y_UNIT_TEST(ParseDurationSeconds_PlainNumber) { + // Plain number should be interpreted as seconds + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("60"), TDuration::Seconds(60)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("30"), TDuration::Seconds(30)); + } + + Y_UNIT_TEST(ParseDurationSeconds_FractionalPlainNumber) { + // Fractional plain number in seconds (rounds to microseconds) + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("30.5"), TDuration::MicroSeconds(30500000)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("60.400001"), TDuration::MicroSeconds(60400001)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationSeconds("1.000001"), TDuration::MicroSeconds(1000001)); + } + + Y_UNIT_TEST(ParseDurationHours_WithUnit) { + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("2h"), TDuration::Hours(2)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("30m"), TDuration::Minutes(30)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("1d"), TDuration::Hours(24)); + } + + Y_UNIT_TEST(ParseDurationHours_PlainNumber) { + // Plain number should be interpreted as hours + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("2"), TDuration::Hours(2)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("24"), TDuration::Hours(24)); + } + + Y_UNIT_TEST(ParseDurationHours_FractionalPlainNumber) { + // Fractional plain number in hours (rounds to microseconds) + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("0.5"), TDuration::MicroSeconds(1800000000)); // 0.5h = 30 min = 1800000000 us + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("1.5"), TDuration::MicroSeconds(5400000000)); // 1.5h = 90 minutes = 5400000000 us + UNIT_ASSERT_VALUES_EQUAL(ParseDurationHours("0.000001"), TDuration::MicroSeconds(3600)); // 0.000001h = 3600 us = 3.6 ms + } + + Y_UNIT_TEST(ParseDurationMicroseconds_WithUnit) { + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("5s"), TDuration::Seconds(5)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("100ms"), TDuration::MilliSeconds(100)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("500us"), TDuration::MicroSeconds(500)); + } + + Y_UNIT_TEST(ParseDurationMicroseconds_PlainNumber) { + // Plain number should be interpreted as microseconds + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("1000"), TDuration::MicroSeconds(1000)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("500"), TDuration::MicroSeconds(500)); + } + + Y_UNIT_TEST(ParseDurationMicroseconds_FractionalPlainNumber) { + // Fractional plain number in microseconds (should round) + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("500.5"), TDuration::MicroSeconds(501)); + UNIT_ASSERT_VALUES_EQUAL(ParseDurationMicroseconds("100.4"), TDuration::MicroSeconds(100)); + } + + Y_UNIT_TEST(ParseDurationWithDefaultUnit_Hours) { + auto hoursParser = [](double hours) { return TDuration::Seconds(hours * 3600); }; + + // With unit - should use the unit + UNIT_ASSERT_VALUES_EQUAL( + ParseDurationWithDefaultUnit("1h", hoursParser), + TDuration::Hours(1) + ); + UNIT_ASSERT_VALUES_EQUAL( + ParseDurationWithDefaultUnit("30m", hoursParser), + TDuration::Minutes(30) + ); + + // Plain number - should use default unit (hours) + UNIT_ASSERT_VALUES_EQUAL( + ParseDurationWithDefaultUnit("2", hoursParser), + TDuration::Hours(2) + ); + UNIT_ASSERT_VALUES_EQUAL( + ParseDurationWithDefaultUnit("0.5", hoursParser), + TDuration::Minutes(30) + ); + } + + Y_UNIT_TEST(ParseDurationWithDefaultUnit_Negative) { + auto msParser = [](double ms) { return TDuration::MilliSeconds(static_cast<ui64>(ms)); }; + UNIT_ASSERT_EXCEPTION(ParseDurationWithDefaultUnit("-5", msParser), TMisuseException); + } + + Y_UNIT_TEST(ParseDurationWithDefaultUnit_Empty) { + auto msParser = [](double ms) { return TDuration::MilliSeconds(static_cast<ui64>(ms)); }; + UNIT_ASSERT_EXCEPTION(ParseDurationWithDefaultUnit("", msParser), TMisuseException); + } +} diff --git a/ydb/public/lib/ydb_cli/common/ut/ya.make b/ydb/public/lib/ydb_cli/common/ut/ya.make index f84667f9ee9..f8a851d0c97 100644 --- a/ydb/public/lib/ydb_cli/common/ut/ya.make +++ b/ydb/public/lib/ydb_cli/common/ut/ya.make @@ -3,6 +3,7 @@ UNITTEST_FOR(ydb/public/lib/ydb_cli/common) SRCS( cert_format_converter_ut.cpp csv_parser_ut.cpp + duration_ut.cpp normalize_path_ut.cpp pg_dump_parser_ut.cpp recursive_remove_ut.cpp diff --git a/ydb/public/lib/ydb_cli/common/ya.make b/ydb/public/lib/ydb_cli/common/ya.make index 1cc8047301e..93f2b6b98cb 100644 --- a/ydb/public/lib/ydb_cli/common/ya.make +++ b/ydb/public/lib/ydb_cli/common/ya.make @@ -10,6 +10,7 @@ SRCS( common.cpp csv_parser.cpp describe.cpp + duration.cpp examples.cpp format.cpp ftxui.cpp |
