| 1 | #include <common/DateLUTImpl.h> |
| 2 | |
| 3 | #include <DataTypes/DataTypeDate.h> |
| 4 | #include <DataTypes/DataTypeDateTime.h> |
| 5 | #include <DataTypes/DataTypeDateTime64.h> |
| 6 | |
| 7 | #include <Columns/ColumnVector.h> |
| 8 | |
| 9 | #include <Functions/IFunctionImpl.h> |
| 10 | #include <Functions/FunctionHelpers.h> |
| 11 | #include <Functions/extractTimeZoneFromFunctionArguments.h> |
| 12 | |
| 13 | #include <IO/WriteHelpers.h> |
| 14 | |
| 15 | |
| 16 | namespace DB |
| 17 | { |
| 18 | |
| 19 | namespace ErrorCodes |
| 20 | { |
| 21 | extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; |
| 22 | extern const int ILLEGAL_TYPE_OF_ARGUMENT; |
| 23 | extern const int ILLEGAL_COLUMN; |
| 24 | } |
| 25 | |
| 26 | /// AddOnDateTime64DefaultImpl provides default implementation of add-X functionality for DateTime64. |
| 27 | /// |
| 28 | /// Default implementation is not to change fractional part, but only modify whole part as if it was DateTime. |
| 29 | /// That means large whole values (for scale less than 9) might not fit into UInt32-range, |
| 30 | /// and hence default implementation will produce incorrect results. |
| 31 | template <typename T> |
| 32 | struct AddOnDateTime64DefaultImpl |
| 33 | { |
| 34 | /*explicit*/ AddOnDateTime64DefaultImpl(UInt32 scale_ = 0) |
| 35 | : scale_multiplier(DecimalUtils::scaleMultiplier<DateTime64::NativeType>(scale_)) |
| 36 | {} |
| 37 | |
| 38 | // Default implementation for add/sub on DateTime64: do math on whole part (the same way as for DateTime), leave fractional as it is. |
| 39 | inline DateTime64 execute(const DateTime64 & t, Int64 delta, const DateLUTImpl & time_zone) const |
| 40 | { |
| 41 | const auto components = DecimalUtils::splitWithScaleMultiplier(t, scale_multiplier); |
| 42 | |
| 43 | const auto whole = static_cast<const T*>(this)->execute(static_cast<UInt32>(components.whole), delta, time_zone); |
| 44 | return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(static_cast<DateTime64::NativeType>(whole), components.fractional, scale_multiplier); |
| 45 | } |
| 46 | |
| 47 | UInt32 scale_multiplier = 1; |
| 48 | }; |
| 49 | |
| 50 | |
| 51 | /// Type of first argument of 'execute' function overload defines what INPUT DataType it is used for. |
| 52 | /// Return type defines what is the OUTPUT (return) type of the CH function. |
| 53 | /// Corresponding types: |
| 54 | /// - UInt16 => DataTypeDate |
| 55 | /// - UInt32 => DataTypeDateTime |
| 56 | /// - DateTime64 => DataTypeDateTime64 |
| 57 | /// Please note that INPUT and OUTPUT types may differ, e.g.: |
| 58 | /// - 'AddSecondsImpl::execute(UInt32, ...) -> UInt32' is available to the ClickHouse users as 'addSeconds(DateTime, ...) -> DateTime' |
| 59 | /// - 'AddSecondsImpl::execute(UInt16, ...) -> UInt32' is available to the ClickHouse users as 'addSeconds(Date, ...) -> DateTime' |
| 60 | |
| 61 | struct AddSecondsImpl : public AddOnDateTime64DefaultImpl<AddSecondsImpl> |
| 62 | { |
| 63 | using Base = AddOnDateTime64DefaultImpl<AddSecondsImpl>; |
| 64 | using Base::Base; |
| 65 | using Base::execute; |
| 66 | |
| 67 | static constexpr auto name = "addSeconds" ; |
| 68 | |
| 69 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &) |
| 70 | { |
| 71 | return t + delta; |
| 72 | } |
| 73 | |
| 74 | static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) |
| 75 | { |
| 76 | return time_zone.fromDayNum(DayNum(d)) + delta; |
| 77 | } |
| 78 | }; |
| 79 | |
| 80 | struct AddMinutesImpl : public AddOnDateTime64DefaultImpl<AddMinutesImpl> |
| 81 | { |
| 82 | using Base = AddOnDateTime64DefaultImpl<AddMinutesImpl>; |
| 83 | using Base::Base; |
| 84 | using Base::execute; |
| 85 | |
| 86 | static constexpr auto name = "addMinutes" ; |
| 87 | |
| 88 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &) |
| 89 | { |
| 90 | return t + delta * 60; |
| 91 | } |
| 92 | |
| 93 | static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) |
| 94 | { |
| 95 | return time_zone.fromDayNum(DayNum(d)) + delta * 60; |
| 96 | } |
| 97 | }; |
| 98 | |
| 99 | struct AddHoursImpl : public AddOnDateTime64DefaultImpl<AddHoursImpl> |
| 100 | { |
| 101 | using Base = AddOnDateTime64DefaultImpl<AddHoursImpl>; |
| 102 | using Base::Base; |
| 103 | using Base::execute; |
| 104 | |
| 105 | static constexpr auto name = "addHours" ; |
| 106 | |
| 107 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &) |
| 108 | { |
| 109 | return t + delta * 3600; |
| 110 | } |
| 111 | |
| 112 | static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) |
| 113 | { |
| 114 | return time_zone.fromDayNum(DayNum(d)) + delta * 3600; |
| 115 | } |
| 116 | }; |
| 117 | |
| 118 | struct AddDaysImpl : public AddOnDateTime64DefaultImpl<AddDaysImpl> |
| 119 | { |
| 120 | using Base = AddOnDateTime64DefaultImpl<AddDaysImpl>; |
| 121 | using Base::Base; |
| 122 | using Base::execute; |
| 123 | |
| 124 | static constexpr auto name = "addDays" ; |
| 125 | |
| 126 | // static inline UInt32 execute(UInt64 t, Int64 delta, const DateLUTImpl & time_zone) |
| 127 | // { |
| 128 | // // TODO (nemkov): LUT does not support out-of range date values for now. |
| 129 | // return time_zone.addDays(t, delta); |
| 130 | // } |
| 131 | |
| 132 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone) |
| 133 | { |
| 134 | return time_zone.addDays(t, delta); |
| 135 | } |
| 136 | |
| 137 | static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &) |
| 138 | { |
| 139 | return d + delta; |
| 140 | } |
| 141 | }; |
| 142 | |
| 143 | struct AddWeeksImpl : public AddOnDateTime64DefaultImpl<AddWeeksImpl> |
| 144 | { |
| 145 | using Base = AddOnDateTime64DefaultImpl<AddWeeksImpl>; |
| 146 | using Base::Base; |
| 147 | using Base::execute; |
| 148 | |
| 149 | static constexpr auto name = "addWeeks" ; |
| 150 | |
| 151 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone) |
| 152 | { |
| 153 | return time_zone.addWeeks(t, delta); |
| 154 | } |
| 155 | |
| 156 | static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &) |
| 157 | { |
| 158 | return d + delta * 7; |
| 159 | } |
| 160 | }; |
| 161 | |
| 162 | struct AddMonthsImpl : public AddOnDateTime64DefaultImpl<AddMonthsImpl> |
| 163 | { |
| 164 | using Base = AddOnDateTime64DefaultImpl<AddMonthsImpl>; |
| 165 | using Base::Base; |
| 166 | using Base::execute; |
| 167 | |
| 168 | static constexpr auto name = "addMonths" ; |
| 169 | |
| 170 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone) |
| 171 | { |
| 172 | return time_zone.addMonths(t, delta); |
| 173 | } |
| 174 | |
| 175 | static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) |
| 176 | { |
| 177 | return time_zone.addMonths(DayNum(d), delta); |
| 178 | } |
| 179 | }; |
| 180 | |
| 181 | struct AddQuartersImpl : public AddOnDateTime64DefaultImpl<AddQuartersImpl> |
| 182 | { |
| 183 | using Base = AddOnDateTime64DefaultImpl<AddQuartersImpl>; |
| 184 | using Base::Base; |
| 185 | using Base::execute; |
| 186 | |
| 187 | static constexpr auto name = "addQuarters" ; |
| 188 | |
| 189 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone) |
| 190 | { |
| 191 | return time_zone.addQuarters(t, delta); |
| 192 | } |
| 193 | |
| 194 | static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) |
| 195 | { |
| 196 | return time_zone.addQuarters(DayNum(d), delta); |
| 197 | } |
| 198 | }; |
| 199 | |
| 200 | struct AddYearsImpl : public AddOnDateTime64DefaultImpl<AddYearsImpl> |
| 201 | { |
| 202 | using Base = AddOnDateTime64DefaultImpl<AddYearsImpl>; |
| 203 | using Base::Base; |
| 204 | using Base::execute; |
| 205 | |
| 206 | static constexpr auto name = "addYears" ; |
| 207 | |
| 208 | static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone) |
| 209 | { |
| 210 | return time_zone.addYears(t, delta); |
| 211 | } |
| 212 | |
| 213 | static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) |
| 214 | { |
| 215 | return time_zone.addYears(DayNum(d), delta); |
| 216 | } |
| 217 | }; |
| 218 | |
| 219 | template <typename Transform> |
| 220 | struct SubtractIntervalImpl : public Transform |
| 221 | { |
| 222 | using Transform::Transform; |
| 223 | |
| 224 | template <typename T> |
| 225 | inline auto execute(T t, Int64 delta, const DateLUTImpl & time_zone) const |
| 226 | { |
| 227 | return Transform::execute(t, -delta, time_zone); |
| 228 | } |
| 229 | }; |
| 230 | |
| 231 | struct SubtractSecondsImpl : SubtractIntervalImpl<AddSecondsImpl> { static constexpr auto name = "subtractSeconds" ; }; |
| 232 | struct SubtractMinutesImpl : SubtractIntervalImpl<AddMinutesImpl> { static constexpr auto name = "subtractMinutes" ; }; |
| 233 | struct SubtractHoursImpl : SubtractIntervalImpl<AddHoursImpl> { static constexpr auto name = "subtractHours" ; }; |
| 234 | struct SubtractDaysImpl : SubtractIntervalImpl<AddDaysImpl> { static constexpr auto name = "subtractDays" ; }; |
| 235 | struct SubtractWeeksImpl : SubtractIntervalImpl<AddWeeksImpl> { static constexpr auto name = "subtractWeeks" ; }; |
| 236 | struct SubtractMonthsImpl : SubtractIntervalImpl<AddMonthsImpl> { static constexpr auto name = "subtractMonths" ; }; |
| 237 | struct SubtractQuartersImpl : SubtractIntervalImpl<AddQuartersImpl> { static constexpr auto name = "subtractQuarters" ; }; |
| 238 | struct SubtractYearsImpl : SubtractIntervalImpl<AddYearsImpl> { static constexpr auto name = "subtractYears" ; }; |
| 239 | |
| 240 | |
| 241 | template <typename Transform> |
| 242 | struct Adder |
| 243 | { |
| 244 | const Transform transform; |
| 245 | |
| 246 | explicit Adder(Transform transform_) |
| 247 | : transform(std::move(transform_)) |
| 248 | {} |
| 249 | |
| 250 | template <typename FromVectorType, typename ToVectorType> |
| 251 | void vector_vector(const FromVectorType & vec_from, ToVectorType & vec_to, const IColumn & delta, const DateLUTImpl & time_zone) const |
| 252 | { |
| 253 | size_t size = vec_from.size(); |
| 254 | vec_to.resize(size); |
| 255 | |
| 256 | for (size_t i = 0; i < size; ++i) |
| 257 | vec_to[i] = transform.execute(vec_from[i], delta.getInt(i), time_zone); |
| 258 | } |
| 259 | |
| 260 | template <typename FromVectorType, typename ToVectorType> |
| 261 | void vector_constant(const FromVectorType & vec_from, ToVectorType & vec_to, Int64 delta, const DateLUTImpl & time_zone) const |
| 262 | { |
| 263 | size_t size = vec_from.size(); |
| 264 | vec_to.resize(size); |
| 265 | |
| 266 | for (size_t i = 0; i < size; ++i) |
| 267 | vec_to[i] = transform.execute(vec_from[i], delta, time_zone); |
| 268 | } |
| 269 | |
| 270 | template <typename FromType, typename ToVectorType> |
| 271 | void constant_vector(const FromType & from, ToVectorType & vec_to, const IColumn & delta, const DateLUTImpl & time_zone) const |
| 272 | { |
| 273 | size_t size = delta.size(); |
| 274 | vec_to.resize(size); |
| 275 | |
| 276 | for (size_t i = 0; i < size; ++i) |
| 277 | vec_to[i] = transform.execute(from, delta.getInt(i), time_zone); |
| 278 | } |
| 279 | }; |
| 280 | |
| 281 | |
| 282 | template <typename FromDataType, typename ToDataType, typename Transform> |
| 283 | struct DateTimeAddIntervalImpl |
| 284 | { |
| 285 | static void execute(Transform transform, Block & block, const ColumnNumbers & arguments, size_t result) |
| 286 | { |
| 287 | using FromValueType = typename FromDataType::FieldType; |
| 288 | using FromColumnType = typename FromDataType::ColumnType; |
| 289 | using ToColumnType = typename ToDataType::ColumnType; |
| 290 | |
| 291 | auto op = Adder<Transform>{std::move(transform)}; |
| 292 | |
| 293 | const DateLUTImpl & time_zone = extractTimeZoneFromFunctionArguments(block, arguments, 2, 0); |
| 294 | |
| 295 | const ColumnPtr source_col = block.getByPosition(arguments[0]).column; |
| 296 | |
| 297 | auto result_col = block.getByPosition(result).type->createColumn(); |
| 298 | auto col_to = assert_cast<ToColumnType *>(result_col.get()); |
| 299 | |
| 300 | if (const auto * sources = checkAndGetColumn<FromColumnType>(source_col.get())) |
| 301 | { |
| 302 | const IColumn & delta_column = *block.getByPosition(arguments[1]).column; |
| 303 | |
| 304 | if (const auto * delta_const_column = typeid_cast<const ColumnConst *>(&delta_column)) |
| 305 | op.vector_constant(sources->getData(), col_to->getData(), delta_const_column->getField().get<Int64>(), time_zone); |
| 306 | else |
| 307 | op.vector_vector(sources->getData(), col_to->getData(), delta_column, time_zone); |
| 308 | } |
| 309 | else if (const auto * sources_const = checkAndGetColumnConst<FromColumnType>(source_col.get())) |
| 310 | { |
| 311 | op.constant_vector(sources_const->template getValue<FromValueType>(), col_to->getData(), *block.getByPosition(arguments[1]).column, time_zone); |
| 312 | } |
| 313 | else |
| 314 | { |
| 315 | throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() |
| 316 | + " of first argument of function " + Transform::name, |
| 317 | ErrorCodes::ILLEGAL_COLUMN); |
| 318 | } |
| 319 | |
| 320 | block.getByPosition(result).column = std::move(result_col); |
| 321 | } |
| 322 | }; |
| 323 | |
| 324 | namespace date_and_time_type_details |
| 325 | { |
| 326 | // Compile-time mapping of value (DataType::FieldType) types to corresponding DataType |
| 327 | template <typename FieldType> struct ResultDataTypeMap {}; |
| 328 | template <> struct ResultDataTypeMap<UInt16> { using ResultDataType = DataTypeDate; }; |
| 329 | template <> struct ResultDataTypeMap<Int16> { using ResultDataType = DataTypeDate; }; |
| 330 | template <> struct ResultDataTypeMap<UInt32> { using ResultDataType = DataTypeDateTime; }; |
| 331 | template <> struct ResultDataTypeMap<Int32> { using ResultDataType = DataTypeDateTime; }; |
| 332 | template <> struct ResultDataTypeMap<DateTime64> { using ResultDataType = DataTypeDateTime64; }; |
| 333 | } |
| 334 | |
| 335 | template <typename Transform> |
| 336 | class FunctionDateOrDateTimeAddInterval : public IFunction |
| 337 | { |
| 338 | public: |
| 339 | static constexpr auto name = Transform::name; |
| 340 | static FunctionPtr create(const Context &) { return std::make_shared<FunctionDateOrDateTimeAddInterval>(); } |
| 341 | |
| 342 | String getName() const override |
| 343 | { |
| 344 | return name; |
| 345 | } |
| 346 | |
| 347 | bool isVariadic() const override { return true; } |
| 348 | size_t getNumberOfArguments() const override { return 0; } |
| 349 | |
| 350 | DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override |
| 351 | { |
| 352 | if (arguments.size() != 2 && arguments.size() != 3) |
| 353 | throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " |
| 354 | + toString(arguments.size()) + ", should be 2 or 3" , |
| 355 | ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); |
| 356 | |
| 357 | if (!isNativeNumber(arguments[1].type)) |
| 358 | throw Exception("Second argument for function " + getName() + " (delta) must be number" , |
| 359 | ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); |
| 360 | |
| 361 | if (arguments.size() == 2) |
| 362 | { |
| 363 | if (!isDateOrDateTime(arguments[0].type)) |
| 364 | throw Exception{"Illegal type " + arguments[0].type->getName() + " of argument of function " + getName() + |
| 365 | ". Should be a date or a date with time" , ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; |
| 366 | } |
| 367 | else |
| 368 | { |
| 369 | if (!WhichDataType(arguments[0].type).isDateTime() |
| 370 | || !WhichDataType(arguments[2].type).isString()) |
| 371 | throw Exception( |
| 372 | "Function " + getName() + " supports 2 or 3 arguments. The 1st argument " |
| 373 | "must be of type Date or DateTime. The 2nd argument must be number. " |
| 374 | "The 3rd argument (optional) must be " |
| 375 | "a constant string with timezone name. The timezone argument is allowed " |
| 376 | "only when the 1st argument has the type DateTime" , |
| 377 | ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); |
| 378 | } |
| 379 | |
| 380 | switch (arguments[0].type->getTypeId()) |
| 381 | { |
| 382 | case TypeIndex::Date: |
| 383 | return resolveReturnType<DataTypeDate>(arguments); |
| 384 | case TypeIndex::DateTime: |
| 385 | return resolveReturnType<DataTypeDateTime>(arguments); |
| 386 | case TypeIndex::DateTime64: |
| 387 | return resolveReturnType<DataTypeDateTime64>(arguments); |
| 388 | default: |
| 389 | { |
| 390 | throw Exception("Invalid type of 1st argument of function " + getName() + ": " |
| 391 | + arguments[0].type->getName() + ", expected: Date, DateTime or DateTime64." , |
| 392 | ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); |
| 393 | } |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | // Helper templates to deduce return type based on argument type, since some overloads may promote or denote types, e.g. addSeconds(Date, 1) => DateTime |
| 398 | template <typename FieldType> |
| 399 | using TransformExecuteReturnType = decltype(std::declval<Transform>().execute(FieldType(), 0, std::declval<DateLUTImpl>())); |
| 400 | |
| 401 | // Deduces RETURN DataType from INTPUT DataType, based on return type of Transform{}.execute(INPUT_TYPE, UInt64, DateLUTImpl). |
| 402 | // e.g. for Transform-type that has execute()-overload with 'UInt16' input and 'UInt32' return, |
| 403 | // argument type is expected to be 'Date', and result type is deduced to be 'DateTime'. |
| 404 | template <typename FromDataType> |
| 405 | using TransformResultDataType = typename date_and_time_type_details::ResultDataTypeMap<TransformExecuteReturnType<typename FromDataType::FieldType>>::ResultDataType; |
| 406 | |
| 407 | template <typename FromDataType> |
| 408 | DataTypePtr resolveReturnType(const ColumnsWithTypeAndName & arguments) const |
| 409 | { |
| 410 | using ResultDataType = TransformResultDataType<FromDataType>; |
| 411 | |
| 412 | if constexpr (std::is_same_v<ResultDataType, DataTypeDate>) |
| 413 | return std::make_shared<DataTypeDate>(); |
| 414 | else if constexpr (std::is_same_v<ResultDataType, DataTypeDateTime>) |
| 415 | { |
| 416 | return std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 2, 0)); |
| 417 | } |
| 418 | else if constexpr (std::is_same_v<ResultDataType, DataTypeDateTime64>) |
| 419 | { |
| 420 | // TODO (vnemkov): what if there is an overload of Transform::execute() that returns DateTime64 from DateTime or Date ? |
| 421 | // Shall we use the default scale or one from optional argument ? |
| 422 | const auto & datetime64_type = assert_cast<const DataTypeDateTime64 &>(*arguments[0].type); |
| 423 | return std::make_shared<DataTypeDateTime64>(datetime64_type.getScale(), extractTimeZoneNameFromFunctionArguments(arguments, 2, 0)); |
| 424 | } |
| 425 | else |
| 426 | { |
| 427 | static_assert("Failed to resolve return type." ); |
| 428 | } |
| 429 | |
| 430 | //to make PVS and GCC happy. |
| 431 | return nullptr; |
| 432 | } |
| 433 | |
| 434 | bool useDefaultImplementationForConstants() const override { return true; } |
| 435 | ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {2}; } |
| 436 | |
| 437 | void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/) override |
| 438 | { |
| 439 | const IDataType * from_type = block.getByPosition(arguments[0]).type.get(); |
| 440 | WhichDataType which(from_type); |
| 441 | |
| 442 | if (which.isDate()) |
| 443 | { |
| 444 | DateTimeAddIntervalImpl<DataTypeDate, TransformResultDataType<DataTypeDate>, Transform>::execute(Transform{}, block, arguments, result); |
| 445 | } |
| 446 | else if (which.isDateTime()) |
| 447 | { |
| 448 | DateTimeAddIntervalImpl<DataTypeDateTime, TransformResultDataType<DataTypeDateTime>, Transform>::execute(Transform{}, block, arguments, result); |
| 449 | } |
| 450 | else if (const auto * datetime64_type = assert_cast<const DataTypeDateTime64 *>(from_type)) |
| 451 | { |
| 452 | DateTimeAddIntervalImpl<DataTypeDateTime64, TransformResultDataType<DataTypeDateTime64>, Transform>::execute(Transform{datetime64_type->getScale()}, block, arguments, result); |
| 453 | } |
| 454 | else |
| 455 | throw Exception("Illegal type " + block.getByPosition(arguments[0]).type->getName() + " of argument of function " + getName(), |
| 456 | ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); |
| 457 | } |
| 458 | }; |
| 459 | |
| 460 | } |
| 461 | |
| 462 | |