FlatCityBuf C++ reader 0.8.0
Native C++17 reader for FlatCityBuf, the cloud-optimized CityJSON format
Loading...
Searching...
No Matches
key.cpp
Go to the documentation of this file.
1#include <fcb/generated/header_generated.h>
2#include <fcb/key.hpp>
3
4#include <algorithm>
5#include <cmath>
6#include <cstring>
7#include <limits>
8
9namespace fcb {
10
11namespace {
12
13bool is_string_kind(KeyKind k) {
14 return k == KeyKind::String20 || k == KeyKind::String50 || k == KeyKind::String100;
15}
16
17template <typename T> void put_le(std::vector<std::uint8_t>& out, T value) {
18 static_assert(std::is_integral<T>::value, "integral only");
19 using U = typename std::make_unsigned<T>::type;
20 U u = static_cast<U>(value);
21 for (std::size_t i = 0; i < sizeof(T); ++i) {
22 out.push_back(static_cast<std::uint8_t>((u >> (8 * i)) & 0xFF));
23 }
24}
25
26template <typename T> T get_le(bytes_view b, std::size_t at = 0) {
27 using U = typename std::make_unsigned<T>::type;
28 U u = 0;
29 for (std::size_t i = 0; i < sizeof(T); ++i) {
30 u |= static_cast<U>(b[at + i]) << (8 * i);
31 }
32 return static_cast<T>(u);
33}
34
37int cmp_ordered_double(double a, double b) {
38 const bool na = std::isnan(a);
39 const bool nb = std::isnan(b);
40 if (na && nb)
41 return 0;
42 if (na)
43 return 1;
44 if (nb)
45 return -1;
46 if (a < b)
47 return -1;
48 if (a > b)
49 return 1;
50 return 0; // also covers -0.0 == +0.0
51}
52
53void require_size(bytes_view b, std::size_t need, const char* what) {
54 if (b.size() < need) {
55 throw Error(ErrorCode::InvalidAttributeValue, std::string("short key buffer for ") + what);
56 }
57}
58
59} // namespace
60
62 switch (kind) {
63 case KeyKind::Int8:
64 case KeyKind::UInt8:
65 case KeyKind::Bool:
66 return 1;
67 case KeyKind::Int16:
68 case KeyKind::UInt16:
69 return 2;
70 case KeyKind::Int32:
71 case KeyKind::UInt32:
73 return 4;
74 case KeyKind::Int64:
75 case KeyKind::UInt64:
77 return 8;
79 return 12; // i64 seconds + u32 nanos
81 return 20;
83 return 50;
85 return 100;
86 }
87 throw Error(ErrorCode::UnsupportedColumnType, "unknown key kind");
88}
89
90#define FCB_KV_INT(NAME, TYPE, KIND, FIELD) \
91 KeyValue KeyValue::NAME(TYPE v) { \
92 KeyValue k; \
93 k.kind_ = KeyKind::KIND; \
94 k.FIELD = static_cast<decltype(k.FIELD)>(v); \
95 return k; \
96 }
97
98FCB_KV_INT(from_i8, std::int8_t, Int8, i_)
99FCB_KV_INT(from_u8, std::uint8_t, UInt8, u_)
100FCB_KV_INT(from_i16, std::int16_t, Int16, i_)
101FCB_KV_INT(from_u16, std::uint16_t, UInt16, u_)
102FCB_KV_INT(from_i32, std::int32_t, Int32, i_)
103FCB_KV_INT(from_u32, std::uint32_t, UInt32, u_)
104FCB_KV_INT(from_i64, std::int64_t, Int64, i_)
105FCB_KV_INT(from_u64, std::uint64_t, UInt64, u_)
106#undef FCB_KV_INT
107
109 KeyValue k;
110 k.kind_ = KeyKind::Float32;
111 k.f_ = static_cast<double>(v);
112 return k;
113}
114
116 KeyValue k;
117 k.kind_ = KeyKind::Float64;
118 k.f_ = v;
119 return k;
120}
121
123 KeyValue k;
124 k.kind_ = KeyKind::Bool;
125 k.u_ = v ? 1 : 0;
126 return k;
127}
128
129KeyValue KeyValue::from_datetime(std::int64_t seconds, std::uint32_t nanos) {
130 KeyValue k;
131 k.kind_ = KeyKind::DateTime;
132 k.i_ = seconds;
133 k.u_ = nanos;
134 return k;
135}
136
138 if (!is_string_kind(kind)) {
139 throw Error(ErrorCode::UnsupportedColumnType, "from_string on a non-string kind");
140 }
141 KeyValue k;
142 k.kind_ = kind;
143 k.str_ = v; // kept untruncated, for post-filtering
144 return k;
145}
146
147std::vector<std::uint8_t> encode_key(const KeyValue& v) {
148 std::vector<std::uint8_t> out;
149 const std::size_t n = key_serialized_size(v.kind_);
150 out.reserve(n);
151
152 switch (v.kind_) {
153 case KeyKind::Int8:
154 put_le<std::int8_t>(out, static_cast<std::int8_t>(v.i_));
155 break;
156 case KeyKind::UInt8:
157 put_le<std::uint8_t>(out, static_cast<std::uint8_t>(v.u_));
158 break;
159 case KeyKind::Int16:
160 put_le<std::int16_t>(out, static_cast<std::int16_t>(v.i_));
161 break;
162 case KeyKind::UInt16:
163 put_le<std::uint16_t>(out, static_cast<std::uint16_t>(v.u_));
164 break;
165 case KeyKind::Int32:
166 put_le<std::int32_t>(out, static_cast<std::int32_t>(v.i_));
167 break;
168 case KeyKind::UInt32:
169 put_le<std::uint32_t>(out, static_cast<std::uint32_t>(v.u_));
170 break;
171 case KeyKind::Int64:
172 put_le<std::int64_t>(out, v.i_);
173 break;
174 case KeyKind::UInt64:
175 put_le<std::uint64_t>(out, v.u_);
176 break;
177 case KeyKind::Bool:
178 out.push_back(v.u_ != 0 ? 1 : 0);
179 break;
180
181 case KeyKind::Float32: {
182 // Raw IEEE-754 bits, little-endian. NO order-preserving
183 // transform: key.rs:323-345 writes the plain bit pattern, and
184 // applying the usual sign-flip trick would disagree with every
185 // file the reference has written.
186 const float f = static_cast<float>(v.f_);
187 std::uint32_t bits;
188 std::memcpy(&bits, &f, sizeof(bits));
189 put_le<std::uint32_t>(out, bits);
190 break;
191 }
192 case KeyKind::Float64: {
193 std::uint64_t bits;
194 std::memcpy(&bits, &v.f_, sizeof(bits));
195 put_le<std::uint64_t>(out, bits);
196 break;
197 }
199 put_le<std::int64_t>(out, v.i_);
200 put_le<std::uint32_t>(out, static_cast<std::uint32_t>(v.u_));
201 break;
202
205 case KeyKind::String100: {
206 // Copy min(len, N) BYTES and zero-pad. Truncation is silent and
207 // does not respect UTF-8 boundaries (key.rs:483-489), so two
208 // distinct strings sharing an N-byte prefix become identical
209 // here -- which is why select_attr must post-filter.
210 out.assign(n, 0);
211 const std::size_t take = std::min(v.str_.size(), n);
212 std::memcpy(out.data(), v.str_.data(), take);
213 break;
214 }
215 }
216 return out;
217}
218
220 const std::size_t n = key_serialized_size(kind);
221 require_size(b, n, "decode_key");
222
223 switch (kind) {
224 case KeyKind::Int8:
225 return KeyValue::from_i8(get_le<std::int8_t>(b));
226 case KeyKind::UInt8:
227 return KeyValue::from_u8(get_le<std::uint8_t>(b));
228 case KeyKind::Int16:
229 return KeyValue::from_i16(get_le<std::int16_t>(b));
230 case KeyKind::UInt16:
231 return KeyValue::from_u16(get_le<std::uint16_t>(b));
232 case KeyKind::Int32:
233 return KeyValue::from_i32(get_le<std::int32_t>(b));
234 case KeyKind::UInt32:
235 return KeyValue::from_u32(get_le<std::uint32_t>(b));
236 case KeyKind::Int64:
237 return KeyValue::from_i64(get_le<std::int64_t>(b));
238 case KeyKind::UInt64:
239 return KeyValue::from_u64(get_le<std::uint64_t>(b));
240 case KeyKind::Bool:
241 return KeyValue::from_bool(b[0] != 0);
242
243 case KeyKind::Float32: {
244 const std::uint32_t bits = get_le<std::uint32_t>(b);
245 float f;
246 std::memcpy(&f, &bits, sizeof(f));
247 return KeyValue::from_f32(f);
248 }
249 case KeyKind::Float64: {
250 const std::uint64_t bits = get_le<std::uint64_t>(b);
251 double d;
252 std::memcpy(&d, &bits, sizeof(d));
253 return KeyValue::from_f64(d);
254 }
256 return KeyValue::from_datetime(get_le<std::int64_t>(b), get_le<std::uint32_t>(b, 8));
257
260 case KeyKind::String100: {
261 // Stop at the first NUL, as to_string_lossy does (key.rs:511).
262 std::size_t len = 0;
263 while (len < n && b[len] != 0)
264 ++len;
266 std::string(reinterpret_cast<const char*>(b.data()), len));
267 }
268 }
269 throw Error(ErrorCode::UnsupportedColumnType, "unknown key kind");
270}
271
272int compare_keys(const KeyValue& a, const KeyValue& b) {
273 if (a.kind_ != b.kind_) {
274 throw Error(ErrorCode::QueryExecutionError, "comparing keys of different kinds");
275 }
276 switch (a.kind_) {
277 case KeyKind::Int8:
278 case KeyKind::Int16:
279 case KeyKind::Int32:
280 case KeyKind::Int64:
281 return a.i_ < b.i_ ? -1 : (a.i_ > b.i_ ? 1 : 0);
282
283 case KeyKind::UInt8:
284 case KeyKind::UInt16:
285 case KeyKind::UInt32:
286 case KeyKind::UInt64:
287 case KeyKind::Bool:
288 return a.u_ < b.u_ ? -1 : (a.u_ > b.u_ ? 1 : 0);
289
290 case KeyKind::Float32:
291 case KeyKind::Float64:
292 return cmp_ordered_double(a.f_, b.f_);
293
295 if (a.i_ != b.i_)
296 return a.i_ < b.i_ ? -1 : 1;
297 return a.u_ < b.u_ ? -1 : (a.u_ > b.u_ ? 1 : 0);
298
301 case KeyKind::String100: {
302 // Compare the ENCODED (truncated, padded) forms, because that is
303 // what the tree stores and orders by.
304 const auto ea = encode_key(a);
305 const auto eb = encode_key(b);
306 const int c = std::memcmp(ea.data(), eb.data(), ea.size());
307 return c < 0 ? -1 : (c > 0 ? 1 : 0);
308 }
309 }
310 throw Error(ErrorCode::UnsupportedColumnType, "unknown key kind");
311}
312
314 switch (kind) {
315 case KeyKind::Int8:
316 return KeyValue::from_i8(std::numeric_limits<std::int8_t>::min());
317 case KeyKind::UInt8:
318 return KeyValue::from_u8(0);
319 case KeyKind::Int16:
320 return KeyValue::from_i16(std::numeric_limits<std::int16_t>::min());
321 case KeyKind::UInt16:
322 return KeyValue::from_u16(0);
323 case KeyKind::Int32:
324 return KeyValue::from_i32(std::numeric_limits<std::int32_t>::min());
325 case KeyKind::UInt32:
326 return KeyValue::from_u32(0);
327 case KeyKind::Int64:
328 return KeyValue::from_i64(std::numeric_limits<std::int64_t>::min());
329 case KeyKind::UInt64:
330 return KeyValue::from_u64(0);
331 case KeyKind::Float32:
332 return KeyValue::from_f32(-std::numeric_limits<float>::infinity());
333 case KeyKind::Float64:
334 return KeyValue::from_f64(-std::numeric_limits<double>::infinity());
335 case KeyKind::Bool:
336 return KeyValue::from_bool(false);
337 // Epoch 0, matching key.rs:242 -- NOT the true i64 minimum. Pre-1970
338 // timestamps are therefore invisible to range queries, in both
339 // implementations. Reproduced deliberately.
341 return KeyValue::from_datetime(0, 0);
345 return KeyValue::from_string(kind, std::string());
346 }
347 throw Error(ErrorCode::UnsupportedColumnType, "unknown key kind");
348}
349
351 switch (kind) {
352 case KeyKind::Int8:
353 return KeyValue::from_i8(std::numeric_limits<std::int8_t>::max());
354 case KeyKind::UInt8:
355 return KeyValue::from_u8(std::numeric_limits<std::uint8_t>::max());
356 case KeyKind::Int16:
357 return KeyValue::from_i16(std::numeric_limits<std::int16_t>::max());
358 case KeyKind::UInt16:
359 return KeyValue::from_u16(std::numeric_limits<std::uint16_t>::max());
360 case KeyKind::Int32:
361 return KeyValue::from_i32(std::numeric_limits<std::int32_t>::max());
362 case KeyKind::UInt32:
363 return KeyValue::from_u32(std::numeric_limits<std::uint32_t>::max());
364 case KeyKind::Int64:
365 return KeyValue::from_i64(std::numeric_limits<std::int64_t>::max());
366 case KeyKind::UInt64:
367 return KeyValue::from_u64(std::numeric_limits<std::uint64_t>::max());
368 // +inf, matching key.rs:139. NaN sorts ABOVE +inf in the total
369 // order, so NaN-keyed features are excluded from range-lowered
370 // operators (Ge, Ne). Reproduced deliberately so results match Rust.
371 case KeyKind::Float32:
372 return KeyValue::from_f32(std::numeric_limits<float>::infinity());
373 case KeyKind::Float64:
374 return KeyValue::from_f64(std::numeric_limits<double>::infinity());
375 case KeyKind::Bool:
376 return KeyValue::from_bool(true);
378 return KeyValue::from_datetime(253402300799LL, 999999999U);
382 return KeyValue::from_string(kind, std::string(key_serialized_size(kind), '\xFF'));
383 }
384 throw Error(ErrorCode::UnsupportedColumnType, "unknown key kind");
385}
386
387KeyKind key_kind_for_column(std::uint8_t column_type) {
388 switch (static_cast<::ColumnType>(column_type)) {
389 // Byte -> UInt8, deliberately. The writer stores Byte as u8
390 // (writer/attribute.rs) and builds MemoryIndex<u8>
391 // (writer/attr_index.rs), so it must be read back unsigned or every
392 // stored value above 127 comes back negative. Rust now agrees on
393 // both paths -- its index reader (reader/attr_query.rs) and its
394 // value reader (reader/deserializer.rs) each decode u8 -- so this is
395 // no longer a divergence.
396 case ::ColumnType::Byte:
397 return KeyKind::UInt8;
398 case ::ColumnType::UByte:
399 return KeyKind::UInt8;
400 case ::ColumnType::Bool:
401 return KeyKind::Bool;
402 case ::ColumnType::Short:
403 return KeyKind::Int16;
404 case ::ColumnType::UShort:
405 return KeyKind::UInt16;
406 case ::ColumnType::Int:
407 return KeyKind::Int32;
408 case ::ColumnType::UInt:
409 return KeyKind::UInt32;
410 case ::ColumnType::Long:
411 return KeyKind::Int64;
412 case ::ColumnType::ULong:
413 return KeyKind::UInt64;
414 case ::ColumnType::Float:
415 return KeyKind::Float32;
416 case ::ColumnType::Double:
417 return KeyKind::Float64;
418 case ::ColumnType::String:
419 return KeyKind::String50;
420 case ::ColumnType::DateTime:
421 return KeyKind::DateTime;
422 case ::ColumnType::Json:
423 return KeyKind::String100;
424 case ::ColumnType::Binary:
425 return KeyKind::String100;
426 }
427 throw Error(ErrorCode::UnsupportedColumnType, "unknown column type");
428}
429
430} // namespace fcb
Every failure the library reports is one of these.
Definition error.hpp:30
A decoded index key.
Definition key.hpp:40
static KeyValue from_datetime(std::int64_t seconds, std::uint32_t nanos)
Definition key.cpp:129
static KeyValue from_i64(std::int64_t v)
Definition key.cpp:104
static KeyValue from_i8(std::int8_t v)
Definition key.cpp:98
static KeyValue from_u32(std::uint32_t v)
Definition key.cpp:103
static KeyValue from_i16(std::int16_t v)
Definition key.cpp:100
static KeyValue from_f32(float v)
Definition key.cpp:108
KeyKind kind() const
Definition key.hpp:58
static KeyValue from_i32(std::int32_t v)
Definition key.cpp:102
static KeyValue from_u16(std::uint16_t v)
Definition key.cpp:101
static KeyValue from_u8(std::uint8_t v)
Definition key.cpp:99
static KeyValue from_f64(double v)
Definition key.cpp:115
static KeyValue from_string(KeyKind kind, const std::string &v)
Definition key.cpp:137
static KeyValue from_u64(std::uint64_t v)
Definition key.cpp:105
static KeyValue from_bool(bool v)
Definition key.cpp:122
Minimal C++17 stand-in for std::span: a non-owning view over contiguous memory.
Definition span.hpp:13
T * data() const noexcept
Definition span.hpp:25
#define FCB_KV_INT(NAME, TYPE, KIND, FIELD)
Definition key.cpp:90
span< const std::uint8_t > bytes_view
The workhorse alias: a read-only view over bytes.
Definition span.hpp:43
std::vector< std::uint8_t > encode_key(const KeyValue &v)
Definition key.cpp:147
KeyKind key_kind_for_column(std::uint8_t column_type)
Column type to key kind, following what the WRITER emits.
Definition key.cpp:387
KeyValue key_max(KeyKind kind)
Definition key.cpp:350
int compare_keys(const KeyValue &a, const KeyValue &b)
Three-way comparison.
Definition key.cpp:272
KeyKind
The concrete key types the B+tree index can hold.
Definition key.hpp:14
std::size_t key_serialized_size(KeyKind kind)
Serialized width in bytes. DateTime is 12: i64 seconds + u32 nanos.
Definition key.cpp:61
KeyValue decode_key(KeyKind kind, bytes_view b)
Definition key.cpp:219
KeyValue key_min(KeyKind kind)
Sentinels used to lower open-ended range queries.
Definition key.cpp:313
KeyKind kind
Definition stree.cpp:166
std::uint32_t nanos
Definition attribute.cpp:30
std::int64_t seconds
Definition attribute.cpp:29