be/src/util/variant/variant_scalar_encoding.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "util/variant/variant_scalar_encoding.h" |
19 | | |
20 | | #include <bit> |
21 | | #include <cstring> |
22 | | #include <limits> |
23 | | |
24 | | #include "common/exception.h" |
25 | | #include "util/utf8_check.h" |
26 | | #include "util/variant/variant_encoding.h" |
27 | | |
28 | | namespace doris { |
29 | | namespace { |
30 | | |
31 | | constexpr unsigned __int128 DECIMAL4_MAX = 999'999'999; |
32 | | constexpr unsigned __int128 DECIMAL8_MAX = 999'999'999'999'999'999; |
33 | | |
34 | 0 | constexpr unsigned __int128 max_decimal38() { |
35 | 0 | unsigned __int128 value = 1; |
36 | 0 | for (uint8_t digit = 0; digit < 38; ++digit) { |
37 | 0 | value *= 10; |
38 | 0 | } |
39 | 0 | return value - 1; |
40 | 0 | } |
41 | | |
42 | | constexpr unsigned __int128 MAX_DECIMAL38 = max_decimal38(); |
43 | | |
44 | 111 | unsigned __int128 magnitude(__int128 value) { |
45 | 111 | const auto unsigned_value = static_cast<unsigned __int128>(value); |
46 | 111 | return value < 0 ? ~unsigned_value + 1 : unsigned_value; |
47 | 111 | } |
48 | | |
49 | 1.08M | constexpr uint8_t primitive_header(VariantPrimitiveId id) noexcept { |
50 | 1.08M | return static_cast<uint8_t>(static_cast<uint8_t>(id) << VARIANT_VALUE_HEADER_SHIFT); |
51 | 1.08M | } |
52 | | |
53 | 61 | constexpr VariantPrimitiveId boolean_primitive_id(bool value) noexcept { |
54 | 61 | return value ? VariantPrimitiveId::TRUE_VALUE : VariantPrimitiveId::FALSE_VALUE; |
55 | 61 | } |
56 | | |
57 | | struct IntegerEncoding { |
58 | | VariantPrimitiveId id; |
59 | | uint8_t width; |
60 | | }; |
61 | | |
62 | 13.9k | IntegerEncoding resolve_integer_encoding(int64_t value, uint8_t requested_width) { |
63 | 13.9k | uint8_t width = requested_width; |
64 | 13.9k | if (width == 0) { |
65 | 13.9k | if (value >= std::numeric_limits<int8_t>::min() && |
66 | 13.9k | value <= std::numeric_limits<int8_t>::max()) { |
67 | 701 | width = 1; |
68 | 13.2k | } else if (value >= std::numeric_limits<int16_t>::min() && |
69 | 13.2k | value <= std::numeric_limits<int16_t>::max()) { |
70 | 920 | width = 2; |
71 | 12.3k | } else if (value >= std::numeric_limits<int32_t>::min() && |
72 | 12.3k | value <= std::numeric_limits<int32_t>::max()) { |
73 | 12.3k | width = 4; |
74 | 12.3k | } else { |
75 | 16 | width = 8; |
76 | 16 | } |
77 | 13.9k | } |
78 | | |
79 | 13.9k | VariantPrimitiveId id = VariantPrimitiveId::INT64; |
80 | 13.9k | bool fits = true; |
81 | 13.9k | if (width == 1) { |
82 | 704 | id = VariantPrimitiveId::INT8; |
83 | 704 | fits = value >= std::numeric_limits<int8_t>::min() && |
84 | 704 | value <= std::numeric_limits<int8_t>::max(); |
85 | 13.2k | } else if (width == 2) { |
86 | 922 | id = VariantPrimitiveId::INT16; |
87 | 922 | fits = value >= std::numeric_limits<int16_t>::min() && |
88 | 922 | value <= std::numeric_limits<int16_t>::max(); |
89 | 12.3k | } else if (width == 4) { |
90 | 12.3k | id = VariantPrimitiveId::INT32; |
91 | 12.3k | fits = value >= std::numeric_limits<int32_t>::min() && |
92 | 12.3k | value <= std::numeric_limits<int32_t>::max(); |
93 | 12.3k | } else if (width != 8) { |
94 | 1 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
95 | 1 | "Variant integer width {} must be one of 1, 2, 4, or 8", width); |
96 | 1 | } |
97 | 13.9k | if (!fits) { |
98 | 1 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
99 | 1 | "Variant integer value does not fit requested width {}", width); |
100 | 1 | } |
101 | 13.9k | return {.id = id, .width = width}; |
102 | 13.9k | } |
103 | | |
104 | 16.1k | void write_unsigned(char*& output, uint64_t value, uint8_t width) noexcept { |
105 | 77.7k | for (uint8_t byte = 0; byte < width; ++byte) { |
106 | 61.6k | *output++ = static_cast<char>(value >> (byte * 8)); |
107 | 61.6k | } |
108 | 16.1k | } |
109 | | |
110 | 73 | void write_signed128(char*& output, __int128 value, uint8_t width) noexcept { |
111 | 73 | const auto unsigned_value = static_cast<unsigned __int128>(value); |
112 | 801 | for (uint8_t byte = 0; byte < width; ++byte) { |
113 | 728 | *output++ = static_cast<char>(unsigned_value >> (byte * 8)); |
114 | 728 | } |
115 | 73 | } |
116 | | |
117 | 2.24k | void validate_string_ref(StringRef value, const char* description) { |
118 | 2.24k | if (value.data == nullptr && value.size != 0) { |
119 | 2 | throw Exception(ErrorCode::INVALID_ARGUMENT, "Variant {} has a null data pointer", |
120 | 2 | description); |
121 | 2 | } |
122 | 2.24k | } |
123 | | |
124 | 2.23k | void validate_utf8_string_ref(StringRef value, const char* description) { |
125 | 2.23k | validate_string_ref(value, description); |
126 | 2.23k | if (value.size != 0 && !validate_utf8(value.data, value.size)) { |
127 | 8 | throw Exception(ErrorCode::INVALID_ARGUMENT, "Variant {} is not valid UTF-8", description); |
128 | 8 | } |
129 | 2.23k | } |
130 | | |
131 | | } // namespace |
132 | | |
133 | 1.06M | VariantScalarEncodingPlan VariantScalarEncodingPlan::null_value() noexcept { |
134 | 1.06M | VariantScalarEncodingPlan plan; |
135 | 1.06M | plan._header = primitive_header(VariantPrimitiveId::NULL_VALUE); |
136 | 1.06M | plan._encoded_size = 1; |
137 | 1.06M | return plan; |
138 | 1.06M | } |
139 | | |
140 | 61 | VariantScalarEncodingPlan VariantScalarEncodingPlan::boolean(bool value) noexcept { |
141 | 61 | VariantScalarEncodingPlan plan; |
142 | 61 | plan._header = primitive_header(boolean_primitive_id(value)); |
143 | 61 | plan._encoded_size = 1; |
144 | 61 | return plan; |
145 | 61 | } |
146 | | |
147 | 13.9k | VariantScalarEncodingPlan VariantScalarEncodingPlan::integer(int64_t value, uint8_t width) { |
148 | 13.9k | const IntegerEncoding encoding = resolve_integer_encoding(value, width); |
149 | | |
150 | 13.9k | VariantScalarEncodingPlan plan; |
151 | 13.9k | plan._header = primitive_header(encoding.id); |
152 | 13.9k | plan._unsigned_value = std::bit_cast<uint64_t>(value); |
153 | 13.9k | plan._payload_width = encoding.width; |
154 | 13.9k | plan._encoded_size = static_cast<size_t>(encoding.width) + 1; |
155 | 13.9k | plan._kind = PayloadKind::UNSIGNED; |
156 | 13.9k | return plan; |
157 | 13.9k | } |
158 | | |
159 | 14 | VariantScalarEncodingPlan VariantScalarEncodingPlan::float32(float value) noexcept { |
160 | 14 | VariantScalarEncodingPlan plan; |
161 | 14 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::FLOAT) << VARIANT_VALUE_HEADER_SHIFT; |
162 | 14 | plan._unsigned_value = std::bit_cast<uint32_t>(value); |
163 | 14 | plan._payload_width = sizeof(float); |
164 | 14 | plan._encoded_size = sizeof(float) + 1; |
165 | 14 | plan._kind = PayloadKind::UNSIGNED; |
166 | 14 | return plan; |
167 | 14 | } |
168 | | |
169 | 95 | VariantScalarEncodingPlan VariantScalarEncodingPlan::float64(double value) noexcept { |
170 | 95 | VariantScalarEncodingPlan plan; |
171 | 95 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::DOUBLE) << VARIANT_VALUE_HEADER_SHIFT; |
172 | 95 | plan._unsigned_value = std::bit_cast<uint64_t>(value); |
173 | 95 | plan._payload_width = sizeof(double); |
174 | 95 | plan._encoded_size = sizeof(double) + 1; |
175 | 95 | plan._kind = PayloadKind::UNSIGNED; |
176 | 95 | return plan; |
177 | 95 | } |
178 | | |
179 | | VariantScalarEncodingPlan VariantScalarEncodingPlan::decimal(__int128 unscaled, uint8_t scale, |
180 | 78 | uint8_t width) { |
181 | 78 | if (scale > 38) { |
182 | 3 | throw Exception(ErrorCode::INVALID_ARGUMENT, "Variant decimal scale {} is outside [0, 38]", |
183 | 3 | scale); |
184 | 3 | } |
185 | 75 | const unsigned __int128 absolute = magnitude(unscaled); |
186 | 75 | if (absolute > MAX_DECIMAL38) { |
187 | 2 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
188 | 2 | "Variant decimal unscaled value exceeds precision 38"); |
189 | 2 | } |
190 | 73 | if (width == 0) { |
191 | 24 | if (absolute <= DECIMAL4_MAX) { |
192 | 10 | width = 4; |
193 | 14 | } else if (absolute <= DECIMAL8_MAX) { |
194 | 8 | width = 8; |
195 | 8 | } else { |
196 | 6 | width = 16; |
197 | 6 | } |
198 | 24 | } |
199 | | |
200 | 73 | VariantPrimitiveId id = VariantPrimitiveId::DECIMAL16; |
201 | 73 | unsigned __int128 width_max = MAX_DECIMAL38; |
202 | 73 | if (width == 4) { |
203 | 25 | id = VariantPrimitiveId::DECIMAL4; |
204 | 25 | width_max = DECIMAL4_MAX; |
205 | 48 | } else if (width == 8) { |
206 | 25 | id = VariantPrimitiveId::DECIMAL8; |
207 | 25 | width_max = DECIMAL8_MAX; |
208 | 25 | } else if (width != 16) { |
209 | 1 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
210 | 1 | "Variant decimal width {} must be one of 4, 8, or 16", width); |
211 | 1 | } |
212 | 72 | if (absolute > width_max) { |
213 | 4 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
214 | 4 | "Variant decimal unscaled value exceeds precision for width {}", width); |
215 | 4 | } |
216 | | |
217 | 68 | VariantScalarEncodingPlan plan; |
218 | 68 | plan._header = static_cast<uint8_t>(static_cast<uint8_t>(id) << VARIANT_VALUE_HEADER_SHIFT); |
219 | 68 | plan._signed_value = unscaled; |
220 | 68 | plan._payload_width = width; |
221 | 68 | plan._scale = scale; |
222 | 68 | plan._encoded_size = static_cast<size_t>(width) + 2; |
223 | 68 | plan._kind = PayloadKind::SIGNED; |
224 | 68 | plan._has_scale = true; |
225 | 68 | return plan; |
226 | 72 | } |
227 | | |
228 | 34 | VariantScalarEncodingPlan VariantScalarEncodingPlan::date(int32_t days_since_epoch) noexcept { |
229 | 34 | VariantScalarEncodingPlan plan; |
230 | 34 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::DATE) << VARIANT_VALUE_HEADER_SHIFT; |
231 | 34 | plan._unsigned_value = std::bit_cast<uint32_t>(days_since_epoch); |
232 | 34 | plan._payload_width = sizeof(int32_t); |
233 | 34 | plan._encoded_size = sizeof(int32_t) + 1; |
234 | 34 | plan._kind = PayloadKind::UNSIGNED; |
235 | 34 | return plan; |
236 | 34 | } |
237 | | |
238 | | VariantScalarEncodingPlan VariantScalarEncodingPlan::timestamp_micros(int64_t value, |
239 | 43 | bool utc_adjusted) noexcept { |
240 | 43 | VariantScalarEncodingPlan plan; |
241 | 43 | plan._header = static_cast<uint8_t>( |
242 | 43 | static_cast<uint8_t>(utc_adjusted ? VariantPrimitiveId::TIMESTAMP_MICROS |
243 | 43 | : VariantPrimitiveId::TIMESTAMP_NTZ_MICROS) |
244 | 43 | << VARIANT_VALUE_HEADER_SHIFT); |
245 | 43 | plan._unsigned_value = std::bit_cast<uint64_t>(value); |
246 | 43 | plan._payload_width = sizeof(int64_t); |
247 | 43 | plan._encoded_size = sizeof(int64_t) + 1; |
248 | 43 | plan._kind = PayloadKind::UNSIGNED; |
249 | 43 | return plan; |
250 | 43 | } |
251 | | |
252 | | VariantScalarEncodingPlan VariantScalarEncodingPlan::timestamp_nanos(int64_t value, |
253 | 28 | bool utc_adjusted) noexcept { |
254 | 28 | VariantScalarEncodingPlan plan; |
255 | 28 | plan._header = static_cast<uint8_t>( |
256 | 28 | static_cast<uint8_t>(utc_adjusted ? VariantPrimitiveId::TIMESTAMP_NANOS |
257 | 28 | : VariantPrimitiveId::TIMESTAMP_NTZ_NANOS) |
258 | 28 | << VARIANT_VALUE_HEADER_SHIFT); |
259 | 28 | plan._unsigned_value = std::bit_cast<uint64_t>(value); |
260 | 28 | plan._payload_width = sizeof(int64_t); |
261 | 28 | plan._encoded_size = sizeof(int64_t) + 1; |
262 | 28 | plan._kind = PayloadKind::UNSIGNED; |
263 | 28 | return plan; |
264 | 28 | } |
265 | | |
266 | 10 | VariantScalarEncodingPlan VariantScalarEncodingPlan::time_ntz_micros(int64_t value) noexcept { |
267 | 10 | VariantScalarEncodingPlan plan; |
268 | 10 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::TIME_NTZ_MICROS) |
269 | 10 | << VARIANT_VALUE_HEADER_SHIFT; |
270 | 10 | plan._unsigned_value = std::bit_cast<uint64_t>(value); |
271 | 10 | plan._payload_width = sizeof(int64_t); |
272 | 10 | plan._encoded_size = sizeof(int64_t) + 1; |
273 | 10 | plan._kind = PayloadKind::UNSIGNED; |
274 | 10 | return plan; |
275 | 10 | } |
276 | | |
277 | 14 | VariantScalarEncodingPlan VariantScalarEncodingPlan::binary(StringRef value) { |
278 | 14 | validate_string_ref(value, "binary"); |
279 | 14 | if (value.size > std::numeric_limits<uint32_t>::max()) { |
280 | 0 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
281 | 0 | "Variant binary exceeds the uint32 byte limit"); |
282 | 0 | } |
283 | 14 | VariantScalarEncodingPlan plan; |
284 | 14 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::BINARY) << VARIANT_VALUE_HEADER_SHIFT; |
285 | 14 | plan._borrowed = value; |
286 | 14 | plan._encoded_size = value.size + sizeof(uint32_t) + 1; |
287 | 14 | plan._kind = PayloadKind::BORROWED_BYTES; |
288 | 14 | plan._has_length = true; |
289 | 14 | return plan; |
290 | 14 | } |
291 | | |
292 | 2.23k | VariantScalarEncodingPlan VariantScalarEncodingPlan::string(StringRef value) { |
293 | 2.23k | validate_utf8_string_ref(value, "string"); |
294 | 2.23k | if (value.size > std::numeric_limits<uint32_t>::max()) { |
295 | 0 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
296 | 0 | "Variant string exceeds the uint32 byte limit"); |
297 | 0 | } |
298 | 2.23k | VariantScalarEncodingPlan plan; |
299 | 2.23k | plan._borrowed = value; |
300 | 2.23k | plan._kind = PayloadKind::BORROWED_BYTES; |
301 | 2.23k | if (value.size <= VARIANT_MAX_SHORT_STRING_SIZE) { |
302 | 151 | plan._header = static_cast<uint8_t>((value.size << VARIANT_VALUE_HEADER_SHIFT) | |
303 | 151 | static_cast<uint8_t>(VariantBasicType::SHORT_STRING)); |
304 | 151 | plan._encoded_size = value.size + 1; |
305 | 2.08k | } else { |
306 | 2.08k | plan._header = static_cast<uint8_t>(VariantPrimitiveId::STRING) |
307 | 2.08k | << VARIANT_VALUE_HEADER_SHIFT; |
308 | 2.08k | plan._encoded_size = value.size + sizeof(uint32_t) + 1; |
309 | 2.08k | plan._has_length = true; |
310 | 2.08k | } |
311 | 2.23k | return plan; |
312 | 2.23k | } |
313 | | |
314 | | VariantScalarEncodingPlan VariantScalarEncodingPlan::uuid( |
315 | 9 | const std::array<uint8_t, 16>& value) noexcept { |
316 | 9 | VariantScalarEncodingPlan plan; |
317 | 9 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::UUID) << VARIANT_VALUE_HEADER_SHIFT; |
318 | 9 | plan._uuid = value; |
319 | 9 | plan._encoded_size = value.size() + 1; |
320 | 9 | plan._kind = PayloadKind::UUID; |
321 | 9 | return plan; |
322 | 9 | } |
323 | | |
324 | 23 | VariantScalarEncodingPlan VariantScalarEncodingPlan::largeint(__int128 value) noexcept { |
325 | 23 | if (magnitude(value) <= MAX_DECIMAL38) { |
326 | 10 | VariantScalarEncodingPlan plan; |
327 | 10 | plan._header = static_cast<uint8_t>(VariantPrimitiveId::DECIMAL16) |
328 | 10 | << VARIANT_VALUE_HEADER_SHIFT; |
329 | 10 | plan._signed_value = value; |
330 | 10 | plan._payload_width = 16; |
331 | 10 | plan._encoded_size = 18; |
332 | 10 | plan._kind = PayloadKind::SIGNED; |
333 | 10 | plan._has_scale = true; |
334 | 10 | return plan; |
335 | 10 | } |
336 | | |
337 | 13 | VariantScalarEncodingPlan plan; |
338 | 13 | char reversed[39]; |
339 | 13 | size_t digit_count = 0; |
340 | 13 | unsigned __int128 remaining = magnitude(value); |
341 | 507 | do { |
342 | 507 | reversed[digit_count++] = static_cast<char>('0' + remaining % 10); |
343 | 507 | remaining /= 10; |
344 | 507 | } while (remaining != 0); |
345 | 13 | if (value < 0) { |
346 | 7 | plan._inline_string[plan._inline_size++] = '-'; |
347 | 7 | } |
348 | 520 | while (digit_count != 0) { |
349 | 507 | plan._inline_string[plan._inline_size++] = reversed[--digit_count]; |
350 | 507 | } |
351 | 13 | plan._header = static_cast<uint8_t>( |
352 | 13 | (static_cast<size_t>(plan._inline_size) << VARIANT_VALUE_HEADER_SHIFT) | |
353 | 13 | static_cast<uint8_t>(VariantBasicType::SHORT_STRING)); |
354 | 13 | plan._encoded_size = static_cast<size_t>(plan._inline_size) + 1; |
355 | 13 | plan._kind = PayloadKind::INLINE_STRING; |
356 | 13 | plan._used_string_fallback = true; |
357 | 13 | return plan; |
358 | 23 | } |
359 | | |
360 | 1.08M | void VariantScalarEncodingPlan::write(char* destination, size_t capacity) const { |
361 | 1.08M | if (destination == nullptr) { |
362 | 1 | throw Exception(ErrorCode::INVALID_ARGUMENT, "Variant scalar destination must not be null"); |
363 | 1 | } |
364 | 1.08M | if (capacity < _encoded_size) { |
365 | 1 | throw Exception(ErrorCode::INVALID_ARGUMENT, |
366 | 1 | "Variant scalar destination capacity {} is smaller than required {}", |
367 | 1 | capacity, _encoded_size); |
368 | 1 | } |
369 | 1.08M | char* output = destination; |
370 | 1.08M | *output++ = static_cast<char>(_header); |
371 | 1.08M | if (_has_scale) { |
372 | 73 | *output++ = static_cast<char>(_scale); |
373 | 73 | } |
374 | 1.08M | if (_has_length) { |
375 | 2.08k | write_unsigned(output, _borrowed.size, sizeof(uint32_t)); |
376 | 2.08k | } |
377 | 1.08M | switch (_kind) { |
378 | 1.06M | case PayloadKind::HEADER_ONLY: |
379 | 1.06M | break; |
380 | 14.0k | case PayloadKind::UNSIGNED: |
381 | 14.0k | write_unsigned(output, _unsigned_value, _payload_width); |
382 | 14.0k | break; |
383 | 73 | case PayloadKind::SIGNED: |
384 | 73 | write_signed128(output, _signed_value, _payload_width); |
385 | 73 | break; |
386 | 2.21k | case PayloadKind::BORROWED_BYTES: |
387 | 2.21k | if (_borrowed.size != 0) { |
388 | 2.21k | std::memcpy(output, _borrowed.data, _borrowed.size); |
389 | 2.21k | output += _borrowed.size; |
390 | 2.21k | } |
391 | 2.21k | break; |
392 | 9 | case PayloadKind::UUID: |
393 | 9 | std::memcpy(output, _uuid.data(), _uuid.size()); |
394 | 9 | output += _uuid.size(); |
395 | 9 | break; |
396 | 13 | case PayloadKind::INLINE_STRING: |
397 | 13 | std::memcpy(output, _inline_string.data(), _inline_size); |
398 | 13 | output += _inline_size; |
399 | 13 | break; |
400 | 1.08M | } |
401 | 1.08M | DCHECK_EQ(output, destination + _encoded_size); |
402 | 1.08M | } |
403 | | |
404 | | } // namespace doris |