ClickHouse
189 строк · 6.6 Кб
1#include "config.h"
2
3#if USE_ULID
4
5#include <Columns/ColumnFixedString.h>
6#include <Columns/ColumnString.h>
7#include <Columns/ColumnsDateTime.h>
8#include <DataTypes/DataTypeFixedString.h>
9#include <DataTypes/DataTypeString.h>
10#include <Functions/extractTimeZoneFromFunctionArguments.h>
11#include <Functions/FunctionFactory.h>
12#include <Functions/FunctionHelpers.h>
13#include <Functions/IFunction.h>
14#include <Interpreters/Context.h>
15
16#include <ulid.h>
17
18
19namespace DB
20{
21
22namespace ErrorCodes
23{
24extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
25extern const int BAD_ARGUMENTS;
26extern const int ILLEGAL_TYPE_OF_ARGUMENT;
27extern const int ILLEGAL_COLUMN;
28}
29
30class FunctionULIDStringToDateTime : public IFunction
31{
32public:
33static constexpr size_t ULID_LENGTH = 26;
34static constexpr UInt32 DATETIME_SCALE = 3;
35
36static constexpr auto name = "ULIDStringToDateTime";
37
38static FunctionPtr create(ContextPtr /*context*/)
39{
40return std::make_shared<FunctionULIDStringToDateTime>();
41}
42
43String getName() const override { return name; }
44
45bool isVariadic() const override { return true; }
46size_t getNumberOfArguments() const override { return 0; }
47
48bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; }
49
50DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
51{
52if (arguments.empty() || arguments.size() > 2)
53throw Exception(
54ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
55"Wrong number of arguments for function {}: should be 1 or 2",
56getName());
57
58const auto * arg_fixed_string = checkAndGetDataType<DataTypeFixedString>(arguments[0].type.get());
59const auto * arg_string = checkAndGetDataType<DataTypeString>(arguments[0].type.get());
60
61if (!arg_string && !(arg_fixed_string && arg_fixed_string->getN() == ULID_LENGTH))
62throw Exception(
63ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
64"Illegal type {} of argument of function {}. Must be String or FixedString(26).",
65arguments[0].type->getName(),
66getName());
67
68String timezone;
69if (arguments.size() == 2)
70{
71timezone = extractTimeZoneNameFromColumn(arguments[1].column.get(), arguments[1].name);
72
73if (timezone.empty())
74throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
75"Function {} supports a 2nd argument (optional) that must be a valid time zone",
76getName());
77}
78
79return std::make_shared<DataTypeDateTime64>(DATETIME_SCALE, timezone);
80}
81
82bool useDefaultImplementationForConstants() const override { return true; }
83
84ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override
85{
86auto col_res = ColumnDateTime64::create(input_rows_count, DATETIME_SCALE);
87auto & vec_res = col_res->getData();
88
89const ColumnPtr column = arguments[0].column;
90
91const auto * column_fixed_string = checkAndGetColumn<ColumnFixedString>(column.get());
92const auto * column_string = checkAndGetColumn<ColumnString>(column.get());
93
94if (column_fixed_string)
95{
96if (column_fixed_string->getN() != ULID_LENGTH)
97throw Exception(
98ErrorCodes::ILLEGAL_COLUMN,
99"Illegal column {} of argument of function {}, expected String or FixedString({})",
100arguments[0].name, getName(), ULID_LENGTH
101);
102
103const auto & vec_src = column_fixed_string->getChars();
104
105for (size_t i = 0; i < input_rows_count; ++i)
106{
107DateTime64 time = decode(vec_src.data() + i * ULID_LENGTH);
108vec_res[i] = time;
109}
110}
111else if (column_string)
112{
113const auto & vec_src = column_string->getChars();
114const auto & offsets_src = column_string->getOffsets();
115
116size_t src_offset = 0;
117
118for (size_t i = 0; i < input_rows_count; ++i)
119{
120DateTime64 time = 0;
121
122size_t string_size = offsets_src[i] - src_offset;
123if (string_size != ULID_LENGTH + 1)
124throw Exception(
125ErrorCodes::ILLEGAL_COLUMN,
126"Illegal column {} of argument of function {}, ULID must be {} characters long",
127arguments[0].name, getName(), ULID_LENGTH
128);
129
130time = decode(vec_src.data() + src_offset);
131
132src_offset += string_size;
133vec_res[i] = time;
134}
135}
136else
137throw Exception(
138ErrorCodes::ILLEGAL_COLUMN,
139"Illegal column {} of argument of function {}, expected String or FixedString({})",
140arguments[0].name, getName(), ULID_LENGTH
141);
142
143return col_res;
144}
145
146static DateTime64 decode(const UInt8 * data)
147{
148unsigned char buffer[16];
149int ret = ulid_decode(buffer, reinterpret_cast<const char *>(data));
150if (ret != 0)
151throw Exception(
152ErrorCodes::BAD_ARGUMENTS,
153"Cannot parse ULID {}",
154std::string_view(reinterpret_cast<const char *>(data), ULID_LENGTH)
155);
156
157/// Timestamp in milliseconds is the first 48 bits of the decoded ULID
158Int64 ms = 0;
159memcpy(reinterpret_cast<UInt8 *>(&ms) + 2, buffer, 6);
160
161# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
162ms = std::byteswap(ms);
163# endif
164
165return DecimalUtils::decimalFromComponents<DateTime64>(ms / intExp10(DATETIME_SCALE), ms % intExp10(DATETIME_SCALE), DATETIME_SCALE);
166}
167};
168
169
170REGISTER_FUNCTION(ULIDStringToDateTime)
171{
172factory.registerFunction<FunctionULIDStringToDateTime>(FunctionDocumentation
173{
174.description=R"(
175This function extracts the timestamp from a ULID and returns it as a DateTime64(3) typed value.
176The function expects the ULID to be provided as the first argument, which can be either a String or a FixedString(26) data type.
177An optional second argument can be passed to specify a timezone for the timestamp.
178)",
179.examples{
180{"ulid", "SELECT ULIDStringToDateTime(generateULID())", ""},
181{"timezone", "SELECT ULIDStringToDateTime(generateULID(), 'Asia/Istanbul')", ""}},
182.categories{"ULID"}
183},
184FunctionFactory::CaseSensitive);
185}
186
187}
188
189#endif
190