Skip to content

Commit

Permalink
Merge pull request #49759 from ClickHouse/cast-ipv6-to-ipv4
Browse files Browse the repository at this point in the history
Allow to cast IPv6 to IPv4 for address in proper mapping block
  • Loading branch information
yakov-olkhovskiy committed Jun 24, 2023
2 parents 9a75faa + 09806bc commit 71678f6
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 3 deletions.
85 changes: 85 additions & 0 deletions src/Functions/FunctionsCodingIP.h
Expand Up @@ -2,6 +2,7 @@

#include <type_traits>
#include <Common/formatIPv6.h>
#include <Common/IPv6ToBinary.h>

#include <Columns/ColumnFixedString.h>
#include <Columns/ColumnNullable.h>
Expand All @@ -16,6 +17,7 @@ namespace ErrorCodes
extern const int CANNOT_PARSE_IPV4;
extern const int CANNOT_PARSE_IPV6;
extern const int ILLEGAL_COLUMN;
extern const int CANNOT_CONVERT_TYPE;
}

enum class IPStringToNumExceptionMode : uint8_t
Expand Down Expand Up @@ -296,4 +298,87 @@ ColumnPtr convertToIPv4(ColumnPtr column, const PaddedPODArray<UInt8> * null_map
return col_res;
}

template <IPStringToNumExceptionMode exception_mode, typename ToColumn = ColumnIPv4>
ColumnPtr convertIPv6ToIPv4(ColumnPtr column, const PaddedPODArray<UInt8> * null_map = nullptr)
{
const ColumnIPv6 * column_ipv6 = checkAndGetColumn<ColumnIPv6>(column.get());

if (!column_ipv6)
throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal column type {}. Expected IPv6.", column->getName());

size_t column_size = column_ipv6->size();

ColumnUInt8::MutablePtr col_null_map_to;
ColumnUInt8::Container * vec_null_map_to = nullptr;

if constexpr (exception_mode == IPStringToNumExceptionMode::Null)
{
col_null_map_to = ColumnUInt8::create(column_size, false);
vec_null_map_to = &col_null_map_to->getData();
}

const uint8_t ip4_cidr[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00};

auto col_res = ToColumn::create();
auto & vec_res = col_res->getData();
vec_res.resize(column_size);
const auto & vec_src = column_ipv6->getData();

for (size_t i = 0; i < vec_res.size(); ++i)
{
const uint8_t * src = reinterpret_cast<const uint8_t *>(&vec_src[i]);
uint8_t * dst = reinterpret_cast<uint8_t *>(&vec_res[i]);

if (null_map && (*null_map)[i])
{
std::memset(dst, '\0', IPV4_BINARY_LENGTH);
if constexpr (exception_mode == IPStringToNumExceptionMode::Null)
(*vec_null_map_to)[i] = true;
continue;
}

if (!matchIPv6Subnet(src, ip4_cidr, 96))
{
if constexpr (exception_mode == IPStringToNumExceptionMode::Throw)
{
char addr[IPV6_MAX_TEXT_LENGTH + 1] {};
char * paddr = addr;
formatIPv6(src, paddr);

throw Exception(ErrorCodes::CANNOT_CONVERT_TYPE, "IPv6 {} in column {} is not in IPv4 mapping block", addr, column->getName());
}
else if constexpr (exception_mode == IPStringToNumExceptionMode::Default)
{
std::memset(dst, '\0', IPV4_BINARY_LENGTH);
}
else if constexpr (exception_mode == IPStringToNumExceptionMode::Null)
{
(*vec_null_map_to)[i] = true;
std::memset(dst, '\0', IPV4_BINARY_LENGTH);
}
continue;
}

if constexpr (std::endian::native == std::endian::little)
{
dst[0] = src[15];
dst[1] = src[14];
dst[2] = src[13];
dst[3] = src[12];
}
else
{
dst[0] = src[12];
dst[1] = src[13];
dst[2] = src[14];
dst[3] = src[15];
}
}

if constexpr (exception_mode == IPStringToNumExceptionMode::Null)
return ColumnNullable::create(std::move(col_res), std::move(col_null_map_to));

return col_res;
}

}
78 changes: 75 additions & 3 deletions src/Functions/FunctionsConversion.h
Expand Up @@ -57,6 +57,7 @@
#include <Interpreters/Context.h>
#include <Common/HashTable/HashMap.h>
#include <DataTypes/DataTypeIPv4andIPv6.h>
#include <Common/IPv6ToBinary.h>
#include <Core/Types.h>


Expand Down Expand Up @@ -210,13 +211,13 @@ struct ConvertImpl
}
else if constexpr (
(std::is_same_v<FromDataType, DataTypeIPv4> != std::is_same_v<ToDataType, DataTypeIPv4>)
&& !(is_any_of<FromDataType, DataTypeUInt8, DataTypeUInt16, DataTypeUInt32, DataTypeUInt64> || is_any_of<ToDataType, DataTypeUInt32, DataTypeUInt64, DataTypeUInt128, DataTypeUInt256>)
&& !(is_any_of<FromDataType, DataTypeUInt8, DataTypeUInt16, DataTypeUInt32, DataTypeUInt64, DataTypeIPv6> || is_any_of<ToDataType, DataTypeUInt32, DataTypeUInt64, DataTypeUInt128, DataTypeUInt256, DataTypeIPv6>)
)
{
throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Conversion from {} to {} is not supported",
TypeName<typename FromDataType::FieldType>, TypeName<typename ToDataType::FieldType>);
}
else if constexpr (std::is_same_v<FromDataType, DataTypeIPv6> != std::is_same_v<ToDataType, DataTypeIPv6>)
else if constexpr (std::is_same_v<FromDataType, DataTypeIPv6> != std::is_same_v<ToDataType, DataTypeIPv6> && !(std::is_same_v<ToDataType, DataTypeIPv4> || std::is_same_v<FromDataType, DataTypeIPv4>))
{
throw Exception(ErrorCodes::NOT_IMPLEMENTED,
"Conversion between numeric types and IPv6 is not supported. "
Expand Down Expand Up @@ -297,7 +298,58 @@ struct ConvertImpl
}
else
{
if constexpr (std::is_same_v<ToDataType, DataTypeIPv4> && std::is_same_v<FromDataType, DataTypeUInt64>)
if constexpr (std::is_same_v<ToDataType, DataTypeIPv4> && std::is_same_v<FromDataType, DataTypeIPv6>)
{
const uint8_t ip4_cidr[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00};
const uint8_t * src = reinterpret_cast<const uint8_t *>(&vec_from[i].toUnderType());
if (!matchIPv6Subnet(src, ip4_cidr, 96))
{
char addr[IPV6_MAX_TEXT_LENGTH + 1] {};
char * paddr = addr;
formatIPv6(src, paddr);

throw Exception(ErrorCodes::CANNOT_CONVERT_TYPE, "IPv6 {} in column {} is not in IPv4 mapping block", addr, named_from.column->getName());
}

uint8_t * dst = reinterpret_cast<uint8_t *>(&vec_to[i].toUnderType());
if constexpr (std::endian::native == std::endian::little)
{
dst[0] = src[15];
dst[1] = src[14];
dst[2] = src[13];
dst[3] = src[12];
}
else
{
dst[0] = src[12];
dst[1] = src[13];
dst[2] = src[14];
dst[3] = src[15];
}
}
else if constexpr (std::is_same_v<ToDataType, DataTypeIPv6> && std::is_same_v<FromDataType, DataTypeIPv4>)
{
const uint8_t * src = reinterpret_cast<const uint8_t *>(&vec_from[i].toUnderType());
uint8_t * dst = reinterpret_cast<uint8_t *>(&vec_to[i].toUnderType());
std::memset(dst, '\0', IPV6_BINARY_LENGTH);
dst[10] = dst[11] = 0xff;

if constexpr (std::endian::native == std::endian::little)
{
dst[12] = src[3];
dst[13] = src[2];
dst[14] = src[1];
dst[15] = src[0];
}
else
{
dst[12] = src[0];
dst[13] = src[1];
dst[14] = src[2];
dst[15] = src[3];
}
}
else if constexpr (std::is_same_v<ToDataType, DataTypeIPv4> && std::is_same_v<FromDataType, DataTypeUInt64>)
vec_to[i] = static_cast<ToFieldType>(static_cast<IPv4::UnderlyingType>(vec_from[i]));
else if constexpr (std::is_same_v<Name, NameToUnixTimestamp> && (std::is_same_v<FromDataType, DataTypeDate> || std::is_same_v<FromDataType, DataTypeDate32>))
vec_to[i] = static_cast<ToFieldType>(vec_from[i] * DATE_SECONDS_PER_DAY);
Expand Down Expand Up @@ -4010,6 +4062,26 @@ class FunctionCast final : public FunctionCastBase
return true;
}
}
else if constexpr (WhichDataType(FromDataType::type_id).isIPv6() && WhichDataType(ToDataType::type_id).isIPv4())
{
ret = [cast_ipv4_ipv6_default_on_conversion_error_value, requested_result_is_nullable](
ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, const ColumnNullable * column_nullable, size_t)
-> ColumnPtr
{
if (!WhichDataType(result_type).isIPv4())
throw Exception(
ErrorCodes::TYPE_MISMATCH, "Wrong result type {}. Expected IPv4", result_type->getName());

const auto * null_map = column_nullable ? &column_nullable->getNullMapData() : nullptr;
if (cast_ipv4_ipv6_default_on_conversion_error_value || requested_result_is_nullable)
return convertIPv6ToIPv4<IPStringToNumExceptionMode::Default>(arguments[0].column, null_map);
else
return convertIPv6ToIPv4<IPStringToNumExceptionMode::Throw>(arguments[0].column, null_map);
};

return true;
}

if constexpr (WhichDataType(ToDataType::type_id).isStringOrFixedString())
{
if (from_type->getCustomSerialization())
Expand Down
4 changes: 4 additions & 0 deletions tests/queries/0_stateless/02234_cast_to_ip_address.reference
Expand Up @@ -11,6 +11,10 @@ IPv4 functions
127.0.0.1
127.0.0.1
--
1.2.3.4
1.2.3.4
0.0.0.0
--
127.0.0.1
--
0
Expand Down
7 changes: 7 additions & 0 deletions tests/queries/0_stateless/02234_cast_to_ip_address.sql
Expand Up @@ -20,6 +20,13 @@ SELECT toIPv4OrNull('127.0.0.1');

SELECT '--';

SELECT toIPv4(toIPv6('::ffff:1.2.3.4'));
SELECT toIPv4(toIPv6('::afff:1.2.3.4')); --{serverError CANNOT_CONVERT_TYPE}
SELECT toIPv4OrDefault(toIPv6('::ffff:1.2.3.4'));
SELECT toIPv4OrDefault(toIPv6('::afff:1.2.3.4'));

SELECT '--';

SELECT cast('test' , 'IPv4'); --{serverError CANNOT_PARSE_IPV4}
SELECT cast('127.0.0.1' , 'IPv4');

Expand Down

0 comments on commit 71678f6

Please sign in to comment.