2020-10-10 18:23:54 +00:00
|
|
|
#include <Functions/FunctionFactory.h>
|
|
|
|
#include <Functions/IFunctionImpl.h>
|
|
|
|
#include <Functions/FunctionHelpers.h>
|
|
|
|
#include <Columns/ColumnString.h>
|
|
|
|
#include <Columns/ColumnVector.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;
|
2020-10-10 22:03:03 +00:00
|
|
|
extern const int BAD_ARGUMENTS;
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
class FunctionFormatReadableTimeDelta : public IFunction
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
static constexpr auto name = "formatReadableTimeDelta";
|
|
|
|
static FunctionPtr create(const Context &) { return std::make_shared<FunctionFormatReadableTimeDelta>(); }
|
|
|
|
|
|
|
|
String getName() const override { return name; }
|
|
|
|
|
|
|
|
bool isVariadic() const override { return true; }
|
|
|
|
|
|
|
|
size_t getNumberOfArguments() const override { return 0; }
|
|
|
|
|
|
|
|
DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
|
|
|
|
{
|
|
|
|
if (arguments.size() < 1)
|
|
|
|
throw Exception(
|
|
|
|
"Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size())
|
|
|
|
+ ", should be at least 1.",
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
|
|
if (arguments.size() > 2)
|
|
|
|
throw Exception(
|
|
|
|
"Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size())
|
|
|
|
+ ", should be at most 2.",
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
|
|
const IDataType & type = *arguments[0];
|
|
|
|
|
|
|
|
if (!isNativeNumber(type))
|
|
|
|
throw Exception("Cannot format " + type.getName() + " as time delta", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
|
|
|
|
|
|
|
if (arguments.size() == 2)
|
|
|
|
{
|
|
|
|
const auto * maximum_unit_arg = arguments[1].get();
|
|
|
|
if (!isStringOrFixedString(maximum_unit_arg))
|
|
|
|
throw Exception("Illegal type " + maximum_unit_arg->getName() + " of argument maximum_unit of function "
|
|
|
|
+ getName(), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_shared<DataTypeString>();
|
|
|
|
}
|
|
|
|
|
|
|
|
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; }
|
|
|
|
|
|
|
|
bool useDefaultImplementationForConstants() const override { return true; }
|
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
enum Unit
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
Seconds,
|
|
|
|
Minutes,
|
|
|
|
Hours,
|
|
|
|
Days,
|
|
|
|
Months,
|
|
|
|
Years
|
|
|
|
};
|
|
|
|
|
|
|
|
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
StringRef maximum_unit_str;
|
|
|
|
if (arguments.size() == 2)
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
const ColumnPtr & maximum_unit_column = block.getByPosition(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);
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
2020-10-10 22:03:03 +00:00
|
|
|
|
|
|
|
Unit max_unit;
|
|
|
|
if (maximum_unit_str.size == 0 || 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;
|
2020-10-10 18:23:54 +00:00
|
|
|
else
|
2020-10-10 22:03:03 +00:00
|
|
|
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.toString(), 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)
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
Float64 value = block.getByPosition(arguments[0]).column->getFloat64(i);
|
|
|
|
bool is_negative = value < 0;
|
|
|
|
if (is_negative)
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
writeChar('-', buf_to);
|
|
|
|
value = -value;
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
bool has_output = false;
|
|
|
|
|
|
|
|
switch (max_unit)
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
case Years: processUnit(365 * 24 * 3600, " year", 5, value, buf_to, has_output); [[fallthrough]];
|
|
|
|
case Months: processUnit(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);
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
2020-10-10 22:03:03 +00:00
|
|
|
|
|
|
|
writeChar(0, buf_to);
|
|
|
|
offsets_to[i] = buf_to.count();
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
buf_to.finalize();
|
|
|
|
block.getByPosition(result).column = std::move(col_to);
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
static void processUnit(
|
|
|
|
UInt64 unit_size, const char * unit_name, size_t unit_name_size,
|
|
|
|
Float64 & value, WriteBuffer & buf_to, bool & has_output)
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
if (unlikely(value + 1.0 == value))
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
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;
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
UInt64 num_units = value / unit_size;
|
|
|
|
|
|
|
|
if (!num_units)
|
2020-10-10 18:23:54 +00:00
|
|
|
{
|
2020-10-10 22:03:03 +00:00
|
|
|
if (unit_size > 1 || has_output)
|
|
|
|
return;
|
|
|
|
}
|
2020-10-10 18:23:54 +00:00
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
value -= num_units * unit_size;
|
2020-10-10 18:23:54 +00:00
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
if (has_output)
|
|
|
|
{
|
|
|
|
if (value < 1)
|
|
|
|
writeCString(" and ", buf_to);
|
|
|
|
else
|
|
|
|
writeCString(", ", buf_to);
|
|
|
|
}
|
2020-10-10 18:23:54 +00:00
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
writeText(num_units, buf_to);
|
|
|
|
buf_to.write(unit_name, unit_name_size);
|
|
|
|
if (num_units != 1)
|
|
|
|
writeChar('s', buf_to);
|
2020-10-10 18:23:54 +00:00
|
|
|
|
2020-10-10 22:03:03 +00:00
|
|
|
has_output = true;
|
2020-10-10 18:23:54 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void registerFunctionFormatReadableTimeDelta(FunctionFactory & factory)
|
|
|
|
{
|
|
|
|
factory.registerFunction<FunctionFormatReadableTimeDelta>();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|