MDBX Containers
Loading...
Searching...
No Matches
kv_container_all_types_test.cpp
Go to the documentation of this file.
1#include <iostream>
2#include <sstream>
3#include <thread>
4#include <mutex>
5#include <condition_variable>
6#include <atomic>
7#include <cassert>
8#include <chrono>
9#include <vector>
10#include <cstring>
11#include <bitset>
12#include <utility>
13
15
16#if __cplusplus >= 201703L
17#define ASSERT_FOUND(table, key, expected) assert((table).find(key).value() == (expected))
18#else
19#define ASSERT_FOUND(table, key, expected) \
20 do { \
21 auto res = (table).find_compat(key); \
22 assert(res.first && res.second == (expected)); \
23 } while (0)
24#endif
25
26// ---- synchronized ostream (flushes once on scope exit) ----
28public:
29 explicit SyncOStream(std::ostream& os) : os_(os) {}
30
31 SyncOStream(const SyncOStream&) = delete;
33
34 SyncOStream(SyncOStream&& other) noexcept
35 : os_(other.os_), buffer_(std::move(other.buffer_)) {}
36
38
39 template <typename T>
40 SyncOStream& operator<<(const T& value) {
41 buffer_ << value;
42 return *this;
43 }
44 // manipulators (std::endl, etc.)
45 SyncOStream& operator<<(std::ostream& (*manip)(std::ostream&)) {
46 buffer_ << manip;
47 return *this;
48 }
50 std::lock_guard<std::mutex> lock(get_mutex());
51 os_ << buffer_.str();
52 os_.flush();
53 }
54private:
55 std::ostream& os_;
56 std::ostringstream buffer_;
57 static std::mutex& get_mutex() {
58 static std::mutex m;
59 return m;
60 }
61};
62
63inline SyncOStream sync_cout() { return SyncOStream(std::cout); }
64inline SyncOStream sync_cerr() { return SyncOStream(std::cerr); }
65
66// ---- thread runner that captures exceptions ----
67template <class F>
68std::thread make_thread_catching(F&& f, std::exception_ptr& out_eptr) {
69 typedef typename std::decay<F>::type Fn;
70 std::shared_ptr<Fn> fn(new Fn(std::forward<F>(f)));
71 return std::thread([fn, &out_eptr]{
72 try { (*fn)(); }
73 catch (...) { out_eptr = std::current_exception(); }
74 });
75}
76
77// ---- safe join (no throw) ----
78inline void safe_join(std::thread& t) noexcept {
79 if (t.joinable()) t.join();
80}
81
82// ---- sample serializable structs ----
84 int x{};
85 float y{};
86
87 SimpleStruct() = default;
88 SimpleStruct(int x_, float y_) : x(x_), y(y_) {}
89
90 std::vector<uint8_t> to_bytes() const {
91 std::vector<uint8_t> bytes(sizeof(int) + sizeof(float));
92 std::memcpy(bytes.data(), &x, sizeof(int));
93 std::memcpy(bytes.data() + sizeof(int), &y, sizeof(float));
94 return bytes;
95 }
96 static SimpleStruct from_bytes(const void* data, size_t size) {
97 if (size != (sizeof(int) + sizeof(float)))
98 throw std::runtime_error("Invalid data size for SimpleStruct");
99 const uint8_t* ptr = static_cast<const uint8_t*>(data);
100 SimpleStruct s{};
101 std::memcpy(&s.x, ptr, sizeof(int));
102 std::memcpy(&s.y, ptr + sizeof(int), sizeof(float));
103 return s;
104 }
105 bool operator==(const SimpleStruct& other) const {
106 return x == other.x && y == other.y;
107 }
108};
109
111 int value{};
112
113 ConcurrentStruct() = default;
114 explicit ConcurrentStruct(int v) : value(v) {}
115
116 std::vector<uint8_t> to_bytes() const {
117 std::vector<uint8_t> bytes(sizeof(int));
118 std::memcpy(bytes.data(), &value, sizeof(int));
119 return bytes;
120 }
121 static ConcurrentStruct from_bytes(const void* data, size_t size) {
122 if (size != sizeof(int)) throw std::runtime_error("Invalid data size");
124 std::memcpy(&s.value, data, sizeof(int));
125 return s;
126 }
127 bool operator==(const ConcurrentStruct& other) const { return value == other.value; }
128 bool operator!=(const ConcurrentStruct& other) const { return !(*this == other); }
129};
130
131int main() {
132 mdbxc::Config cfg;
133 cfg.pathname = "data/kv_container_all_types";
134 cfg.max_dbs = 14;
135 cfg.no_subdir = false;
136 cfg.relative_to_exe= true;
137
138 auto conn = mdbxc::Connection::create(cfg);
139
140 // --- basic cases ---
141 std::cout << "[case] int8 -> int8\n";
142 {
143 mdbxc::KeyValueTable<int8_t, int8_t> kv(conn, "i8_i8");
144 kv.insert_or_assign(1, 100);
145 ASSERT_FOUND(kv, 1, 100);
146 }
147
148 std::cout << "[case] int8 -> int64\n";
149 {
150 mdbxc::KeyValueTable<int8_t, int64_t> kv(conn, "i8_i64");
151 kv.insert_or_assign(2, 1234567890123456LL);
152 ASSERT_FOUND(kv, 2, 1234567890123456LL);
153 }
154
155 std::cout << "[case] int32 -> string\n";
156 {
158 kv.insert_or_assign(3, "hello");
159 ASSERT_FOUND(kv, 3, std::string("hello"));
160 }
161
162 std::cout << "[case] string -> string\n";
163 {
165 kv.insert_or_assign("key", "value");
166 ASSERT_FOUND(kv, std::string("key"), std::string("value"));
167 }
168
169 std::cout << "[case] string -> POD(SimpleStruct)\n";
170 {
172 SimpleStruct s{42, 3.14f};
173 kv.insert_or_assign("obj", s);
174 ASSERT_FOUND(kv, std::string("obj"), s);
175 }
176
177 std::cout << "[case] string -> set<int>\n";
178 {
180 std::set<int> s{1, 2, 3};
181 kv.insert_or_assign("digits", s);
182 ASSERT_FOUND(kv, std::string("digits"), s);
183 }
184
185#if __cplusplus >= 201703L
186 std::cout << "[case] int64 -> vector<uint8_t>\n";
187 {
189 std::vector<uint8_t> data{1, 2, 3, 4};
190 kv.insert_or_assign(9, data);
191 ASSERT_FOUND(kv, 9, data);
192 }
193
194 std::cout << "[case] string -> vector<SimpleStruct>\n";
195 {
197 std::vector<SimpleStruct> vec{{1, 1.0f}, {2, 2.0f}};
198 kv.insert_or_assign("many", vec);
199 ASSERT_FOUND(kv, std::string("many"), vec);
200 }
201
202 std::cout << "[case] string -> list<string>\n";
203 {
205 std::list<std::string> lst{"a", "b", "c"};
206 kv.insert_or_assign("letters", lst);
207 ASSERT_FOUND(kv, std::string("letters"), lst);
208 }
209
210 std::cout << "[case] string -> vector<string>\n";
211 {
213 std::vector<std::string> lst{"a", "b", "c"};
214 kv.insert_or_assign("letters", lst);
215 ASSERT_FOUND(kv, std::string("letters"), lst);
216 }
217
218 std::cout << "[case] string -> set<string>\n";
219 {
221 std::set<std::string> s{"a", "b", "c"};
222 kv.insert_or_assign("letters", s);
223 ASSERT_FOUND(kv, std::string("letters"), s);
224 }
225#endif
226
227 std::cout << "[case] string -> self-serializable struct\n";
228 {
229 struct Serializable {
230 int a = 0;
231 std::string b;
232
233 Serializable() {}
234 Serializable(int a_, const std::string& b_) : a(a_), b(b_) {}
235
236 std::vector<uint8_t> to_bytes() const {
237 std::vector<uint8_t> result(sizeof(int) + b.size());
238 std::memcpy(result.data(), &a, sizeof(int));
239 std::memcpy(result.data() + sizeof(int), b.data(), b.size());
240 return result;
241 }
242 static Serializable from_bytes(const void* data, size_t size) {
243 if (size < sizeof(int))
244 throw std::runtime_error("Invalid data size for Serializable");
245 const uint8_t* ptr = static_cast<const uint8_t*>(data);
246 Serializable s;
247 std::memcpy(&s.a, ptr, sizeof(int));
248 s.b = std::string(reinterpret_cast<const char*>(ptr + sizeof(int)), size - sizeof(int));
249 return s;
250 }
251 bool operator==(const Serializable& other) const {
252 return a == other.a && b == other.b;
253 }
254 };
255
256 mdbxc::KeyValueTable<std::string, Serializable> kv(conn, "str_serializable");
257 Serializable s{7, "seven"};
258 kv.insert_or_assign("ser", s);
259 ASSERT_FOUND(kv, std::string("ser"), s);
260 }
261
262 // --- NEW: bitset key example ---
263 std::cout << "[case] std::bitset<32> -> int\n";
264 {
265 using Bits = std::bitset<32>;
266 mdbxc::KeyValueTable<Bits, int> kv(conn, "bitset32_int");
267 Bits key(std::string("10101010101010101010101010101010")); // 32-bit pattern
268 kv.insert_or_assign(key, 31415);
269 ASSERT_FOUND(kv, key, 31415);
270 }
271
272 // --- concurrency smoke test ---
273 std::cout << "[concurrency] start\n";
274 {
275 mdbxc::KeyValueTable<int, ConcurrentStruct> kv(conn, "concurrent_test");
276
277 std::mutex mtx;
278 std::condition_variable cv;
279 std::size_t epoch = 0;
280 std::size_t last_seen = 0;
281 std::atomic<bool> failed(false);
282 std::atomic<bool> done(false);
283 ConcurrentStruct written;
284 std::exception_ptr w_ex, r_ex;
285
286 auto writer = make_thread_catching([&] {
287 try {
288 for (int i = 0; i < 1000; ++i) {
289 if (failed) break;
290 ConcurrentStruct w(i);
291 {
292 std::lock_guard<std::mutex> lock(mtx);
293 kv.insert_or_assign(1, w);
294 written = w;
295 ++epoch;
296 }
297 cv.notify_one();
298 std::this_thread::sleep_for(std::chrono::milliseconds(1));
299 }
300 done.store(true, std::memory_order_release);
301 cv.notify_all();
302 sync_cout() << "[writer] done\n";
303 } catch (const std::exception& ex) {
304 sync_cerr() << "[error] writer: " << ex.what() << "\n";
305 failed = true;
306 done.store(true, std::memory_order_release);
307 cv.notify_all();
308 } catch (...) {
309 sync_cerr() << "[error] writer: unknown\n";
310 failed = true;
311 done.store(true, std::memory_order_release);
312 cv.notify_all();
313 }
314 }, w_ex);
315
316 auto reader = make_thread_catching([&] {
317 try {
318 for (;;) {
319 std::unique_lock<std::mutex> lock(mtx);
320 bool ok = cv.wait_for(lock, std::chrono::seconds(2), [&] {
321 return epoch > last_seen || done.load(std::memory_order_acquire);
322 });
323 if (!ok) {
324 sync_cerr() << "[error] reader timeout (epoch=" << epoch
325 << ", last_seen=" << last_seen << ")\n";
326 failed = true;
327 break;
328 }
329 if (done.load(std::memory_order_acquire) && epoch == last_seen) {
330 break;
331 }
332 last_seen = epoch;
333 ConcurrentStruct expected = written;
334 lock.unlock();
335
336# if __cplusplus >= 201703L
337 auto val = kv.find(1);
338 if (!val || *val != expected) {
339 sync_cerr() << "[error] mismatch: got "
340 << (val ? val->value : -1)
341 << ", expected " << expected.value << "\n";
342 failed = true;
343 break;
344 }
345# else
346 auto val = kv.find_compat(1);
347 if (!val.first || val.second != expected) {
348 sync_cerr() << "[error] mismatch: got "
349 << val.second.value
350 << ", expected " << expected.value << "\n";
351 failed = true;
352 break;
353 }
354# endif
355 }
356 sync_cout() << "[reader] done\n";
357 } catch (const std::exception& ex) {
358 sync_cerr() << "[error] reader: " << ex.what() << "\n";
359 failed = true;
360 } catch (...) {
361 sync_cerr() << "[error] reader: unknown\n";
362 failed = true;
363 }
364 }, r_ex);
365
366 safe_join(writer);
367 safe_join(reader);
368
369 if (w_ex) {
370 try { std::rethrow_exception(w_ex); }
371 catch (const std::exception& e) { sync_cerr() << "[error] writer rethrow: " << e.what() << "\n"; }
372 catch (...) { sync_cerr() << "[error] writer rethrow: unknown\n"; }
373 }
374 if (r_ex) {
375 try { std::rethrow_exception(r_ex); }
376 catch (const std::exception& e) { sync_cerr() << "[error] reader rethrow: " << e.what() << "\n"; }
377 catch (...) { sync_cerr() << "[error] reader rethrow: unknown\n"; }
378 }
379
380 if (w_ex || r_ex) {
381 sync_cerr() << "[concurrency] failed: thread exception\n";
382 return 1;
383 }
384 if (failed) {
385 std::cerr << "[concurrency] failed\n";
386 return 1;
387 }
388
389 std::cout << "[concurrency] ok\n";
390 }
391
392 std::cout << "[result] all tests passed\n";
393 return 0;
394}
Declaration of the KeyValueTable class for managing key-value pairs in an MDBX database.
SyncOStream(const SyncOStream &)=delete
static std::mutex & get_mutex()
SyncOStream(SyncOStream &&other) noexcept
SyncOStream & operator<<(std::ostream &(*manip)(std::ostream &))
SyncOStream & operator=(SyncOStream &&)=delete
SyncOStream & operator<<(const T &value)
SyncOStream(std::ostream &os)
SyncOStream & operator=(const SyncOStream &)=delete
std::ostringstream buffer_
Parameters used by Connection to create the MDBX environment.
Definition Config.hpp:17
bool no_subdir
Whether to store the database in a single file instead of a directory.
Definition Config.hpp:30
std::string pathname
Path to the database file or directory containing the database.
Definition Config.hpp:19
bool relative_to_exe
Whether to resolve a relative path relative to the executable directory.
Definition Config.hpp:33
int64_t max_dbs
Maximum number of named databases (DBI) in the environment.
Definition Config.hpp:27
static std::shared_ptr< Connection > create(const Config &config)
Creates and connects a new shared Connection instance.
Template class for managing key-value pairs in an MDBX database.
void insert_or_assign(const KeyT &key, const ValueT &value, MDBX_txn *txn=nullptr)
Inserts or replaces key-value pair.
std::pair< bool, ValueT > find_compat(const KeyT &key, MDBX_txn *txn=nullptr) const
Finds value by key.
std::pair< bool, ValueT > find(const KeyT &key, MDBX_txn *txn=nullptr) const
Finds value by key.
SyncOStream sync_cerr()
void safe_join(std::thread &t) noexcept
#define ASSERT_FOUND(table, key, expected)
std::thread make_thread_catching(F &&f, std::exception_ptr &out_eptr)
SyncOStream sync_cout()
bool operator!=(const ConcurrentStruct &other) const
static ConcurrentStruct from_bytes(const void *data, size_t size)
bool operator==(const ConcurrentStruct &other) const
ConcurrentStruct()=default
std::vector< uint8_t > to_bytes() const
SimpleStruct(int x_, float y_)
SimpleStruct()=default
bool operator==(const SimpleStruct &other) const
static SimpleStruct from_bytes(const void *data, size_t size)
std::vector< uint8_t > to_bytes() const