From 414af624c78280607f7b39f3fd6a11db8d1222ff Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Thu, 10 Apr 2014 21:28:06 +0400 Subject: [PATCH] functions: Fixed. Function array now accept arguments of different types. Also added simple test [METR-10799] --- dbms/include/DB/Functions/FunctionsArray.h | 131 +++++++++++++++++- ...00035_function_array_return_type.reference | 5 + .../00035_function_array_return_type.sql | 5 + 3 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 dbms/tests/queries/0_stateless/00035_function_array_return_type.reference create mode 100644 dbms/tests/queries/0_stateless/00035_function_array_return_type.sql diff --git a/dbms/include/DB/Functions/FunctionsArray.h b/dbms/include/DB/Functions/FunctionsArray.h index f51f47bbd33..9edc6f5ca94 100644 --- a/dbms/include/DB/Functions/FunctionsArray.h +++ b/dbms/include/DB/Functions/FunctionsArray.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include @@ -30,26 +32,128 @@ namespace DB * Например: arrayEnumerateUniq([10, 20, 10, 30]) = [1, 1, 2, 1] */ + + + class FunctionArray : public IFunction { -public: +private: /// Получить имя функции. String getName() const { return "array"; } + template + bool checkRightType(DataTypePtr left, DataTypePtr right, DataTypePtr & type_res) const + { + if (dynamic_cast(&*right)) + { + typedef typename NumberTraits::ResultOfIf::Type ResultType; + type_res = DataTypeFromFieldTypeOrError::getDataType(); + if (!type_res) + throw Exception("Arguments of function " + getName() + " are not upscalable to a common type without loss of precision.", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + return true; + } + return false; + } + + template + bool checkLeftType(DataTypePtr left, DataTypePtr right, DataTypePtr & type_res) const + { + if (dynamic_cast(&*left)) + { + if ( checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res) + || checkRightType(left, right, type_res)) + return true; + else + throw Exception("Illegal type " + right->getName() + " as argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + return false; + } + + template + bool tryAddField(DataTypePtr type_res, const Field & f, Array & arr) const + { + if (dynamic_cast(&*type_res)) + { + arr.push_back(apply_visitor(FieldVisitorConvertToNumber(), f)); + return true; + } + return false; + } + + bool addField(DataTypePtr type_res, const Field & f, Array & arr) const + { + /// Иначе необходимо + if ( tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) ) + return true; + else + throw Exception("Illegal result type " + type_res->getName() + " of function " + getName(), + ErrorCodes::LOGICAL_ERROR); + } + + DataTypePtr getLeastCommonType(DataTypePtr left, DataTypePtr right) const + { + DataTypePtr type_res; + if (!( checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res) + || checkLeftType(left, right, type_res))) + throw Exception("Internal error: unexpected type " + left->getName() + " as argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + return type_res; + } + +public: /// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. DataTypePtr getReturnType(const DataTypes & arguments) const { if (arguments.empty()) throw Exception("Function array requires at least one argument.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - for (size_t i = 1, size = arguments.size(); i < size; ++i) - if (arguments[i]->getName() != arguments[0]->getName()) - throw Exception("Arguments for function array must have same type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + DataTypePtr result_type = arguments[0]; - return new DataTypeArray(arguments[0]); + if (result_type->behavesAsNumber()) + { + /// Если тип числовой, пробуем выделить наименьший общий тип + for (size_t i = 1, size = arguments.size(); i < size; ++i) + result_type = getLeastCommonType(result_type, arguments[i]); + } + else + { + /// Иначе все аргументы должны быть одинаковыми + for (size_t i = 1, size = arguments.size(); i < size; ++i) + if (arguments[i]->getName() != arguments[0]->getName()) + throw Exception("Arguments for function array must have same type or behave as number.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + + return new DataTypeArray(result_type); } /// Выполнить функцию над блоком. @@ -60,11 +164,24 @@ public: if (!block.getByPosition(arguments[i]).column->isConst()) throw Exception("Arguments for function array must be constant.", ErrorCodes::ILLEGAL_COLUMN); + DataTypePtr result_type = block.getByPosition(arguments[0]).type; + if (result_type->behavesAsNumber()) + { + /// Если тип числовой, вычисляем наименьший общий тип + for (size_t i = 1, size = arguments.size(); i < size; ++i) + result_type = getLeastCommonType(result_type, block.getByPosition(arguments[i]).type); + } + Array arr; for (size_t i = 0, size = arguments.size(); i < size; ++i) - arr.push_back((*block.getByPosition(arguments[i]).column)[0]); + if (block.getByPosition(arguments[i]).type->getName() == result_type->getName()) + /// Если элемент такого же типа как результат, просто добавляем его в ответ + arr.push_back((*block.getByPosition(arguments[i]).column)[0]); + else + /// Иначе необходимо привести его к типу результата + addField(result_type, (*block.getByPosition(arguments[i]).column)[0], arr); - block.getByPosition(result).column = new ColumnConstArray(block.getByPosition(arguments[0]).column->size(), arr, new DataTypeArray(block.getByPosition(arguments[0]).type)); + block.getByPosition(result).column = new ColumnConstArray(block.getByPosition(arguments[0]).column->size(), arr, new DataTypeArray(result_type)); } }; diff --git a/dbms/tests/queries/0_stateless/00035_function_array_return_type.reference b/dbms/tests/queries/0_stateless/00035_function_array_return_type.reference new file mode 100644 index 00000000000..6e7a54cc778 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00035_function_array_return_type.reference @@ -0,0 +1,5 @@ +[1] +[1,255] +[1,256] +[-1,-2.5,15,699] +['q','w','ert','y'] diff --git a/dbms/tests/queries/0_stateless/00035_function_array_return_type.sql b/dbms/tests/queries/0_stateless/00035_function_array_return_type.sql new file mode 100644 index 00000000000..f698f65709a --- /dev/null +++ b/dbms/tests/queries/0_stateless/00035_function_array_return_type.sql @@ -0,0 +1,5 @@ +SELECT [1]; +SELECT [1, 255]; +SELECT [1, 256]; +SELECT [-1, -2.5, 15, 699]; +SELECT ['q', 'w', 'ert', 'y'];