2020-10-10 18:37:02 +00:00
|
|
|
#pragma once
|
2021-12-21 13:41:53 +00:00
|
|
|
#include <Common/DateLUTImpl.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
|
|
|
|
#include <DataTypes/DataTypeDate.h>
|
2021-07-15 11:41:52 +00:00
|
|
|
#include <DataTypes/DataTypeDate32.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
#include <DataTypes/DataTypeDateTime.h>
|
2019-11-04 14:06:22 +00:00
|
|
|
#include <DataTypes/DataTypeDateTime64.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2020-02-24 15:42:40 +00:00
|
|
|
#include <Columns/ColumnsNumber.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2021-05-17 07:30:42 +00:00
|
|
|
#include <Functions/IFunction.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
#include <Functions/FunctionHelpers.h>
|
2020-02-24 15:42:40 +00:00
|
|
|
#include <Functions/castTypeToEither.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
#include <Functions/extractTimeZoneFromFunctionArguments.h>
|
2020-04-17 13:26:44 +00:00
|
|
|
#include <Functions/TransformDateTime64.h>
|
2018-09-26 00:31:40 +00:00
|
|
|
|
|
|
|
#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 ILLEGAL_COLUMN;
|
|
|
|
}
|
|
|
|
|
2019-10-10 13:49:40 +00:00
|
|
|
/// Type of first argument of 'execute' function overload defines what INPUT DataType it is used for.
|
|
|
|
/// Return type defines what is the OUTPUT (return) type of the CH function.
|
|
|
|
/// Corresponding types:
|
|
|
|
/// - UInt16 => DataTypeDate
|
|
|
|
/// - UInt32 => DataTypeDateTime
|
|
|
|
/// - DateTime64 => DataTypeDateTime64
|
|
|
|
/// Please note that INPUT and OUTPUT types may differ, e.g.:
|
|
|
|
/// - 'AddSecondsImpl::execute(UInt32, ...) -> UInt32' is available to the ClickHouse users as 'addSeconds(DateTime, ...) -> DateTime'
|
|
|
|
/// - 'AddSecondsImpl::execute(UInt16, ...) -> UInt32' is available to the ClickHouse users as 'addSeconds(Date, ...) -> DateTime'
|
|
|
|
|
2022-02-09 07:28:15 +00:00
|
|
|
//TODO: pass info about current scale
|
2022-02-07 18:44:14 +00:00
|
|
|
struct AddNanosecondsImpl
|
|
|
|
{
|
|
|
|
static constexpr auto name = "addNanoseconds";
|
|
|
|
|
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &)
|
|
|
|
{
|
|
|
|
return {t.whole, t.fractional + delta};
|
|
|
|
}
|
|
|
|
|
2022-02-09 07:28:15 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
|
2022-02-07 18:44:14 +00:00
|
|
|
{
|
2022-02-09 07:28:15 +00:00
|
|
|
return t + delta;
|
2022-02-07 18:44:14 +00:00
|
|
|
}
|
|
|
|
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
// use default datetime64 scale
|
|
|
|
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta) * 1000;
|
|
|
|
}
|
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.fromDayNum(DayNum(d)) + delta;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct AddMicrosecondsImpl
|
|
|
|
{
|
|
|
|
static constexpr auto name = "addMicroseconds";
|
|
|
|
|
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &)
|
|
|
|
{
|
|
|
|
return {t.whole, t.fractional + delta};
|
|
|
|
}
|
|
|
|
|
2022-02-09 07:28:15 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
|
2022-02-07 18:44:14 +00:00
|
|
|
{
|
2022-02-09 07:28:15 +00:00
|
|
|
return t + delta;
|
2022-02-07 18:44:14 +00:00
|
|
|
}
|
|
|
|
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
// use default datetime64 scale
|
|
|
|
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta) * 1000;
|
|
|
|
}
|
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.fromDayNum(DayNum(d)) + delta;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct AddMillisecondsImpl
|
|
|
|
{
|
|
|
|
static constexpr auto name = "addMilliseconds";
|
|
|
|
|
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &)
|
|
|
|
{
|
|
|
|
return {t.whole, t.fractional + delta};
|
|
|
|
}
|
|
|
|
|
2022-02-09 07:28:15 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
|
2022-02-07 18:44:14 +00:00
|
|
|
{
|
2022-02-09 07:28:15 +00:00
|
|
|
return t + delta;
|
2022-02-07 18:44:14 +00:00
|
|
|
}
|
|
|
|
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
// use default datetime64 scale
|
|
|
|
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta) * 1000;
|
|
|
|
}
|
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.fromDayNum(DayNum(d)) + delta;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddSecondsImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addSeconds";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {t.whole + delta, t.fractional};
|
|
|
|
}
|
|
|
|
|
2021-01-31 03:49:33 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
return t + delta;
|
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
// use default datetime64 scale
|
|
|
|
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta) * 1000;
|
|
|
|
}
|
2021-01-31 03:49:33 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2021-11-08 07:19:31 +00:00
|
|
|
return time_zone.fromDayNum(DayNum(d)) + delta;
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddMinutesImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addMinutes";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {t.whole + delta * 60, t.fractional};
|
|
|
|
}
|
|
|
|
|
2021-01-22 21:25:49 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
return t + delta * 60;
|
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
// use default datetime64 scale
|
|
|
|
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta * 60) * 1000;
|
|
|
|
}
|
2021-01-31 03:49:33 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2021-11-08 07:19:31 +00:00
|
|
|
return time_zone.fromDayNum(DayNum(d)) + delta * 60;
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddHoursImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addHours";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl &)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {t.whole + delta * 3600, t.fractional};
|
|
|
|
}
|
2021-01-22 21:25:49 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
return t + delta * 3600;
|
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
// use default datetime64 scale
|
|
|
|
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta * 3600) * 1000;
|
|
|
|
}
|
2021-01-31 03:49:33 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2021-11-08 07:19:31 +00:00
|
|
|
return time_zone.fromDayNum(DayNum(d)) + delta * 3600;
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddDaysImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addDays";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {time_zone.addDays(t.whole, delta), t.fractional};
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
return time_zone.addDays(t, delta);
|
|
|
|
}
|
|
|
|
|
2021-01-31 03:49:33 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
return d + delta;
|
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
|
|
|
|
static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &)
|
|
|
|
{
|
|
|
|
return d + delta;
|
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddWeeksImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addWeeks";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED DecimalUtils::DecimalComponents<DateTime64>
|
2021-07-15 11:41:52 +00:00
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int32 delta, const DateLUTImpl & time_zone)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {time_zone.addWeeks(t.whole, delta), t.fractional};
|
|
|
|
}
|
|
|
|
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int32 delta, const DateLUTImpl & time_zone)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
return time_zone.addWeeks(t, delta);
|
|
|
|
}
|
|
|
|
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int32 delta, const DateLUTImpl &)
|
|
|
|
{
|
|
|
|
return d + delta * 7;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int32 delta, const DateLUTImpl &)
|
2021-06-17 08:03:31 +00:00
|
|
|
{
|
|
|
|
return d + delta * 7;
|
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddMonthsImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addMonths";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {time_zone.addMonths(t.whole, delta), t.fractional};
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:31:40 +00:00
|
|
|
static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.addMonths(t, delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
2021-11-08 07:19:31 +00:00
|
|
|
return time_zone.addMonths(DayNum(d), delta);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
|
|
|
|
static inline Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.addMonths(ExtendedDayNum(d), delta);
|
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddQuartersImpl
|
2018-12-18 13:16:48 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addQuarters";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline DecimalUtils::DecimalComponents<DateTime64>
|
2021-07-15 11:41:52 +00:00
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int32 delta, const DateLUTImpl & time_zone)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {time_zone.addQuarters(t.whole, delta), t.fractional};
|
|
|
|
}
|
|
|
|
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline UInt32 execute(UInt32 t, Int32 delta, const DateLUTImpl & time_zone)
|
2018-12-18 13:16:48 +00:00
|
|
|
{
|
|
|
|
return time_zone.addQuarters(t, delta);
|
|
|
|
}
|
|
|
|
|
2021-07-15 11:41:52 +00:00
|
|
|
static inline UInt16 execute(UInt16 d, Int32 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
2021-11-08 07:19:31 +00:00
|
|
|
return time_zone.addQuarters(DayNum(d), delta);
|
2021-07-15 11:41:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline Int32 execute(Int32 d, Int32 delta, const DateLUTImpl & time_zone)
|
2021-06-17 08:03:31 +00:00
|
|
|
{
|
|
|
|
return time_zone.addQuarters(ExtendedDayNum(d), delta);
|
|
|
|
}
|
2018-12-18 13:16:48 +00:00
|
|
|
};
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
struct AddYearsImpl
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
static constexpr auto name = "addYears";
|
|
|
|
|
2021-03-23 23:07:13 +00:00
|
|
|
static inline DecimalUtils::DecimalComponents<DateTime64>
|
|
|
|
execute(DecimalUtils::DecimalComponents<DateTime64> t, Int64 delta, const DateLUTImpl & time_zone)
|
2020-04-17 13:26:44 +00:00
|
|
|
{
|
|
|
|
return {time_zone.addYears(t.whole, delta), t.fractional};
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:31:40 +00:00
|
|
|
static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.addYears(t, delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
2021-11-08 07:19:31 +00:00
|
|
|
return time_zone.addYears(DayNum(d), delta);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
|
|
|
|
static inline Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone)
|
|
|
|
{
|
|
|
|
return time_zone.addYears(ExtendedDayNum(d), delta);
|
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
template <typename Transform>
|
2019-10-10 13:49:40 +00:00
|
|
|
struct SubtractIntervalImpl : public Transform
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2019-10-10 13:49:40 +00:00
|
|
|
using Transform::Transform;
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
template <typename T>
|
2021-01-21 12:01:24 +00:00
|
|
|
inline NO_SANITIZE_UNDEFINED auto execute(T t, Int64 delta, const DateLUTImpl & time_zone) const
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2021-01-21 12:01:24 +00:00
|
|
|
/// Signed integer overflow is Ok.
|
2019-10-09 13:18:38 +00:00
|
|
|
return Transform::execute(t, -delta, time_zone);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-02-09 07:28:15 +00:00
|
|
|
struct SubtractNanosecondsImpl : SubtractIntervalImpl<AddNanosecondsImpl> { static constexpr auto name = "subtractNanoseconds"; };
|
|
|
|
struct SubtractMicrosecondsImpl : SubtractIntervalImpl<AddMicrosecondsImpl> { static constexpr auto name = "subtractMicroseconds"; };
|
|
|
|
struct SubtractMillisecondsImpl : SubtractIntervalImpl<AddMillisecondsImpl> { static constexpr auto name = "subtractMilliseconds"; };
|
2018-09-26 00:31:40 +00:00
|
|
|
struct SubtractSecondsImpl : SubtractIntervalImpl<AddSecondsImpl> { static constexpr auto name = "subtractSeconds"; };
|
|
|
|
struct SubtractMinutesImpl : SubtractIntervalImpl<AddMinutesImpl> { static constexpr auto name = "subtractMinutes"; };
|
|
|
|
struct SubtractHoursImpl : SubtractIntervalImpl<AddHoursImpl> { static constexpr auto name = "subtractHours"; };
|
|
|
|
struct SubtractDaysImpl : SubtractIntervalImpl<AddDaysImpl> { static constexpr auto name = "subtractDays"; };
|
|
|
|
struct SubtractWeeksImpl : SubtractIntervalImpl<AddWeeksImpl> { static constexpr auto name = "subtractWeeks"; };
|
|
|
|
struct SubtractMonthsImpl : SubtractIntervalImpl<AddMonthsImpl> { static constexpr auto name = "subtractMonths"; };
|
2018-12-18 13:16:48 +00:00
|
|
|
struct SubtractQuartersImpl : SubtractIntervalImpl<AddQuartersImpl> { static constexpr auto name = "subtractQuarters"; };
|
2018-09-26 00:31:40 +00:00
|
|
|
struct SubtractYearsImpl : SubtractIntervalImpl<AddYearsImpl> { static constexpr auto name = "subtractYears"; };
|
|
|
|
|
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
template <typename Transform>
|
2018-09-26 00:31:40 +00:00
|
|
|
struct Adder
|
|
|
|
{
|
2019-10-09 13:18:38 +00:00
|
|
|
const Transform transform;
|
|
|
|
|
|
|
|
explicit Adder(Transform transform_)
|
|
|
|
: transform(std::move(transform_))
|
|
|
|
{}
|
|
|
|
|
|
|
|
template <typename FromVectorType, typename ToVectorType>
|
2020-03-23 02:12:31 +00:00
|
|
|
void NO_INLINE vectorConstant(const FromVectorType & vec_from, ToVectorType & vec_to, Int64 delta, const DateLUTImpl & time_zone) const
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
size_t size = vec_from.size();
|
|
|
|
vec_to.resize(size);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < size; ++i)
|
2020-02-24 15:42:40 +00:00
|
|
|
vec_to[i] = transform.execute(vec_from[i], delta, time_zone);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
template <typename FromVectorType, typename ToVectorType>
|
2020-03-23 02:12:31 +00:00
|
|
|
void vectorVector(const FromVectorType & vec_from, ToVectorType & vec_to, const IColumn & delta, const DateLUTImpl & time_zone) const
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
size_t size = vec_from.size();
|
|
|
|
vec_to.resize(size);
|
|
|
|
|
2020-02-24 15:42:40 +00:00
|
|
|
castTypeToEither<
|
|
|
|
ColumnUInt8, ColumnUInt16, ColumnUInt32, ColumnUInt64,
|
|
|
|
ColumnInt8, ColumnInt16, ColumnInt32, ColumnInt64,
|
|
|
|
ColumnFloat32, ColumnFloat64>(
|
2020-03-23 02:12:31 +00:00
|
|
|
&delta, [&](const auto & column){ vectorVector(vec_from, vec_to, column, time_zone, size); return true; });
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
template <typename FromType, typename ToVectorType>
|
2020-03-23 02:12:31 +00:00
|
|
|
void constantVector(const FromType & from, ToVectorType & vec_to, const IColumn & delta, const DateLUTImpl & time_zone) const
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
|
|
|
size_t size = delta.size();
|
|
|
|
vec_to.resize(size);
|
|
|
|
|
2020-02-24 15:42:40 +00:00
|
|
|
castTypeToEither<
|
|
|
|
ColumnUInt8, ColumnUInt16, ColumnUInt32, ColumnUInt64,
|
|
|
|
ColumnInt8, ColumnInt16, ColumnInt32, ColumnInt64,
|
|
|
|
ColumnFloat32, ColumnFloat64>(
|
2020-03-23 02:12:31 +00:00
|
|
|
&delta, [&](const auto & column){ constantVector(from, vec_to, column, time_zone, size); return true; });
|
2020-02-24 15:42:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
template <typename FromVectorType, typename ToVectorType, typename DeltaColumnType>
|
2021-03-23 23:07:13 +00:00
|
|
|
NO_INLINE NO_SANITIZE_UNDEFINED void vectorVector(
|
|
|
|
const FromVectorType & vec_from, ToVectorType & vec_to, const DeltaColumnType & delta, const DateLUTImpl & time_zone, size_t size) const
|
2020-02-24 15:42:40 +00:00
|
|
|
{
|
|
|
|
for (size_t i = 0; i < size; ++i)
|
|
|
|
vec_to[i] = transform.execute(vec_from[i], delta.getData()[i], time_zone);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename FromType, typename ToVectorType, typename DeltaColumnType>
|
2021-03-23 23:07:13 +00:00
|
|
|
NO_INLINE NO_SANITIZE_UNDEFINED void constantVector(
|
|
|
|
const FromType & from, ToVectorType & vec_to, const DeltaColumnType & delta, const DateLUTImpl & time_zone, size_t size) const
|
2020-02-24 15:42:40 +00:00
|
|
|
{
|
2018-09-26 00:31:40 +00:00
|
|
|
for (size_t i = 0; i < size; ++i)
|
2020-02-24 15:42:40 +00:00
|
|
|
vec_to[i] = transform.execute(from, delta.getData()[i], time_zone);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
template <typename FromDataType, typename ToDataType, typename Transform>
|
2018-09-26 00:31:40 +00:00
|
|
|
struct DateTimeAddIntervalImpl
|
|
|
|
{
|
2020-11-17 13:24:45 +00:00
|
|
|
static ColumnPtr execute(Transform transform, const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2019-10-09 13:18:38 +00:00
|
|
|
using FromValueType = typename FromDataType::FieldType;
|
|
|
|
using FromColumnType = typename FromDataType::ColumnType;
|
|
|
|
using ToColumnType = typename ToDataType::ColumnType;
|
|
|
|
|
|
|
|
auto op = Adder<Transform>{std::move(transform)};
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2020-10-17 15:22:42 +00:00
|
|
|
const DateLUTImpl & time_zone = extractTimeZoneFromFunctionArguments(arguments, 2, 0);
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2020-10-17 15:22:42 +00:00
|
|
|
const ColumnPtr source_col = arguments[0].column;
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2020-10-17 15:22:42 +00:00
|
|
|
auto result_col = result_type->createColumn();
|
2019-10-09 13:18:38 +00:00
|
|
|
auto col_to = assert_cast<ToColumnType *>(result_col.get());
|
2018-09-26 00:31:40 +00:00
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
if (const auto * sources = checkAndGetColumn<FromColumnType>(source_col.get()))
|
|
|
|
{
|
2020-10-17 15:22:42 +00:00
|
|
|
const IColumn & delta_column = *arguments[1].column;
|
2018-09-26 00:31:40 +00:00
|
|
|
|
|
|
|
if (const auto * delta_const_column = typeid_cast<const ColumnConst *>(&delta_column))
|
2020-06-28 20:20:49 +00:00
|
|
|
op.vectorConstant(sources->getData(), col_to->getData(), delta_const_column->getInt(0), time_zone);
|
2018-09-26 00:31:40 +00:00
|
|
|
else
|
2020-03-23 02:12:31 +00:00
|
|
|
op.vectorVector(sources->getData(), col_to->getData(), delta_column, time_zone);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
2019-10-09 13:18:38 +00:00
|
|
|
else if (const auto * sources_const = checkAndGetColumnConst<FromColumnType>(source_col.get()))
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2020-06-28 20:20:49 +00:00
|
|
|
op.constantVector(
|
|
|
|
sources_const->template getValue<FromValueType>(),
|
|
|
|
col_to->getData(),
|
2020-10-17 15:22:42 +00:00
|
|
|
*arguments[1].column, time_zone);
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-10-17 15:22:42 +00:00
|
|
|
throw Exception("Illegal column " + arguments[0].column->getName()
|
2018-09-26 00:31:40 +00:00
|
|
|
+ " of first argument of function " + Transform::name,
|
|
|
|
ErrorCodes::ILLEGAL_COLUMN);
|
|
|
|
}
|
2019-10-09 13:18:38 +00:00
|
|
|
|
2020-10-17 15:22:42 +00:00
|
|
|
return result_col;
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-10 13:49:40 +00:00
|
|
|
namespace date_and_time_type_details
|
|
|
|
{
|
|
|
|
// Compile-time mapping of value (DataType::FieldType) types to corresponding DataType
|
|
|
|
template <typename FieldType> struct ResultDataTypeMap {};
|
|
|
|
template <> struct ResultDataTypeMap<UInt16> { using ResultDataType = DataTypeDate; };
|
|
|
|
template <> struct ResultDataTypeMap<Int16> { using ResultDataType = DataTypeDate; };
|
|
|
|
template <> struct ResultDataTypeMap<UInt32> { using ResultDataType = DataTypeDateTime; };
|
2021-07-15 11:41:52 +00:00
|
|
|
template <> struct ResultDataTypeMap<Int32> { using ResultDataType = DataTypeDate32; };
|
2019-10-10 13:49:40 +00:00
|
|
|
template <> struct ResultDataTypeMap<DateTime64> { using ResultDataType = DataTypeDateTime64; };
|
2020-04-17 13:26:44 +00:00
|
|
|
template <> struct ResultDataTypeMap<Int64> { using ResultDataType = DataTypeDateTime64; };
|
2019-10-10 13:49:40 +00:00
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
|
|
|
|
template <typename Transform>
|
|
|
|
class FunctionDateOrDateTimeAddInterval : public IFunction
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
static constexpr auto name = Transform::name;
|
2021-04-10 23:33:54 +00:00
|
|
|
static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionDateOrDateTimeAddInterval>(); }
|
2018-09-26 00:31:40 +00:00
|
|
|
|
|
|
|
String getName() const override
|
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isVariadic() const override { return true; }
|
|
|
|
size_t getNumberOfArguments() const override { return 0; }
|
2021-06-22 16:21:23 +00:00
|
|
|
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; }
|
2018-09-26 00:31:40 +00:00
|
|
|
|
|
|
|
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
|
|
|
|
{
|
|
|
|
if (arguments.size() != 2 && arguments.size() != 3)
|
|
|
|
throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
|
|
|
|
+ toString(arguments.size()) + ", should be 2 or 3",
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
2019-05-24 12:11:03 +00:00
|
|
|
if (!isNativeNumber(arguments[1].type))
|
2018-09-26 00:31:40 +00:00
|
|
|
throw Exception("Second argument for function " + getName() + " (delta) must be number",
|
|
|
|
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
|
|
|
|
|
|
|
if (arguments.size() == 2)
|
|
|
|
{
|
2021-07-15 11:41:52 +00:00
|
|
|
if (!isDate(arguments[0].type) && !isDate32(arguments[0].type) && !isDateTime(arguments[0].type) && !isDateTime64(arguments[0].type))
|
2020-02-24 14:28:32 +00:00
|
|
|
throw Exception{"Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName() +
|
2018-09-26 00:31:40 +00:00
|
|
|
". Should be a date or a date with time", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!WhichDataType(arguments[0].type).isDateTime()
|
|
|
|
|| !WhichDataType(arguments[2].type).isString())
|
2020-06-28 20:20:49 +00:00
|
|
|
{
|
2018-09-26 00:31:40 +00:00
|
|
|
throw Exception(
|
|
|
|
"Function " + getName() + " supports 2 or 3 arguments. The 1st argument "
|
|
|
|
"must be of type Date or DateTime. The 2nd argument must be number. "
|
|
|
|
"The 3rd argument (optional) must be "
|
|
|
|
"a constant string with timezone name. The timezone argument is allowed "
|
|
|
|
"only when the 1st argument has the type DateTime",
|
|
|
|
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
2020-06-28 20:20:49 +00:00
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
switch (arguments[0].type->getTypeId())
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2019-10-09 13:18:38 +00:00
|
|
|
case TypeIndex::Date:
|
2019-10-10 13:49:40 +00:00
|
|
|
return resolveReturnType<DataTypeDate>(arguments);
|
2021-07-15 11:41:52 +00:00
|
|
|
case TypeIndex::Date32:
|
|
|
|
return resolveReturnType<DataTypeDate32>(arguments);
|
2019-10-09 13:18:38 +00:00
|
|
|
case TypeIndex::DateTime:
|
2019-10-10 13:49:40 +00:00
|
|
|
return resolveReturnType<DataTypeDateTime>(arguments);
|
2019-10-09 13:18:38 +00:00
|
|
|
case TypeIndex::DateTime64:
|
2019-10-10 13:49:40 +00:00
|
|
|
return resolveReturnType<DataTypeDateTime64>(arguments);
|
2019-10-09 13:18:38 +00:00
|
|
|
default:
|
|
|
|
{
|
|
|
|
throw Exception("Invalid type of 1st argument of function " + getName() + ": "
|
|
|
|
+ arguments[0].type->getName() + ", expected: Date, DateTime or DateTime64.",
|
|
|
|
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
2019-10-09 13:18:38 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 13:26:44 +00:00
|
|
|
// TransformDateTime64 helps choosing correct overload of exec and does some transformations
|
|
|
|
// on input and output parameters to simplify support of DateTime64 in concrete Transform.
|
|
|
|
template <typename FieldType>
|
|
|
|
using TransformType = std::conditional_t<
|
|
|
|
std::is_same_v<FieldType, DateTime64>,
|
|
|
|
TransformDateTime64<Transform>,
|
|
|
|
Transform>;
|
|
|
|
|
2020-06-28 20:20:49 +00:00
|
|
|
/// Helper templates to deduce return type based on argument type, since some overloads may promote or denote types,
|
|
|
|
/// e.g. addSeconds(Date, 1) => DateTime
|
2022-02-09 07:28:15 +00:00
|
|
|
|
2019-10-09 13:18:38 +00:00
|
|
|
template <typename FieldType>
|
2020-04-17 13:26:44 +00:00
|
|
|
using TransformExecuteReturnType = decltype(std::declval<TransformType<FieldType>>().execute(FieldType(), 0, std::declval<DateLUTImpl>()));
|
2019-10-09 13:18:38 +00:00
|
|
|
|
2020-08-08 00:47:03 +00:00
|
|
|
// Deduces RETURN DataType from INPUT DataType, based on return type of Transform{}.execute(INPUT_TYPE, UInt64, DateLUTImpl).
|
2019-10-09 13:18:38 +00:00
|
|
|
// e.g. for Transform-type that has execute()-overload with 'UInt16' input and 'UInt32' return,
|
|
|
|
// argument type is expected to be 'Date', and result type is deduced to be 'DateTime'.
|
2019-10-10 13:53:53 +00:00
|
|
|
template <typename FromDataType>
|
|
|
|
using TransformResultDataType = typename date_and_time_type_details::ResultDataTypeMap<TransformExecuteReturnType<typename FromDataType::FieldType>>::ResultDataType;
|
2019-10-09 13:18:38 +00:00
|
|
|
|
2019-10-10 13:49:40 +00:00
|
|
|
template <typename FromDataType>
|
2019-10-09 13:18:38 +00:00
|
|
|
DataTypePtr resolveReturnType(const ColumnsWithTypeAndName & arguments) const
|
|
|
|
{
|
2019-10-10 13:53:53 +00:00
|
|
|
using ResultDataType = TransformResultDataType<FromDataType>;
|
2019-10-09 13:18:38 +00:00
|
|
|
|
|
|
|
if constexpr (std::is_same_v<ResultDataType, DataTypeDate>)
|
|
|
|
return std::make_shared<DataTypeDate>();
|
2021-07-15 11:41:52 +00:00
|
|
|
else if constexpr (std::is_same_v<ResultDataType, DataTypeDate32>)
|
|
|
|
return std::make_shared<DataTypeDate32>();
|
2019-10-09 13:18:38 +00:00
|
|
|
else if constexpr (std::is_same_v<ResultDataType, DataTypeDateTime>)
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2019-10-09 13:18:38 +00:00
|
|
|
return std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 2, 0));
|
|
|
|
}
|
|
|
|
else if constexpr (std::is_same_v<ResultDataType, DataTypeDateTime64>)
|
|
|
|
{
|
2021-07-15 11:41:52 +00:00
|
|
|
if (typeid_cast<const DataTypeDateTime64 *>(arguments[0].type.get()))
|
|
|
|
{
|
|
|
|
const auto & datetime64_type = assert_cast<const DataTypeDateTime64 &>(*arguments[0].type);
|
2022-02-09 07:28:15 +00:00
|
|
|
|
|
|
|
auto from_scale = datetime64_type.getScale();
|
|
|
|
auto scale = from_scale;
|
|
|
|
|
|
|
|
if (std::is_same_v<Transform, AddNanosecondsImpl>)
|
|
|
|
scale = 9;
|
|
|
|
else if (std::is_same_v<Transform, AddMicrosecondsImpl>)
|
|
|
|
scale = 6;
|
|
|
|
else if (std::is_same_v<Transform, AddMillisecondsImpl>)
|
|
|
|
scale = 3;
|
|
|
|
|
|
|
|
scale = std::max(scale, from_scale);
|
|
|
|
|
|
|
|
return std::make_shared<DataTypeDateTime64>(scale, extractTimeZoneNameFromFunctionArguments(arguments, 2, 0));
|
2021-07-15 11:41:52 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-02-09 07:28:15 +00:00
|
|
|
auto scale = DataTypeDateTime64::default_scale;
|
|
|
|
|
|
|
|
if (std::is_same_v<Transform, AddNanosecondsImpl>)
|
|
|
|
scale = 9;
|
|
|
|
else if (std::is_same_v<Transform, AddMicrosecondsImpl>)
|
|
|
|
scale = 6;
|
|
|
|
else if (std::is_same_v<Transform, AddMillisecondsImpl>)
|
|
|
|
scale = 3;
|
|
|
|
return std::make_shared<DataTypeDateTime64>(scale, extractTimeZoneNameFromFunctionArguments(arguments, 2, 0));
|
2021-07-15 11:41:52 +00:00
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-11-10 13:13:26 +00:00
|
|
|
static_assert("Failed to resolve return type.");
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
2019-11-11 04:15:24 +00:00
|
|
|
|
|
|
|
//to make PVS and GCC happy.
|
|
|
|
return nullptr;
|
2018-09-26 00:31:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool useDefaultImplementationForConstants() const override { return true; }
|
|
|
|
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {2}; }
|
|
|
|
|
2020-11-17 13:24:45 +00:00
|
|
|
ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t /*input_rows_count*/) const override
|
2018-09-26 00:31:40 +00:00
|
|
|
{
|
2020-10-17 15:22:42 +00:00
|
|
|
const IDataType * from_type = arguments[0].type.get();
|
2018-09-26 00:31:40 +00:00
|
|
|
WhichDataType which(from_type);
|
|
|
|
|
|
|
|
if (which.isDate())
|
2019-10-09 13:18:38 +00:00
|
|
|
{
|
2020-10-17 15:22:42 +00:00
|
|
|
return DateTimeAddIntervalImpl<DataTypeDate, TransformResultDataType<DataTypeDate>, Transform>::execute(
|
|
|
|
Transform{}, arguments, result_type);
|
2019-10-09 13:18:38 +00:00
|
|
|
}
|
2021-07-15 11:41:52 +00:00
|
|
|
else if (which.isDate32())
|
|
|
|
{
|
|
|
|
return DateTimeAddIntervalImpl<DataTypeDate32, TransformResultDataType<DataTypeDate32>, Transform>::execute(
|
|
|
|
Transform{}, arguments, result_type);
|
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
else if (which.isDateTime())
|
2019-10-09 13:18:38 +00:00
|
|
|
{
|
2020-10-17 15:22:42 +00:00
|
|
|
return DateTimeAddIntervalImpl<DataTypeDateTime, TransformResultDataType<DataTypeDateTime>, Transform>::execute(
|
|
|
|
Transform{}, arguments, result_type);
|
2019-10-09 13:18:38 +00:00
|
|
|
}
|
|
|
|
else if (const auto * datetime64_type = assert_cast<const DataTypeDateTime64 *>(from_type))
|
|
|
|
{
|
2020-04-17 13:26:44 +00:00
|
|
|
using WrappedTransformType = TransformType<typename DataTypeDateTime64::FieldType>;
|
|
|
|
return DateTimeAddIntervalImpl<DataTypeDateTime64, TransformResultDataType<DataTypeDateTime64>, WrappedTransformType>::execute(
|
|
|
|
WrappedTransformType{datetime64_type->getScale()}, arguments, result_type);
|
2019-10-09 13:18:38 +00:00
|
|
|
}
|
2018-09-26 00:31:40 +00:00
|
|
|
else
|
2020-10-17 15:22:42 +00:00
|
|
|
throw Exception("Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName(),
|
2018-09-26 00:31:40 +00:00
|
|
|
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|