#include #include #include #include #include #include #include #include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int BAD_TYPE_OF_FIELD; extern const int EMPTY_DATA_PASSED; extern const int UNEXPECTED_AST_STRUCTURE; extern const int ARGUMENT_OUT_OF_BOUND; } template struct EnumName; template <> struct EnumName { static constexpr auto value = "Enum8"; }; template <> struct EnumName { static constexpr auto value = "Enum16"; }; template const char * DataTypeEnum::getFamilyName() const { return EnumName::value; } template std::string DataTypeEnum::generateName(const Values & values) { WriteBufferFromOwnString out; writeString(EnumName::value, out); writeChar('(', out); auto first = true; for (const auto & name_and_value : values) { if (!first) writeString(", ", out); first = false; writeQuotedString(name_and_value.first, out); writeString(" = ", out); writeText(name_and_value.second, out); } writeChar(')', out); return out.str(); } template DataTypeEnum::DataTypeEnum(const Values & values_) : EnumValues(values_) , type_name(generateName(this->getValues())) { } template Field DataTypeEnum::getDefault() const { return this->getValues().front().second; } template void DataTypeEnum::insertDefaultInto(IColumn & column) const { assert_cast(column).getData().push_back(this->getValues().front().second); } template bool DataTypeEnum::equals(const IDataType & rhs) const { return typeid(rhs) == typeid(*this) && type_name == static_cast &>(rhs).type_name; } template bool DataTypeEnum::textCanContainOnlyValidUTF8() const { for (const auto & elem : this->getValues()) { const char * pos = elem.first.data(); const char * end = pos + elem.first.size(); while (pos < end) { size_t length = UTF8::seqLength(*pos); if (pos + length > end) return false; if (Poco::UTF8Encoding::isLegal(reinterpret_cast(pos), static_cast(length))) pos += length; else return false; } } return true; } template static void checkOverflow(Int64 value) { if (!(std::numeric_limits::min() <= value && value <= std::numeric_limits::max())) throw Exception("DataTypeEnum: Unexpected value " + toString(value), ErrorCodes::BAD_TYPE_OF_FIELD); } template Field DataTypeEnum::castToName(const Field & value_or_name) const { if (value_or_name.getType() == Field::Types::String) { this->getValue(value_or_name.get()); /// Check correctness return value_or_name.get(); } else if (value_or_name.getType() == Field::Types::Int64) { Int64 value = value_or_name.get(); checkOverflow(value); return this->getNameForValue(static_cast(value)).toString(); } else throw Exception(ErrorCodes::BAD_TYPE_OF_FIELD, "DataTypeEnum: Unsupported type of field {}", value_or_name.getTypeName()); } template Field DataTypeEnum::castToValue(const Field & value_or_name) const { if (value_or_name.getType() == Field::Types::String) { return this->getValue(value_or_name.get()); } else if (value_or_name.getType() == Field::Types::Int64 || value_or_name.getType() == Field::Types::UInt64) { Int64 value = value_or_name.get(); checkOverflow(value); this->getNameForValue(static_cast(value)); /// Check correctness return value; } else throw Exception(ErrorCodes::BAD_TYPE_OF_FIELD, "DataTypeEnum: Unsupported type of field {}", value_or_name.getTypeName()); } template bool DataTypeEnum::contains(const IDataType & rhs) const { if (const auto * rhs_enum8 = typeid_cast(&rhs)) return this->containsAll(rhs_enum8->getValues()); if (const auto * rhs_enum16 = typeid_cast(&rhs)) return this->containsAll(rhs_enum16->getValues()); return false; } template SerializationPtr DataTypeEnum::doGetDefaultSerialization() const { return std::make_shared>(this->getValues()); } /// Explicit instantiations. template class DataTypeEnum; template class DataTypeEnum; static void checkASTStructure(const ASTPtr & child) { const auto * func = child->as(); if (!func || func->name != "equals" || func->parameters || !func->arguments || func->arguments->children.size() != 2) throw Exception("Elements of Enum data type must be of form: 'name' = number, where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); } static void autoAssignNumberForEnum(const ASTPtr & arguments) { Int64 literal_child_assign_num = 1; ASTs assign_number_child; assign_number_child.reserve(arguments->children.size()); bool is_first_child = true; size_t assign_count= 0; for (const ASTPtr & child : arguments->children) { if (child->as()) { assign_count += !is_first_child; ASTPtr func = makeASTFunction("equals", child, std::make_shared(literal_child_assign_num + assign_count)); assign_number_child.emplace_back(func); } else if (child->as()) { if (is_first_child) { checkASTStructure(child); const auto * func = child->as(); const auto * value_literal = func->arguments->children[1]->as(); if (!value_literal || (value_literal->value.getType() != Field::Types::UInt64 && value_literal->value.getType() != Field::Types::Int64)) throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); literal_child_assign_num = value_literal->value.get(); } assign_number_child.emplace_back(child); } else throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); is_first_child = false; } if (assign_count != 0 && assign_count != arguments->children.size() - 1) throw Exception("All elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); arguments->children = assign_number_child; } template static DataTypePtr createExact(const ASTPtr & arguments) { if (!arguments || arguments->children.empty()) throw Exception("Enum data type cannot be empty", ErrorCodes::EMPTY_DATA_PASSED); typename DataTypeEnum::Values values; values.reserve(arguments->children.size()); using FieldType = typename DataTypeEnum::FieldType; autoAssignNumberForEnum(arguments); /// Children must be functions 'equals' with string literal as left argument and numeric literal as right argument. for (const ASTPtr & child : arguments->children) { checkASTStructure(child); const auto * func = child->as(); const auto * name_literal = func->arguments->children[0]->as(); const auto * value_literal = func->arguments->children[1]->as(); if (!name_literal || !value_literal || name_literal->value.getType() != Field::Types::String || (value_literal->value.getType() != Field::Types::UInt64 && value_literal->value.getType() != Field::Types::Int64)) throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); const String & field_name = name_literal->value.get(); const auto value = value_literal->value.get(); if (value > std::numeric_limits::max() || value < std::numeric_limits::min()) throw Exception{"Value " + toString(value) + " for element '" + field_name + "' exceeds range of " + EnumName::value, ErrorCodes::ARGUMENT_OUT_OF_BOUND}; values.emplace_back(field_name, value); } return std::make_shared(values); } static DataTypePtr create(const ASTPtr & arguments) { if (!arguments || arguments->children.empty()) throw Exception("Enum data type cannot be empty", ErrorCodes::EMPTY_DATA_PASSED); autoAssignNumberForEnum(arguments); /// Children must be functions 'equals' with string literal as left argument and numeric literal as right argument. for (const ASTPtr & child : arguments->children) { checkASTStructure(child); const auto * func = child->as(); const auto * value_literal = func->arguments->children[1]->as(); if (!value_literal || (value_literal->value.getType() != Field::Types::UInt64 && value_literal->value.getType() != Field::Types::Int64)) throw Exception("Elements of Enum data type must be of form: 'name' = number or 'name', where name is string literal and number is an integer", ErrorCodes::UNEXPECTED_AST_STRUCTURE); Int64 value = value_literal->value.get(); if (value > std::numeric_limits::max() || value < std::numeric_limits::min()) return createExact(arguments); } return createExact(arguments); } void registerDataTypeEnum(DataTypeFactory & factory) { factory.registerDataType("Enum8", createExact>); factory.registerDataType("Enum16", createExact>); factory.registerDataType("Enum", create); /// MySQL factory.registerAlias("ENUM", "Enum", DataTypeFactory::CaseInsensitive); } }