Skip to content

Commit 63288eb

Browse files
authored
Merge pull request #81 from Enmk/artpaul_119
Fixes #66 to allow using absl::int128 as Int128
2 parents 6eee617 + 47c0fbb commit 63288eb

File tree

8 files changed

+439
-52
lines changed

8 files changed

+439
-52
lines changed

clickhouse/columns/date.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ void ColumnDateTime64::Append(const Int64& value) {
134134
//}
135135

136136
Int64 ColumnDateTime64::At(size_t n) const {
137-
return data_->At(n);
137+
// make sure to use Absl's Int128 conversion
138+
return static_cast<Int64>(data_->At(n));
138139
}
139140

140141
void ColumnDateTime64::Append(ColumnRef column) {

clickhouse/columns/decimal.cpp

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,102 @@
11
#include "decimal.h"
22

3+
namespace
4+
{
5+
using namespace clickhouse;
6+
7+
#ifdef ABSL_HAVE_INTRINSIC_INT128
8+
template <typename T>
9+
inline bool addOverflow(const Int128 & l, const T & r, Int128 * result)
10+
{
11+
__int128 res;
12+
const auto ret_value = __builtin_add_overflow(static_cast<__int128>(l), static_cast<__int128>(r), &res);
13+
14+
*result = res;
15+
return ret_value;
16+
}
17+
18+
template <typename T>
19+
inline bool mulOverflow(const Int128 & l, const T & r, Int128 * result)
20+
{
21+
__int128 res;
22+
const auto ret_value = __builtin_mul_overflow(static_cast<__int128>(l), static_cast<__int128>(r), &res);
23+
24+
*result = res;
25+
return ret_value;
26+
}
27+
28+
#else
29+
template <typename T>
30+
inline bool getSignBit(const T & v)
31+
{
32+
return std::signbit(v);
33+
}
34+
35+
inline bool getSignBit(const Int128 & v)
36+
{
37+
// static constexpr Int128 zero {};
38+
// return v < zero;
39+
40+
// Sign of the whole absl::int128 value is determined by sign of higher 64 bits.
41+
return absl::Int128High64(v) < 0;
42+
}
43+
44+
inline bool addOverflow(const Int128 & l, const Int128 & r, Int128 * result)
45+
{
46+
// *result = l + r;
47+
// const auto result_sign = getSignBit(*result);
48+
// return l_sign == r_sign && l_sign != result_sign;
49+
50+
// Based on code from:
51+
// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow#INT32C.Ensurethatoperationsonsignedintegersdonotresultinoverflow-CompliantSolution
52+
const auto r_positive = !getSignBit(r);
53+
54+
if ((r_positive && (l > (std::numeric_limits<Int128>::max() - r))) ||
55+
(!r_positive && (l < (std::numeric_limits<Int128>::min() - r)))) {
56+
return true;
57+
}
58+
*result = l + r;
59+
60+
return false;
61+
}
62+
63+
template <typename T>
64+
inline bool mulOverflow(const Int128 & l, const T & r, Int128 * result)
65+
{
66+
// Based on code from:
67+
// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow#INT32C.Ensurethatoperationsonsignedintegersdonotresultinoverflow-CompliantSolution.3
68+
const auto l_positive = !getSignBit(l);
69+
const auto r_positive = !getSignBit(r);
70+
71+
if (l_positive) {
72+
if (r_positive) {
73+
if (r != 0 && l > (std::numeric_limits<Int128>::max() / r)) {
74+
return true;
75+
}
76+
} else {
77+
if (l != 0 && r < (std::numeric_limits<Int128>::min() / l)) {
78+
return true;
79+
}
80+
}
81+
} else {
82+
if (r_positive) {
83+
if (r != 0 && l < (std::numeric_limits<Int128>::min() / r)) {
84+
return true;
85+
}
86+
} else {
87+
if (l != 0 && (r < (std::numeric_limits<Int128>::max() / l))) {
88+
return true;
89+
}
90+
}
91+
}
92+
93+
*result = l * r;
94+
return false;
95+
}
96+
#endif
97+
98+
}
99+
3100
namespace clickhouse {
4101

5102
ColumnDecimal::ColumnDecimal(size_t precision, size_t scale)
@@ -57,8 +154,8 @@ void ColumnDecimal::Append(const std::string& value) {
57154

58155
has_dot = true;
59156
} else if (*c >= '0' && *c <= '9') {
60-
if (__builtin_mul_overflow(int_value, 10, &int_value) ||
61-
__builtin_add_overflow(int_value, *c - '0', &int_value)) {
157+
if (mulOverflow(int_value, 10, &int_value) ||
158+
addOverflow(int_value, *c - '0', &int_value)) {
62159
throw std::runtime_error("value is too big for 128-bit integer");
63160
}
64161
} else {
@@ -72,7 +169,7 @@ void ColumnDecimal::Append(const std::string& value) {
72169
}
73170

74171
while (zeros) {
75-
if (__builtin_mul_overflow(int_value, 10, &int_value)) {
172+
if (mulOverflow(int_value, 10, &int_value)) {
76173
throw std::runtime_error("value is too big for 128-bit integer");
77174
}
78175
--zeros;

clickhouse/columns/itemview.cpp

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,69 @@
33
namespace clickhouse {
44

55
void ItemView::ValidateData(Type::Code type, DataType data) {
6-
static const int ANY = -1; // value can be of any size
7-
static const int ERR = -2; // value is not allowed inside ItemView
8-
static const int value_size_by_type[] = {
9-
0, /*Void*/
10-
1, /*Int8*/
11-
2, /*Int16*/
12-
4, /*Int32*/
13-
8, /*Int64*/
14-
1, /*UInt8*/
15-
2, /*UInt16*/
16-
4, /*UInt32*/
17-
8, /*UInt64*/
18-
4, /*Float32*/
19-
8, /*Float64*/
20-
ANY, /*String*/
21-
ANY, /*FixedString*/
22-
4, /*DateTime*/
23-
8, /*DateTime64*/
24-
2, /*Date*/
25-
ERR, /*Array*/
26-
ERR, /*Nullable*/
27-
ERR, /*Tuple*/
28-
1, /*Enum8*/
29-
2, /*Enum16*/
30-
16, /*UUID*/
31-
4, /*IPv4*/
32-
8, /*IPv6*/
33-
16, /*Int128*/
34-
16, /*Decimal*/
35-
4, /*Decimal32*/
36-
8, /*Decimal64*/
37-
16, /*Decimal128*/
38-
ERR, /*LowCardinality*/
39-
};
40-
41-
if (type >= sizeof(value_size_by_type)/sizeof(value_size_by_type[0]) || type < 0) {
42-
throw std::runtime_error("Unknon type code:" + std::to_string(static_cast<int>(type)));
43-
} else {
44-
const auto expected_size = value_size_by_type[type];
45-
if (expected_size == ERR) {
6+
int expected_size = 0;
7+
switch (type) {
8+
case Type::Code::Void:
9+
expected_size = 0;
10+
break;
11+
12+
case Type::Code::Int8:
13+
case Type::Code::UInt8:
14+
case Type::Code::Enum8:
15+
expected_size = 1;
16+
break;
17+
18+
case Type::Code::Int16:
19+
case Type::Code::UInt16:
20+
case Type::Code::Date:
21+
case Type::Code::Enum16:
22+
expected_size = 2;
23+
break;
24+
25+
case Type::Code::Int32:
26+
case Type::Code::UInt32:
27+
case Type::Code::Float32:
28+
case Type::Code::DateTime:
29+
case Type::Code::IPv4:
30+
case Type::Code::Decimal32:
31+
expected_size = 4;
32+
break;
33+
34+
case Type::Code::Int64:
35+
case Type::Code::UInt64:
36+
case Type::Code::Float64:
37+
case Type::Code::DateTime64:
38+
case Type::Code::IPv6:
39+
case Type::Code::Decimal64:
40+
expected_size = 8;
41+
break;
42+
43+
case Type::Code::String:
44+
case Type::Code::FixedString:
45+
// value can be of any size
46+
return;
47+
48+
case Type::Code::Array:
49+
case Type::Code::Nullable:
50+
case Type::Code::Tuple:
51+
case Type::Code::LowCardinality:
4652
throw std::runtime_error("Unsupported type in ItemView: " + std::to_string(static_cast<int>(type)));
47-
} else if (expected_size != ANY && expected_size != static_cast<int>(data.size())) {
48-
throw std::runtime_error("Value size mismatch for type "
49-
+ std::to_string(static_cast<int>(type)) + " expected: "
50-
+ std::to_string(expected_size) + ", got: " + std::to_string(data.size()));
51-
}
53+
54+
case Type::Code::UUID:
55+
case Type::Code::Int128:
56+
case Type::Code::Decimal:
57+
case Type::Code::Decimal128:
58+
expected_size = 16;
59+
break;
60+
61+
default:
62+
throw std::runtime_error("Unknon type code:" + std::to_string(static_cast<int>(type)));
63+
}
64+
65+
if (expected_size != static_cast<int>(data.size())) {
66+
throw std::runtime_error("Value size mismatch for type "
67+
+ std::to_string(static_cast<int>(type)) + " expected: "
68+
+ std::to_string(expected_size) + ", got: " + std::to_string(data.size()));
5269
}
5370
}
5471

clickhouse/columns/itemview.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ struct ItemView {
2626
inline auto ConvertToStorageValue(const T& t) {
2727
if constexpr (std::is_same_v<std::string_view, T> || std::is_same_v<std::string, T>) {
2828
return std::string_view{t};
29-
} else if constexpr (std::is_fundamental_v<T>) {
29+
} else if constexpr (std::is_fundamental_v<T> || std::is_same_v<Int128, std::decay_t<T>>) {
3030
return std::string_view{reinterpret_cast<const char*>(&t), sizeof(T)};
3131
} else {
32-
// will caue error at compile-time
32+
static_assert(!std::is_same_v<T, T>, "Unknown type, which can't be stored in ItemView");
3333
return;
3434
}
3535
}
@@ -55,7 +55,7 @@ struct ItemView {
5555
T get() const {
5656
if constexpr (std::is_same_v<std::string_view, T> || std::is_same_v<std::string, T>) {
5757
return data;
58-
} else if constexpr (std::is_fundamental_v<T>) {
58+
} else if constexpr (std::is_fundamental_v<T> || std::is_same_v<Int128, T>) {
5959
if (sizeof(T) == data.size()) {
6060
return *reinterpret_cast<const T*>(data.data());
6161
} else {

clickhouse/types/types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
namespace clickhouse {
1212

13+
using Int128 = absl::int128;
14+
using Int64 = int64_t;
15+
1316
using TypeRef = std::shared_ptr<class Type>;
1417

1518
class Type {

ut/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ ADD_EXECUTABLE (clickhouse-cpp-ut
33

44
client_ut.cpp
55
columns_ut.cpp
6+
itemview_ut.cpp
67
socket_ut.cpp
78
stream_ut.cpp
89
type_parser_ut.cpp

ut/columns_ut.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,62 @@ TEST(ColumnsCase, UUIDSlice) {
403403
ASSERT_EQ(sub->At(1), UInt128(0x3507213c178649f9llu, 0x9faf035d662f60aellu));
404404
}
405405

406+
TEST(ColumnsCase, Int128) {
407+
auto col = std::make_shared<ColumnInt128>(std::vector<Int128>{
408+
absl::MakeInt128(0xffffffffffffffffll, 0xffffffffffffffffll), // -1
409+
absl::MakeInt128(0, 0xffffffffffffffffll), // 2^64
410+
absl::MakeInt128(0xffffffffffffffffll, 0),
411+
absl::MakeInt128(0x8000000000000000ll, 0),
412+
Int128(0)
413+
});
414+
EXPECT_EQ(-1, col->At(0));
415+
EXPECT_EQ(0xffffffffffffffffll, col->At(1));
416+
EXPECT_EQ(0, col->At(4));
417+
}
418+
419+
TEST(ColumnsCase, ColumnDecimal128_from_string) {
420+
auto col = std::make_shared<ColumnDecimal>(38, 0);
421+
422+
const auto values = {
423+
Int128(0),
424+
Int128(-1),
425+
Int128(1),
426+
std::numeric_limits<Int128>::min() + 1,
427+
std::numeric_limits<Int128>::max(),
428+
};
429+
430+
for (size_t i = 0; i < values.size(); ++i)
431+
{
432+
const auto value = values.begin()[i];
433+
SCOPED_TRACE(::testing::Message() << "# index: " << i << " Int128 value: " << value);
434+
435+
{
436+
std::stringstream sstr;
437+
sstr << value;
438+
const auto string_value = sstr.str();
439+
440+
EXPECT_NO_THROW(col->Append(string_value));
441+
}
442+
443+
ASSERT_EQ(i + 1, col->Size());
444+
EXPECT_EQ(value, col->At(i));
445+
}
446+
}
447+
448+
TEST(ColumnsCase, ColumnDecimal128_from_string_overflow) {
449+
auto col = std::make_shared<ColumnDecimal>(38, 0);
450+
451+
// 2^128 overflows
452+
EXPECT_ANY_THROW(col->Append("340282366920938463463374607431768211456"));
453+
// special case for number bigger than 2^128, ending in zeroes.
454+
EXPECT_ANY_THROW(col->Append("400000000000000000000000000000000000000"));
455+
456+
#ifndef ABSL_HAVE_INTRINSIC_INT128
457+
// unfortunatelly std::numeric_limits<Int128>::min() overflows when there is no __int128 intrinsic type.
458+
EXPECT_ANY_THROW(col->Append("-170141183460469231731687303715884105728"));
459+
#endif
460+
}
461+
406462
TEST(ColumnsCase, ColumnLowCardinalityString_Append_and_Read) {
407463
const size_t items_count = 11;
408464
ColumnLowCardinalityT<ColumnString> col;

0 commit comments

Comments
 (0)