Skip to content

Commit 07fbf6d

Browse files
authored
Merge pull request #149 from Enmk/ipv4_ipv6_constructor_fix
Fixed ColumnIPv4 and ColumnIPv6 costruct from data
2 parents 166b825 + d9f6d0f commit 07fbf6d

File tree

7 files changed

+249
-61
lines changed

7 files changed

+249
-61
lines changed

clickhouse/columns/ip4.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ ColumnIPv4::ColumnIPv4()
1313

1414
ColumnIPv4::ColumnIPv4(ColumnRef data)
1515
: Column(Type::CreateIPv4())
16-
, data_(data->As<ColumnUInt32>())
16+
, data_(data ? data->As<ColumnUInt32>() : nullptr)
1717
{
18-
if (data_->Size() != 0) {
19-
throw std::runtime_error("number of entries must be even (32-bit numbers for each IPv4)");
20-
}
18+
if (!data_)
19+
throw std::runtime_error("Expecting ColumnUInt32, got " + (data ? data->GetType().GetName() : "null"));
2120
}
2221

2322
void ColumnIPv4::Append(const std::string& str) {
@@ -31,8 +30,7 @@ void ColumnIPv4::Append(uint32_t ip) {
3130
data_->Append(htonl(ip));
3231
}
3332

34-
void ColumnIPv4::Append(in_addr ip)
35-
{
33+
void ColumnIPv4::Append(in_addr ip) {
3634
data_->Append(htonl(ip.s_addr));
3735
}
3836

@@ -53,7 +51,18 @@ in_addr ColumnIPv4::operator [] (size_t n) const {
5351
}
5452

5553
std::string ColumnIPv4::AsString(size_t n) const {
56-
return inet_ntoa(this->At(n));
54+
const auto& addr = this->At(n);
55+
56+
char buf[INET_ADDRSTRLEN];
57+
const char* ip_str = inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN);
58+
59+
if (ip_str == nullptr) {
60+
throw std::system_error(
61+
std::error_code(errno, std::generic_category()),
62+
"Invalid IPv4 data");
63+
}
64+
65+
return ip_str;
5766
}
5867

5968
void ColumnIPv4::Append(ColumnRef column) {

clickhouse/columns/ip4.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ namespace clickhouse {
88

99
class ColumnIPv4 : public Column {
1010
public:
11+
using DataType = in_addr;
12+
using ValueType = in_addr;
13+
1114
ColumnIPv4();
15+
/** Takes ownership of the data, expects ColumnUInt32.
16+
* Modifying memory pointed by `data` from outside is UB.
17+
*
18+
* TODO: deprecate and remove as it is too dangerous and error-prone.
19+
*/
1220
explicit ColumnIPv4(ColumnRef data);
1321

1422
/// Appends one element to the column.

clickhouse/columns/ip6.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ ColumnIPv6::ColumnIPv6()
1616

1717
ColumnIPv6::ColumnIPv6(ColumnRef data)
1818
: Column(Type::CreateIPv6())
19-
, data_(data->As<ColumnFixedString>())
19+
, data_(data ? data->As<ColumnFixedString>() : nullptr)
2020
{
21-
if (data_->Size() != 0) {
22-
throw std::runtime_error("number of entries must be even (two 64-bit numbers for each IPv6)");
23-
}
21+
if (!data_ || data_->FixedSize() != sizeof(in6_addr))
22+
throw std::runtime_error("Expecting ColumnFixedString(16), got " + (data ? data->GetType().GetName() : "null"));
2423
}
2524

26-
void ColumnIPv6::Append(const std::string& ip) {
25+
void ColumnIPv6::Append(const std::string_view& str) {
2726
unsigned char buf[16];
28-
if (inet_pton(AF_INET6, ip.c_str(), buf) != 1) {
29-
throw std::runtime_error("invalid IPv6 format, ip: " + ip);
27+
if (inet_pton(AF_INET6, str.data(), buf) != 1) {
28+
throw std::runtime_error("invalid IPv6 format, ip: " + std::string(str));
3029
}
3130
data_->Append(std::string_view((const char*)buf, 16));
3231
}
@@ -43,13 +42,18 @@ void ColumnIPv6::Clear() {
4342
data_->Clear();
4443
}
4544

46-
std::string ColumnIPv6::AsString (size_t n) const{
47-
const auto& addr = data_->At(n);
45+
std::string ColumnIPv6::AsString (size_t n) const {
46+
const auto& addr = this->At(n);
47+
4848
char buf[INET6_ADDRSTRLEN];
49-
const char* ip_str = inet_ntop(AF_INET6, addr.data(), buf, INET6_ADDRSTRLEN);
49+
const char* ip_str = inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN);
50+
5051
if (ip_str == nullptr) {
51-
throw std::runtime_error("invalid IPv6 format: " + std::string(addr));
52+
throw std::system_error(
53+
std::error_code(errno, std::generic_category()),
54+
"Invalid IPv6 data");
5255
}
56+
5357
return ip_str;
5458
}
5559

clickhouse/columns/ip6.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ struct in6_addr;
77

88
namespace clickhouse {
99

10-
class ColumnIPv6 : public Column{
10+
class ColumnIPv6 : public Column {
1111
public:
12+
using DataType = in6_addr;
13+
using ValueType = in6_addr;
14+
1215
ColumnIPv6();
16+
/** Takes ownership of the data, expects ColumnFixedString.
17+
* Modifying memory pointed by `data` from outside is UB.
18+
*
19+
* TODO: deprecate and remove as it is too dangerous and error-prone.
20+
*/
1321
explicit ColumnIPv6(ColumnRef data);
1422

1523
/// Appends one element to the column.
16-
void Append(const std::string& str);
24+
void Append(const std::string_view& str);
1725

1826
void Append(const in6_addr* addr);
1927
void Append(const in6_addr& addr);

ut/columns_ut.cpp

Lines changed: 156 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,40 +20,44 @@
2020

2121
#include <clickhouse/base/socket.h> // for ipv4-ipv6 platform-specific stuff
2222

23-
bool operator==(const in_addr & left, const in_addr & right)
24-
{
23+
// only compare PODs of equal size this way
24+
template <typename L, typename R, typename
25+
= std::enable_if_t<sizeof(L) == sizeof(R) && std::conjunction_v<std::is_pod<L>, std::is_pod<R>>>>
26+
bool operator==(const L & left, const R& right) {
2527
return memcmp(&left, &right, sizeof(left)) == 0;
2628
}
2729

28-
bool operator==(const in6_addr & left, const in6_addr & right)
29-
{
30-
return memcmp(&left, &right, sizeof(left)) == 0;
30+
bool operator==(const in6_addr & left, const std::string_view & right) {
31+
return right.size() == sizeof(left) && memcmp(&left, right.data(), sizeof(left)) == 0;
3132
}
3233

33-
in_addr make_IPv4(uint32_t ip)
34-
{
34+
bool operator==(const std::string_view & left, const in6_addr & right) {
35+
return left.size() == sizeof(right) && memcmp(left.data(), &right, sizeof(right)) == 0;
36+
}
37+
38+
namespace {
39+
40+
using namespace clickhouse;
41+
using namespace std::literals::string_view_literals;
42+
43+
in_addr MakeIPv4(uint32_t ip) {
3544
static_assert(sizeof(in_addr) == sizeof(ip));
3645
in_addr result;
3746
memcpy(&result, &ip, sizeof(ip));
3847

3948
return result;
4049
}
4150

42-
std::ostream& operator<<(std::ostream& ostr, const in6_addr & addr)
43-
{
44-
char buf[INET6_ADDRSTRLEN];
45-
const char* ip_str = inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN);
46-
47-
if (!ip_str)
48-
return ostr << "<!INVALID IPv6 VALUE!>";
49-
50-
return ostr << ip_str;
51+
in6_addr MakeIPv6(uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3,
52+
uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7,
53+
uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11,
54+
uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15) {
55+
return in6_addr{{{v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15}}};
5156
}
5257

53-
namespace {
54-
55-
using namespace clickhouse;
56-
using namespace std::literals::string_view_literals;
58+
in6_addr MakeIPv6(uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15) {
59+
return in6_addr{{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, v10, v11, v12, v13, v14, v15}}};
60+
}
5761

5862
static std::vector<uint32_t> MakeNumbers() {
5963
return std::vector<uint32_t>
@@ -91,7 +95,7 @@ static const auto LOWCARDINALITY_STRING_FOOBAR_10_ITEMS_BINARY =
9195

9296
template <typename Generator>
9397
auto GenerateVector(size_t items, Generator && gen) {
94-
std::vector<std::result_of_t<Generator(size_t)>> result;
98+
std::vector<my_result_of_t<Generator(size_t)>> result;
9599
result.reserve(items);
96100
for (size_t i = 0; i < items; ++i) {
97101
result.push_back(std::move(gen(i)));
@@ -130,8 +134,7 @@ auto AlternateGenerators(Generator1 && gen1, Generator2 && gen2) {
130134
}
131135

132136
template <typename T>
133-
std::vector<T> ConcatSequences(std::vector<T> && vec1, std::vector<T> && vec2)
134-
{
137+
std::vector<T> ConcatSequences(std::vector<T> && vec1, std::vector<T> && vec2) {
135138
std::vector<T> result(vec1);
136139

137140
result.reserve(vec1.size() + vec2.size());
@@ -548,14 +551,14 @@ TEST(ColumnsCase, ColumnIPv4)
548551
col.Append("127.0.0.1");
549552
col.Append(3585395774);
550553
col.Append(0);
551-
const in_addr ip = make_IPv4(0x12345678);
554+
const in_addr ip = MakeIPv4(0x12345678);
552555
col.Append(ip);
553556

554557
ASSERT_EQ(5u, col.Size());
555-
EXPECT_EQ(make_IPv4(0xffffffff), col.At(0));
556-
EXPECT_EQ(make_IPv4(0x0100007f), col.At(1));
557-
EXPECT_EQ(make_IPv4(3585395774), col.At(2));
558-
EXPECT_EQ(make_IPv4(0), col.At(3));
558+
EXPECT_EQ(MakeIPv4(0xffffffff), col.At(0));
559+
EXPECT_EQ(MakeIPv4(0x0100007f), col.At(1));
560+
EXPECT_EQ(MakeIPv4(3585395774), col.At(2));
561+
EXPECT_EQ(MakeIPv4(0), col.At(3));
559562
EXPECT_EQ(ip, col.At(4));
560563

561564
EXPECT_EQ("255.255.255.255", col.AsString(0));
@@ -568,6 +571,63 @@ TEST(ColumnsCase, ColumnIPv4)
568571
EXPECT_EQ(0u, col.Size());
569572
}
570573

574+
TEST(ColumnsCase, ColumnIPv4_construct_from_data)
575+
{
576+
const auto vals = {
577+
MakeIPv4(0x12345678),
578+
MakeIPv4(0x0),
579+
MakeIPv4(0x0100007f)
580+
};
581+
582+
{
583+
// Column is usable after being initialized with empty data column
584+
auto col = ColumnIPv4(std::make_shared<ColumnUInt32>());
585+
EXPECT_EQ(0u, col.Size());
586+
587+
// Make sure that `Append` and `At`/`[]` work properly
588+
size_t i = 0;
589+
for (const auto & v : vals) {
590+
col.Append(v);
591+
EXPECT_EQ(v, col[col.Size() - 1]) << "At pos " << i;
592+
EXPECT_EQ(v, col.At(col.Size() - 1)) << "At pos " << i;
593+
++i;
594+
}
595+
596+
EXPECT_EQ(vals.size(), col.Size());
597+
}
598+
599+
{
600+
// Column reports values from data column exactly, and also can be modified afterwards.
601+
const auto values = std::vector<uint32_t>{std::numeric_limits<uint32_t>::min(), 123, 456, 789101112, std::numeric_limits<uint32_t>::max()};
602+
auto col = ColumnIPv4(std::make_shared<ColumnUInt32>(values));
603+
604+
EXPECT_EQ(values.size(), col.Size());
605+
for (size_t i = 0; i < values.size(); ++i) {
606+
EXPECT_EQ(ntohl(values[i]), col[i]) << " At pos: " << i;
607+
}
608+
609+
// Make sure that `Append` and `At`/`[]` work properly
610+
size_t i = 0;
611+
for (const auto & v : vals) {
612+
col.Append(v);
613+
EXPECT_EQ(v, col[col.Size() - 1]) << "At pos " << i;
614+
EXPECT_EQ(v, col.At(col.Size() - 1)) << "At pos " << i;
615+
++i;
616+
}
617+
618+
EXPECT_EQ(values.size() + vals.size(), col.Size());
619+
}
620+
621+
EXPECT_ANY_THROW(ColumnIPv4(nullptr));
622+
EXPECT_ANY_THROW(ColumnIPv4(ColumnRef(std::make_shared<ColumnInt8>())));
623+
EXPECT_ANY_THROW(ColumnIPv4(ColumnRef(std::make_shared<ColumnInt32>())));
624+
625+
EXPECT_ANY_THROW(ColumnIPv4(ColumnRef(std::make_shared<ColumnUInt8>())));
626+
627+
EXPECT_ANY_THROW(ColumnIPv4(ColumnRef(std::make_shared<ColumnInt128>())));
628+
EXPECT_ANY_THROW(ColumnIPv4(ColumnRef(std::make_shared<ColumnString>())));
629+
}
630+
571631
TEST(ColumnsCase, ColumnIPv6)
572632
{
573633
// TODO: split into proper method-level unit-tests
@@ -576,22 +636,16 @@ TEST(ColumnsCase, ColumnIPv6)
576636
col.Append("::");
577637
col.Append("::FFFF:204.152.189.116");
578638

579-
const auto ipv6 = in6_addr{{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}};
639+
const auto ipv6 = MakeIPv6(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
580640
col.Append(ipv6);
581641

582642
ASSERT_EQ(4u, col.Size());
583-
const auto first_val = in6_addr{{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}};
584-
EXPECT_EQ(first_val, col.At(0));
585-
586-
const auto second_val = in6_addr{{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}};
587-
EXPECT_EQ(second_val, col.At(1));
588-
589-
const auto third_val = in6_addr{{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 204, 152, 189, 116}}};
590-
EXPECT_EQ(third_val, col.At(2));
643+
EXPECT_EQ(MakeIPv6(0, 0, 0, 0, 0, 1), col.At(0));
644+
EXPECT_EQ(MakeIPv6(0, 0, 0, 0, 0, 0), col.At(1));
645+
EXPECT_EQ(MakeIPv6(0xff, 0xff, 204, 152, 189, 116), col.At(2));
591646

592647
EXPECT_EQ(ipv6, col.At(3));
593648

594-
595649
EXPECT_EQ("::1", col.AsString(0));
596650
EXPECT_EQ("::", col.AsString(1));
597651
EXPECT_EQ("::ffff:204.152.189.116", col.AsString(2));
@@ -601,6 +655,70 @@ TEST(ColumnsCase, ColumnIPv6)
601655
EXPECT_EQ(0u, col.Size());
602656
}
603657

658+
TEST(ColumnsCase, ColumnIPv6_construct_from_data)
659+
{
660+
const auto vals = {
661+
MakeIPv6(0xff, 0xff, 204, 152, 189, 116),
662+
MakeIPv6(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
663+
};
664+
665+
{
666+
// Column is usable after being initialized with empty data column
667+
auto col = ColumnIPv6(std::make_shared<ColumnFixedString>(16));
668+
EXPECT_EQ(0u, col.Size());
669+
670+
// Make sure that `Append` and `At`/`[]` work properly
671+
size_t i = 0;
672+
for (const auto & v : vals) {
673+
col.Append(v);
674+
EXPECT_EQ(v, col[col.Size() - 1]) << "At pos " << i;
675+
EXPECT_EQ(v, col.At(col.Size() - 1)) << "At pos " << i;
676+
++i;
677+
}
678+
679+
EXPECT_EQ(vals.size(), col.Size());
680+
}
681+
682+
{
683+
// Column reports values from data column exactly, and also can be modified afterwards.
684+
using namespace std::literals;
685+
const auto values = std::vector<std::string_view>{
686+
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"sv,
687+
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"sv,
688+
"\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"sv};
689+
auto col = ColumnIPv6(std::make_shared<ColumnFixedString>(16, values));
690+
691+
EXPECT_EQ(values.size(), col.Size());
692+
for (size_t i = 0; i < values.size(); ++i) {
693+
EXPECT_EQ(values[i], col[i]) << " At pos: " << i;
694+
}
695+
696+
// Make sure that `Append` and `At`/`[]` work properly
697+
size_t i = 0;
698+
for (const auto & v : vals) {
699+
col.Append(v);
700+
EXPECT_EQ(v, col[col.Size() - 1]) << "At pos " << i;
701+
EXPECT_EQ(v, col.At(col.Size() - 1)) << "At pos " << i;
702+
++i;
703+
}
704+
705+
EXPECT_EQ(values.size() + vals.size(), col.Size());
706+
}
707+
708+
// Make sure that column can't be constructed with wrong data columns (wrong size/wrong type or null)
709+
EXPECT_ANY_THROW(ColumnIPv4(nullptr));
710+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnFixedString>(15))));
711+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnFixedString>(17))));
712+
713+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnInt8>())));
714+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnInt32>())));
715+
716+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnUInt8>())));
717+
718+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnInt128>())));
719+
EXPECT_ANY_THROW(ColumnIPv6(ColumnRef(std::make_shared<ColumnString>())));
720+
}
721+
604722
TEST(ColumnsCase, ColumnDecimal128_from_string) {
605723
auto col = std::make_shared<ColumnDecimal>(38, 0);
606724

0 commit comments

Comments
 (0)