From e3b48d53244fe5184d153df311c0c18a495661b4 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 7 Mar 2013 12:01:41 +0000 Subject: [PATCH] clickhouse: added function hex [#CONV-6788]. --- dbms/include/DB/Functions/FunctionsCoding.h | 203 +++++++++++++++++++- dbms/src/Functions/FunctionFactory.cpp | 1 + 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/dbms/include/DB/Functions/FunctionsCoding.h b/dbms/include/DB/Functions/FunctionsCoding.h index 157886d74ef..2de0af34d18 100644 --- a/dbms/include/DB/Functions/FunctionsCoding.h +++ b/dbms/include/DB/Functions/FunctionsCoding.h @@ -18,11 +18,18 @@ namespace DB /** Функции кодирования: * - * IPv4NumToString(num) - см. ниже. - * IPv4StringToNum(string) - преобразуют, например, '192.168.0.1' в 3232235521 и наоборот. + * IPv4NumToString(num) - См. ниже. + * IPv4StringToNum(string) - Преобразуют, например, '192.168.0.1' в 3232235521 и наоборот. + * + * hex(x) - Возвращает hex; буквы заглавные; префиксов 0x или суффиксов h нет. + * Для чисел возвращает строку переменной длины - hex в "человеческом" (big endian) формате, с вырезанием старших нулей, но только по целым байтам. Для дат и дат-с-временем - как для чисел. + * Например, hex(257) = '0101'. */ +/// Включая нулевой символ в конце. +#define MAX_UINT_HEX_LENGTH 20 + class FunctionIPv4NumToString : public IFunction { public: @@ -211,5 +218,197 @@ public: ErrorCodes::ILLEGAL_COLUMN); } }; + + +class FunctionHex : public IFunction +{ +public: + /// Получить имя функции. + String getName() const + { + return "hex"; + } + + /// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. + DataTypePtr getReturnType(const DataTypes & arguments) const + { + if (arguments.size() != 1) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + Poco::NumberFormatter::format(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + if (!dynamic_cast(&*arguments[0]) && + !dynamic_cast(&*arguments[0]) && + !dynamic_cast(&*arguments[0]) && + !dynamic_cast(&*arguments[0]) && + !dynamic_cast(&*arguments[0]) && + !dynamic_cast(&*arguments[0]) && + !dynamic_cast(&*arguments[0])) + throw Exception("Illegal type " + arguments[0]->getName() + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return new DataTypeString; + } + + template + void executeOneUInt(T x, char *& out) + { + const char digit[17] = "0123456789ABCDEF"; + bool was_nonzero = false; + for (int offset = (sizeof(T) - 1) * 8; offset >= 0; offset -= 8) + { + UInt8 byte = static_cast((x >> offset) & 255); + + /// Ведущие нули. + if (byte == 0 && !was_nonzero && offset) + continue; + + *(out++) = digit[byte >> 4]; + *(out++) = digit[byte & 15]; + } + *(out++) = '\0'; + } + + template + bool tryExecuteUInt(const IColumn * col, ColumnPtr & col_res) + { + const ColumnVector * col_vec = dynamic_cast *>(col); + const ColumnConst * col_const = dynamic_cast *>(col); + + if (col_vec) + { + ColumnString * col_str = new ColumnString; + col_res = col_str; + ColumnString::DataVector_t & out_vec = col_str->getDataVector(); + ColumnString::Offsets_t & out_offsets = col_str->getOffsets(); + + const typename ColumnVector::Container_t & in_vec = col_vec->getData(); + + size_t size = in_vec.size(); + out_offsets.resize(size); + out_vec.resize(size * 3 + MAX_UINT_HEX_LENGTH); + + size_t pos = 0; + for (size_t i = 0; i < size; ++i) + { + /// Ручной экспоненциальный рост, чтобы не полагаться на линейное амортизированное время работы resize (его никто не гарантирует). + if (pos + MAX_UINT_HEX_LENGTH < out_vec.size()) + out_vec.resize(out_vec.size() * 2 + MAX_UINT_HEX_LENGTH); + + char * begin = reinterpret_cast(&out_vec[pos]); + char * end = begin; + executeOneUInt(in_vec[i], end); + + pos += end - begin; + out_offsets[i] = pos; + } + + out_vec.resize(pos); + + return true; + } + else if(col_const) + { + char buf[MAX_UINT_HEX_LENGTH]; + char * pos = buf; + executeOneUInt(col_const->getData(), pos); + + col_res = new ColumnConstString(col_const->size(), buf); + + return true; + } + else + { + return false; + } + } + + void executeOneString(const UInt8 * pos, const UInt8 * end, char *& out) + { + const char digit[17] = "0123456789ABCDEF"; + while (pos < end) + { + UInt8 byte = *(pos++); + *(out++) = digit[byte >> 4]; + *(out++) = digit[byte & 15]; + } + *(out++) = '\0'; + } + + bool tryExecuteString(const IColumn * col, ColumnPtr & col_res) + { + const ColumnString * col_str_in = dynamic_cast(col); + const ColumnConstString * col_const_in = dynamic_cast(col); + + if (col_str_in) + { + ColumnString * col_str = new ColumnString; + col_res = col_str; + ColumnString::DataVector_t & out_vec = col_str->getDataVector(); + ColumnString::Offsets_t & out_offsets = col_str->getOffsets(); + + const ColumnString::DataVector_t & in_vec = col_str_in->getDataVector(); + const ColumnString::Offsets_t & in_offsets = col_str_in->getOffsets(); + + size_t size = in_offsets.size(); + out_offsets.resize(size); + out_vec.resize(in_vec.size() * 2 - size); + + char * begin = reinterpret_cast(&out_vec[0]); + char * pos = begin; + size_t prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + size_t new_offset = in_offsets[i]; + + executeOneString(&in_vec[prev_offset], &in_vec[new_offset - 1], pos); + + out_offsets[i] = pos - begin; + + prev_offset = new_offset; + } + + if (out_offsets.back() != out_vec.size()) + throw Exception("Column size mismatch (internal logical error)", ErrorCodes::LOGICAL_ERROR); + + return true; + } + else if(col_const_in) + { + const std::string & src = col_const_in->getData(); + std::string res(src.size() * 2 + 1, '\0'); + char * pos = &res[0]; + const UInt8 * src_ptr = reinterpret_cast(src.c_str()); + executeOneString(src_ptr, src_ptr + src.size(), pos); + + col_res = new ColumnConstString(col_const_in->size(), res); + + return true; + } + else + { + return false; + } + } + + /// Выполнить функцию над блоком. + void execute(Block & block, const ColumnNumbers & arguments, size_t result) + { + const IColumn * column = &*block.getByPosition(arguments[0]).column; + ColumnPtr & res_column = block.getByPosition(result).column; + + if (tryExecuteUInt(column, res_column) || + tryExecuteUInt(column, res_column) || + tryExecuteUInt(column, res_column) || + tryExecuteUInt(column, res_column) || + tryExecuteString(column, res_column)) + return; + + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + } +}; } diff --git a/dbms/src/Functions/FunctionFactory.cpp b/dbms/src/Functions/FunctionFactory.cpp index 407d7e22f24..33f7de88c62 100644 --- a/dbms/src/Functions/FunctionFactory.cpp +++ b/dbms/src/Functions/FunctionFactory.cpp @@ -134,6 +134,7 @@ FunctionPtr FunctionFactory::get( else if (name == "IPv4NumToString") return new FunctionIPv4NumToString; else if (name == "IPv4StringToNum") return new FunctionIPv4StringToNum; + else if (name == "hex") return new FunctionHex; else if (name == "rand") return new FunctionRand; else if (name == "rand64") return new FunctionRand64;