2015-10-12 07:05:54 +00:00
|
|
|
#pragma once
|
|
|
|
|
2020-08-19 11:52:17 +00:00
|
|
|
#include <Core/DecimalFunctions.h>
|
2017-04-01 09:19:00 +00:00
|
|
|
#include <Core/Field.h>
|
2018-04-09 13:52:39 +00:00
|
|
|
#include <common/demangle.h>
|
2020-11-04 16:48:36 +00:00
|
|
|
#include <Common/NaNUtils.h>
|
2015-10-12 07:05:54 +00:00
|
|
|
|
2017-01-06 17:41:19 +00:00
|
|
|
|
|
|
|
class SipHash;
|
2015-10-12 07:05:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
2016-01-11 21:46:36 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
extern const int CANNOT_CONVERT_TYPE;
|
2017-09-18 15:41:07 +00:00
|
|
|
extern const int LOGICAL_ERROR;
|
2020-08-19 11:52:17 +00:00
|
|
|
extern const int NOT_IMPLEMENTED;
|
2016-01-11 21:46:36 +00:00
|
|
|
}
|
|
|
|
|
2015-10-12 07:05:54 +00:00
|
|
|
|
2017-01-06 17:41:19 +00:00
|
|
|
/** StaticVisitor (and its descendants) - class with overloaded operator() for all types of fields.
|
|
|
|
* You could call visitor for field using function 'applyVisitor'.
|
|
|
|
* Also "binary visitor" is supported - its operator() takes two arguments.
|
2015-10-12 07:05:54 +00:00
|
|
|
*/
|
|
|
|
template <typename R = void>
|
|
|
|
struct StaticVisitor
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
using ResultType = R;
|
2015-10-12 07:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2017-01-06 17:41:19 +00:00
|
|
|
/// F is template parameter, to allow universal reference for field, that is useful for const and non-const values.
|
2015-10-12 07:05:54 +00:00
|
|
|
template <typename Visitor, typename F>
|
2019-12-10 13:40:45 +00:00
|
|
|
auto applyVisitor(Visitor && visitor, F && field)
|
2015-10-12 07:05:54 +00:00
|
|
|
{
|
2020-07-09 16:15:46 +00:00
|
|
|
return Field::dispatch(std::forward<Visitor>(visitor),
|
|
|
|
std::forward<F>(field));
|
2015-10-12 07:05:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Visitor, typename F1, typename F2>
|
2019-12-10 13:40:45 +00:00
|
|
|
auto applyVisitor(Visitor && visitor, F1 && field1, F2 && field2)
|
2015-10-12 07:05:54 +00:00
|
|
|
{
|
2020-07-09 16:15:46 +00:00
|
|
|
return Field::dispatch(
|
2020-07-10 05:37:43 +00:00
|
|
|
[&field2, &visitor](auto & field1_value)
|
2019-12-10 13:40:45 +00:00
|
|
|
{
|
2020-07-09 16:15:46 +00:00
|
|
|
return Field::dispatch(
|
|
|
|
[&field1_value, &visitor](auto & field2_value)
|
2019-12-10 13:40:45 +00:00
|
|
|
{
|
|
|
|
return visitor(field1_value, field2_value);
|
|
|
|
},
|
2020-07-09 16:15:46 +00:00
|
|
|
std::forward<F2>(field2));
|
2019-12-10 13:40:45 +00:00
|
|
|
},
|
2020-07-09 16:15:46 +00:00
|
|
|
std::forward<F1>(field1));
|
2015-10-12 07:05:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-01-06 17:41:19 +00:00
|
|
|
/** Prints Field as literal in SQL query */
|
|
|
|
class FieldVisitorToString : public StaticVisitor<String>
|
2015-10-12 07:05:54 +00:00
|
|
|
{
|
|
|
|
public:
|
2017-05-12 20:41:50 +00:00
|
|
|
String operator() (const Null & x) const;
|
|
|
|
String operator() (const UInt64 & x) const;
|
2018-07-09 21:25:56 +00:00
|
|
|
String operator() (const UInt128 & x) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
String operator() (const UInt256 & x) const;
|
2017-05-12 20:41:50 +00:00
|
|
|
String operator() (const Int64 & x) const;
|
2020-08-19 11:52:17 +00:00
|
|
|
String operator() (const Int128 & x) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
String operator() (const Int256 & x) const;
|
2021-05-03 22:46:51 +00:00
|
|
|
String operator() (const UUID & x) const;
|
2017-05-12 20:41:50 +00:00
|
|
|
String operator() (const Float64 & x) const;
|
|
|
|
String operator() (const String & x) const;
|
|
|
|
String operator() (const Array & x) const;
|
|
|
|
String operator() (const Tuple & x) const;
|
2020-10-10 06:49:03 +00:00
|
|
|
String operator() (const Map & x) const;
|
2018-08-23 19:11:31 +00:00
|
|
|
String operator() (const DecimalField<Decimal32> & x) const;
|
|
|
|
String operator() (const DecimalField<Decimal64> & x) const;
|
|
|
|
String operator() (const DecimalField<Decimal128> & x) const;
|
2020-08-19 11:52:17 +00:00
|
|
|
String operator() (const DecimalField<Decimal256> & x) const;
|
2019-02-11 11:19:56 +00:00
|
|
|
String operator() (const AggregateFunctionStateData & x) const;
|
2015-10-12 07:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2020-11-02 06:05:53 +00:00
|
|
|
class FieldVisitorWriteBinary
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void operator() (const Null & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const UInt64 & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const UInt128 & x, WriteBuffer & buf) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
void operator() (const UInt256 & x, WriteBuffer & buf) const;
|
2020-11-02 06:05:53 +00:00
|
|
|
void operator() (const Int64 & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const Int128 & x, WriteBuffer & buf) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
void operator() (const Int256 & x, WriteBuffer & buf) const;
|
2021-05-03 22:46:51 +00:00
|
|
|
void operator() (const UUID & x, WriteBuffer & buf) const;
|
2020-11-02 06:05:53 +00:00
|
|
|
void operator() (const Float64 & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const String & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const Array & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const Tuple & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const Map & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const DecimalField<Decimal32> & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const DecimalField<Decimal64> & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const DecimalField<Decimal128> & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const DecimalField<Decimal256> & x, WriteBuffer & buf) const;
|
|
|
|
void operator() (const AggregateFunctionStateData & x, WriteBuffer & buf) const;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2017-01-06 17:41:19 +00:00
|
|
|
/** Print readable and unique text dump of field type and value. */
|
|
|
|
class FieldVisitorDump : public StaticVisitor<String>
|
2015-10-12 07:05:54 +00:00
|
|
|
{
|
|
|
|
public:
|
2017-05-12 20:41:50 +00:00
|
|
|
String operator() (const Null & x) const;
|
|
|
|
String operator() (const UInt64 & x) const;
|
2018-07-09 21:25:56 +00:00
|
|
|
String operator() (const UInt128 & x) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
String operator() (const UInt256 & x) const;
|
2017-05-12 20:41:50 +00:00
|
|
|
String operator() (const Int64 & x) const;
|
2020-08-19 11:52:17 +00:00
|
|
|
String operator() (const Int128 & x) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
String operator() (const Int256 & x) const;
|
2021-05-03 22:46:51 +00:00
|
|
|
String operator() (const UUID & x) const;
|
2017-05-12 20:41:50 +00:00
|
|
|
String operator() (const Float64 & x) const;
|
|
|
|
String operator() (const String & x) const;
|
|
|
|
String operator() (const Array & x) const;
|
|
|
|
String operator() (const Tuple & x) const;
|
2020-10-10 06:49:03 +00:00
|
|
|
String operator() (const Map & x) const;
|
2018-08-23 19:11:31 +00:00
|
|
|
String operator() (const DecimalField<Decimal32> & x) const;
|
|
|
|
String operator() (const DecimalField<Decimal64> & x) const;
|
|
|
|
String operator() (const DecimalField<Decimal128> & x) const;
|
2020-08-19 11:52:17 +00:00
|
|
|
String operator() (const DecimalField<Decimal256> & x) const;
|
2019-02-11 11:19:56 +00:00
|
|
|
String operator() (const AggregateFunctionStateData & x) const;
|
2015-10-12 07:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2020-08-08 00:47:03 +00:00
|
|
|
/** Converts numeric value of any type to specified type. */
|
2015-10-12 07:05:54 +00:00
|
|
|
template <typename T>
|
|
|
|
class FieldVisitorConvertToNumber : public StaticVisitor<T>
|
|
|
|
{
|
|
|
|
public:
|
2017-12-01 17:49:12 +00:00
|
|
|
T operator() (const Null &) const
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-12-25 06:33:51 +00:00
|
|
|
throw Exception("Cannot convert NULL to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
2017-12-01 17:49:12 +00:00
|
|
|
T operator() (const String &) const
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-12-25 06:33:51 +00:00
|
|
|
throw Exception("Cannot convert String to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
2017-12-01 17:49:12 +00:00
|
|
|
T operator() (const Array &) const
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-12-25 06:33:51 +00:00
|
|
|
throw Exception("Cannot convert Array to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
2017-12-01 17:49:12 +00:00
|
|
|
T operator() (const Tuple &) const
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-12-25 06:33:51 +00:00
|
|
|
throw Exception("Cannot convert Tuple to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 06:49:03 +00:00
|
|
|
T operator() (const Map &) const
|
|
|
|
{
|
|
|
|
throw Exception("Cannot convert Map to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
|
|
|
}
|
|
|
|
|
2020-07-21 21:15:19 +00:00
|
|
|
T operator() (const UInt64 & x) const { return T(x); }
|
|
|
|
T operator() (const Int64 & x) const { return T(x); }
|
2020-08-19 11:52:17 +00:00
|
|
|
T operator() (const Int128 & x) const { return T(x); }
|
2021-05-03 22:46:51 +00:00
|
|
|
T operator() (const UUID & x) const { return T(x.toUnderType()); }
|
2020-08-19 11:52:17 +00:00
|
|
|
|
|
|
|
T operator() (const Float64 & x) const
|
|
|
|
{
|
2020-11-04 16:48:36 +00:00
|
|
|
if constexpr (!std::is_floating_point_v<T>)
|
2020-11-05 09:26:51 +00:00
|
|
|
{
|
2020-11-04 16:48:36 +00:00
|
|
|
if (!isFinite(x))
|
2020-11-05 09:26:51 +00:00
|
|
|
{
|
|
|
|
/// When converting to bool it's ok (non-zero converts to true, NaN including).
|
|
|
|
if (std::is_same_v<T, bool>)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/// Conversion of infinite values to integer is undefined.
|
2020-11-04 16:48:36 +00:00
|
|
|
throw Exception("Cannot convert infinite value to integer type", ErrorCodes::CANNOT_CONVERT_TYPE);
|
2020-11-05 09:26:51 +00:00
|
|
|
}
|
2021-04-10 21:52:42 +00:00
|
|
|
else if (x > std::numeric_limits<T>::max() || x < std::numeric_limits<T>::lowest())
|
|
|
|
{
|
|
|
|
throw Exception("Cannot convert out of range floating point value to integer type", ErrorCodes::CANNOT_CONVERT_TYPE);
|
|
|
|
}
|
2020-11-05 09:26:51 +00:00
|
|
|
}
|
2020-11-04 16:48:36 +00:00
|
|
|
|
2020-08-19 11:52:17 +00:00
|
|
|
if constexpr (std::is_same_v<Decimal256, T>)
|
2021-04-10 20:38:14 +00:00
|
|
|
{
|
2020-09-01 09:54:50 +00:00
|
|
|
return Int256(x);
|
2021-04-10 20:38:14 +00:00
|
|
|
}
|
2020-08-19 11:52:17 +00:00
|
|
|
else
|
2021-04-10 20:38:14 +00:00
|
|
|
{
|
2020-08-19 11:52:17 +00:00
|
|
|
return T(x);
|
2021-04-10 20:38:14 +00:00
|
|
|
}
|
2020-08-19 11:52:17 +00:00
|
|
|
}
|
2018-08-03 20:25:47 +00:00
|
|
|
|
|
|
|
T operator() (const UInt128 &) const
|
|
|
|
{
|
|
|
|
throw Exception("Cannot convert UInt128 to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
|
|
|
}
|
2018-08-23 19:11:31 +00:00
|
|
|
|
|
|
|
template <typename U>
|
|
|
|
T operator() (const DecimalField<U> & x) const
|
|
|
|
{
|
|
|
|
if constexpr (std::is_floating_point_v<T>)
|
2020-08-19 11:52:17 +00:00
|
|
|
return x.getValue(). template convertTo<T>() / x.getScaleMultiplier(). template convertTo<T>();
|
|
|
|
else if constexpr (std::is_same_v<T, UInt128>)
|
|
|
|
{
|
|
|
|
/// TODO: remove with old UInt128 type
|
|
|
|
if constexpr (sizeof(U) < 16)
|
|
|
|
{
|
|
|
|
return UInt128(0, (x.getValue() / x.getScaleMultiplier()).value);
|
|
|
|
}
|
|
|
|
else if constexpr (sizeof(U) == 16)
|
|
|
|
{
|
|
|
|
auto tmp = (x.getValue() / x.getScaleMultiplier()).value;
|
|
|
|
return UInt128(tmp >> 64, UInt64(tmp));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw Exception("No conversion to old UInt128 from " + demangle(typeid(U).name()), ErrorCodes::NOT_IMPLEMENTED);
|
|
|
|
}
|
2018-08-23 19:11:31 +00:00
|
|
|
else
|
2020-08-19 11:52:17 +00:00
|
|
|
return (x.getValue() / x.getScaleMultiplier()). template convertTo<T>();
|
2018-08-23 19:11:31 +00:00
|
|
|
}
|
2019-02-11 11:19:56 +00:00
|
|
|
|
|
|
|
T operator() (const AggregateFunctionStateData &) const
|
|
|
|
{
|
2019-02-11 13:11:52 +00:00
|
|
|
throw Exception("Cannot convert AggregateFunctionStateData to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE);
|
2019-02-11 11:19:56 +00:00
|
|
|
}
|
2020-08-19 11:52:17 +00:00
|
|
|
|
|
|
|
template <typename U, typename = std::enable_if_t<is_big_int_v<U>> >
|
|
|
|
T operator() (const U & x) const
|
|
|
|
{
|
|
|
|
if constexpr (IsDecimalNumber<T>)
|
|
|
|
return static_cast<T>(static_cast<typename T::NativeType>(x));
|
|
|
|
else if constexpr (std::is_same_v<T, UInt128>)
|
|
|
|
throw Exception("No conversion to old UInt128 from " + demangle(typeid(U).name()), ErrorCodes::NOT_IMPLEMENTED);
|
|
|
|
else
|
2021-01-26 19:04:03 +00:00
|
|
|
return static_cast<T>(x);
|
2020-08-19 11:52:17 +00:00
|
|
|
}
|
2015-10-12 07:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2017-01-06 17:41:19 +00:00
|
|
|
/** Updates SipHash by type and value of Field */
|
|
|
|
class FieldVisitorHash : public StaticVisitor<>
|
|
|
|
{
|
|
|
|
private:
|
2017-04-01 07:20:54 +00:00
|
|
|
SipHash & hash;
|
2017-01-06 17:41:19 +00:00
|
|
|
public:
|
2019-08-03 11:02:40 +00:00
|
|
|
FieldVisitorHash(SipHash & hash_);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-05-12 20:41:50 +00:00
|
|
|
void operator() (const Null & x) const;
|
|
|
|
void operator() (const UInt64 & x) const;
|
2018-07-09 21:25:56 +00:00
|
|
|
void operator() (const UInt128 & x) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
void operator() (const UInt256 & x) const;
|
2017-05-12 20:41:50 +00:00
|
|
|
void operator() (const Int64 & x) const;
|
2020-08-19 11:52:17 +00:00
|
|
|
void operator() (const Int128 & x) const;
|
2021-05-03 16:12:28 +00:00
|
|
|
void operator() (const Int256 & x) const;
|
2021-05-03 22:46:51 +00:00
|
|
|
void operator() (const UUID & x) const;
|
2017-05-12 20:41:50 +00:00
|
|
|
void operator() (const Float64 & x) const;
|
|
|
|
void operator() (const String & x) const;
|
|
|
|
void operator() (const Array & x) const;
|
2019-10-18 15:57:05 +00:00
|
|
|
void operator() (const Tuple & x) const;
|
2020-10-10 06:49:03 +00:00
|
|
|
void operator() (const Map & x) const;
|
2018-08-23 19:11:31 +00:00
|
|
|
void operator() (const DecimalField<Decimal32> & x) const;
|
|
|
|
void operator() (const DecimalField<Decimal64> & x) const;
|
|
|
|
void operator() (const DecimalField<Decimal128> & x) const;
|
2020-08-19 11:52:17 +00:00
|
|
|
void operator() (const DecimalField<Decimal256> & x) const;
|
2019-02-11 11:19:56 +00:00
|
|
|
void operator() (const AggregateFunctionStateData & x) const;
|
2017-01-06 17:41:19 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-08-23 19:11:31 +00:00
|
|
|
template <typename T> constexpr bool isDecimalField() { return false; }
|
|
|
|
template <> constexpr bool isDecimalField<DecimalField<Decimal32>>() { return true; }
|
|
|
|
template <> constexpr bool isDecimalField<DecimalField<Decimal64>>() { return true; }
|
|
|
|
template <> constexpr bool isDecimalField<DecimalField<Decimal128>>() { return true; }
|
2020-08-19 11:52:17 +00:00
|
|
|
template <> constexpr bool isDecimalField<DecimalField<Decimal256>>() { return true; }
|
2018-08-23 19:11:31 +00:00
|
|
|
|
|
|
|
|
2017-09-18 15:41:07 +00:00
|
|
|
/** Implements `+=` operation.
|
|
|
|
* Returns false if the result is zero.
|
|
|
|
*/
|
|
|
|
class FieldVisitorSum : public StaticVisitor<bool>
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
const Field & rhs;
|
|
|
|
public:
|
|
|
|
explicit FieldVisitorSum(const Field & rhs_) : rhs(rhs_) {}
|
|
|
|
|
2019-12-10 13:40:45 +00:00
|
|
|
// We can add all ints as unsigned regardless of their actual signedness.
|
|
|
|
bool operator() (Int64 & x) const { return this->operator()(reinterpret_cast<UInt64 &>(x)); }
|
|
|
|
bool operator() (UInt64 & x) const
|
|
|
|
{
|
|
|
|
x += rhs.reinterpret<UInt64>();
|
|
|
|
return x != 0;
|
|
|
|
}
|
|
|
|
|
2017-09-18 15:41:07 +00:00
|
|
|
bool operator() (Float64 & x) const { x += get<Float64>(rhs); return x != 0; }
|
|
|
|
|
2017-12-01 17:49:12 +00:00
|
|
|
bool operator() (Null &) const { throw Exception("Cannot sum Nulls", ErrorCodes::LOGICAL_ERROR); }
|
|
|
|
bool operator() (String &) const { throw Exception("Cannot sum Strings", ErrorCodes::LOGICAL_ERROR); }
|
|
|
|
bool operator() (Array &) const { throw Exception("Cannot sum Arrays", ErrorCodes::LOGICAL_ERROR); }
|
2019-10-18 15:57:05 +00:00
|
|
|
bool operator() (Tuple &) const { throw Exception("Cannot sum Tuples", ErrorCodes::LOGICAL_ERROR); }
|
2020-10-10 06:49:03 +00:00
|
|
|
bool operator() (Map &) const { throw Exception("Cannot sum Maps", ErrorCodes::LOGICAL_ERROR); }
|
2021-05-03 22:46:51 +00:00
|
|
|
bool operator() (UUID &) const { throw Exception("Cannot sum UUIDs", ErrorCodes::LOGICAL_ERROR); }
|
2019-02-11 11:19:56 +00:00
|
|
|
bool operator() (AggregateFunctionStateData &) const { throw Exception("Cannot sum AggregateFunctionStates", ErrorCodes::LOGICAL_ERROR); }
|
2018-08-23 19:11:31 +00:00
|
|
|
|
2020-08-19 11:52:17 +00:00
|
|
|
bool operator() (Int128 & x) const
|
|
|
|
{
|
|
|
|
x += get<Int128>(rhs);
|
|
|
|
return x != Int128(0);
|
|
|
|
}
|
|
|
|
|
2018-08-23 19:11:31 +00:00
|
|
|
template <typename T>
|
|
|
|
bool operator() (DecimalField<T> & x) const
|
|
|
|
{
|
|
|
|
x += get<DecimalField<T>>(rhs);
|
2020-08-19 11:52:17 +00:00
|
|
|
return x.getValue() != T(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T, typename = std::enable_if_t<is_big_int_v<T>> >
|
|
|
|
bool operator() (T & x) const
|
|
|
|
{
|
|
|
|
x += rhs.reinterpret<T>();
|
|
|
|
return x != T(0);
|
2018-08-23 19:11:31 +00:00
|
|
|
}
|
2017-09-18 15:41:07 +00:00
|
|
|
};
|
|
|
|
|
2015-10-12 07:05:54 +00:00
|
|
|
}
|