diff options
| author | vitalyisaev <[email protected]> | 2023-11-14 09:58:56 +0300 |
|---|---|---|
| committer | vitalyisaev <[email protected]> | 2023-11-14 10:20:20 +0300 |
| commit | c2b2dfd9827a400a8495e172a56343462e3ceb82 (patch) | |
| tree | cd4e4f597d01bede4c82dffeb2d780d0a9046bd0 /contrib/clickhouse/src/Functions/formatReadableTimeDelta.cpp | |
| parent | d4ae8f119e67808cb0cf776ba6e0cf95296f2df7 (diff) | |
YQ Connector: move tests from yql to ydb (OSS)
Перенос папки с тестами на Коннектор из папки yql в папку ydb (синхронизируется с github).
Diffstat (limited to 'contrib/clickhouse/src/Functions/formatReadableTimeDelta.cpp')
| -rw-r--r-- | contrib/clickhouse/src/Functions/formatReadableTimeDelta.cpp | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/contrib/clickhouse/src/Functions/formatReadableTimeDelta.cpp b/contrib/clickhouse/src/Functions/formatReadableTimeDelta.cpp new file mode 100644 index 00000000000..5fd48c01e8f --- /dev/null +++ b/contrib/clickhouse/src/Functions/formatReadableTimeDelta.cpp @@ -0,0 +1,228 @@ +#include <Functions/FunctionFactory.h> +#include <Functions/IFunction.h> +#include <Functions/FunctionHelpers.h> +#include <Columns/ColumnString.h> +#include <Columns/ColumnVector.h> +#include <Common/NaNUtils.h> +#include <DataTypes/DataTypeString.h> +#include <IO/WriteBufferFromVector.h> +#include <IO/WriteHelpers.h> + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int BAD_ARGUMENTS; +} + +namespace +{ + +/** Prints amount of seconds in form of: + * "1 year, 2 months, 12 days, 3 hours, 1 minute and 33 seconds". + * Maximum unit can be specified as a second argument: for example, you can specify "days", + * and it will avoid using years and months. + * + * The length of years and months (and even days in presence of time adjustments) are rough: + * year is just 365 days, month is 30.5 days, day is 86400 seconds. + * + * You may think that the choice of constants and the whole purpose of this function is very ignorant... + * And you're right. But actually it's made similar to a random Python library from the internet: + * https://github.com/jmoiron/humanize/blob/b37dc30ba61c2446eecb1a9d3e9ac8c9adf00f03/src/humanize/time.py#L462 + */ +class FunctionFormatReadableTimeDelta : public IFunction +{ +public: + static constexpr auto name = "formatReadableTimeDelta"; + static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionFormatReadableTimeDelta>(); } + + String getName() const override { return name; } + + bool isVariadic() const override { return true; } + + bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; } + + size_t getNumberOfArguments() const override { return 0; } + + DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override + { + if (arguments.empty()) + throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, + "Number of arguments for function {} doesn't match: passed {}, should be at least 1.", + getName(), arguments.size()); + + if (arguments.size() > 2) + throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, + "Number of arguments for function {} doesn't match: passed {}, should be at most 2.", + getName(), arguments.size()); + + const IDataType & type = *arguments[0]; + + if (!isNativeNumber(type)) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Cannot format {} as time delta", type.getName()); + + if (arguments.size() == 2) + { + const auto * maximum_unit_arg = arguments[1].get(); + if (!isStringOrFixedString(maximum_unit_arg)) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of argument maximum_unit of function {}", + maximum_unit_arg->getName(), getName()); + } + + return std::make_shared<DataTypeString>(); + } + + ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; } + + bool useDefaultImplementationForConstants() const override { return true; } + + enum Unit + { + Seconds, + Minutes, + Hours, + Days, + Months, + Years + }; + + ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override + { + std::string_view maximum_unit_str; + if (arguments.size() == 2) + { + const ColumnPtr & maximum_unit_column = arguments[1].column; + const ColumnConst * maximum_unit_const_col = checkAndGetColumnConstStringOrFixedString(maximum_unit_column.get()); + if (maximum_unit_const_col) + maximum_unit_str = maximum_unit_const_col->getDataColumn().getDataAt(0).toView(); + } + + Unit max_unit; + + /// Default means "use all available units". + if (maximum_unit_str.empty() || maximum_unit_str == "years") + max_unit = Years; + else if (maximum_unit_str == "months") + max_unit = Months; + else if (maximum_unit_str == "days") + max_unit = Days; + else if (maximum_unit_str == "hours") + max_unit = Hours; + else if (maximum_unit_str == "minutes") + max_unit = Minutes; + else if (maximum_unit_str == "seconds") + max_unit = Seconds; + else + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Unexpected value of maximum unit argument ({}) for function {}, the only allowed values are:" + " 'seconds', 'minutes', 'hours', 'days', 'months', 'years'.", + maximum_unit_str, getName()); + + auto col_to = ColumnString::create(); + + ColumnString::Chars & data_to = col_to->getChars(); + ColumnString::Offsets & offsets_to = col_to->getOffsets(); + offsets_to.resize(input_rows_count); + + WriteBufferFromVector<ColumnString::Chars> buf_to(data_to); + + for (size_t i = 0; i < input_rows_count; ++i) + { + /// Virtual call is Ok (negligible comparing to the rest of calculations). + Float64 value = arguments[0].column->getFloat64(i); + + if (!isFinite(value)) + { + /// Cannot decide what unit it is (years, month), just simply write inf or nan. + writeFloatText(value, buf_to); + } + else + { + bool is_negative = value < 0; + if (is_negative) + { + writeChar('-', buf_to); + value = -value; + } + + /// To output separators between parts: ", " and " and ". + bool has_output = false; + + switch (max_unit) /// A kind of Duff Device. + { + case Years: processUnit(365 * 24 * 3600, " year", 5, value, buf_to, has_output); [[fallthrough]]; + case Months: processUnit(static_cast<UInt64>(30.5 * 24 * 3600), " month", 6, value, buf_to, has_output); [[fallthrough]]; + case Days: processUnit(24 * 3600, " day", 4, value, buf_to, has_output); [[fallthrough]]; + case Hours: processUnit(3600, " hour", 5, value, buf_to, has_output); [[fallthrough]]; + case Minutes: processUnit(60, " minute", 7, value, buf_to, has_output); [[fallthrough]]; + case Seconds: processUnit(1, " second", 7, value, buf_to, has_output); + } + } + + writeChar(0, buf_to); + offsets_to[i] = buf_to.count(); + } + + buf_to.finalize(); + return col_to; + } + + static void processUnit( + UInt64 unit_size, const char * unit_name, size_t unit_name_size, + Float64 & value, WriteBuffer & buf_to, bool & has_output) + { + if (unlikely(value + 1.0 == value)) + { + /// The case when value is too large so exact representation for subsequent smaller units is not possible. + writeText(std::floor(value / unit_size), buf_to); + buf_to.write(unit_name, unit_name_size); + writeChar('s', buf_to); + has_output = true; + value = 0; + return; + } + + UInt64 num_units = static_cast<UInt64>(value / unit_size); + + if (!num_units) + { + /// Zero units, no need to print. But if it's the last (seconds) and the only unit, print "0 seconds" nevertheless. + if (unit_size > 1 || has_output) + return; + } + + /// Remaining value to print on next iteration. + value -= num_units * unit_size; + + if (has_output) + { + /// Need delimiter between values. The last delimiter is " and ", all previous are comma. + if (value < 1) + writeCString(" and ", buf_to); + else + writeCString(", ", buf_to); + } + + writeText(num_units, buf_to); + buf_to.write(unit_name, unit_name_size); /// If we just leave strlen(unit_name) here, clang-11 fails to make it compile-time. + + /// How to pronounce: unit vs. units. + if (num_units != 1) + writeChar('s', buf_to); + + has_output = true; + } +}; + +} + +REGISTER_FUNCTION(FormatReadableTimeDelta) +{ + factory.registerFunction<FunctionFormatReadableTimeDelta>(); +} + +} |
