1 /* This is free and unencumbered software released into the public domain. */
2 
3 #ifndef LMDBXX_H
4 #define LMDBXX_H
5 
6 /**
7  * <lmdb++.h> - C++11 wrapper for LMDB.
8  *
9  * @author Arto Bendiken <arto@bendiken.net>
10  * @see https://sourceforge.net/projects/lmdbxx/
11  */
12 
13 #ifndef __cplusplus
14 #error "<lmdb++.h> requires a C++ compiler"
15 #endif
16 
17 #if __cplusplus < 201103L
18 #if !defined(_MSC_VER) || _MSC_VER < 1900
19 #error "<lmdb++.h> requires a C++11 compiler (CXXFLAGS='-std=c++11')"
20 #endif // _MSC_VER check
21 #endif
22 
23 ////////////////////////////////////////////////////////////////////////////////
24 
25 #include <lmdb.h>      /* for MDB_*, mdb_*() */
26 
27 #ifdef LMDBXX_DEBUG
28 #include <cassert>     /* for assert() */
29 #endif
30 #include <cstddef>     /* for std::size_t */
31 #include <cstdio>      /* for std::snprintf() */
32 #include <cstring>     /* for std::strlen() */
33 #include <stdexcept>   /* for std::runtime_error */
34 #include <string>      /* for std::string */
35 #include <type_traits> /* for std::is_pod<> */
36 
37 namespace lmdb {
38   using mode = mdb_mode_t;
39 }
40 
41 ////////////////////////////////////////////////////////////////////////////////
42 /* Error Handling */
43 
44 namespace lmdb {
45   class error;
46   class logic_error;
47   class fatal_error;
48   class runtime_error;
49   class key_exist_error;
50   class not_found_error;
51   class corrupted_error;
52   class panic_error;
53   class version_mismatch_error;
54   class map_full_error;
55   class bad_dbi_error;
56 }
57 
58 /**
59  * Base class for LMDB exception conditions.
60  *
61  * @see http://symas.com/mdb/doc/group__errors.html
62  */
63 class lmdb::error : public std::runtime_error {
64 protected:
65   const int _code;
66 
67 public:
68   /**
69    * Throws an error based on the given LMDB return code.
70    */
71   [[noreturn]] static inline void raise(const char* origin, int rc);
72 
73   /**
74    * Constructor.
75    */
error(const char * const origin,const int rc)76   error(const char* const origin,
77         const int rc) noexcept
78     : runtime_error{origin},
79       _code{rc} {}
80 
81   /**
82    * Returns the underlying LMDB error code.
83    */
code()84   int code() const noexcept {
85     return _code;
86   }
87 
88   /**
89    * Returns the origin of the LMDB error.
90    */
origin()91   const char* origin() const noexcept {
92     return runtime_error::what();
93   }
94 
95   /**
96    * Returns the underlying LMDB error code.
97    */
what()98   virtual const char* what() const noexcept {
99 #if !defined(__APPLE__)
100     static thread_local char buffer[1024];
101 #else
102     static char buffer[1024];   /* FIXME: need to provide locking */
103 #endif
104 #if defined(_MSC_VER)
105     snprintf(buffer, sizeof(buffer),
106       "%s: %s", origin(), ::mdb_strerror(code()));
107 #else
108 	std::snprintf(buffer, sizeof(buffer),
109 		"%s: %s", origin(), ::mdb_strerror(code()));
110 #endif
111     return buffer;
112   }
113 };
114 
115 /**
116  * Base class for logic error conditions.
117  */
118 class lmdb::logic_error : public lmdb::error {
119 public:
120   using error::error;
121 };
122 
123 /**
124  * Base class for fatal error conditions.
125  */
126 class lmdb::fatal_error : public lmdb::error {
127 public:
128   using error::error;
129 };
130 
131 /**
132  * Base class for runtime error conditions.
133  */
134 class lmdb::runtime_error : public lmdb::error {
135 public:
136   using error::error;
137 };
138 
139 /**
140  * Exception class for `MDB_KEYEXIST` errors.
141  *
142  * @see http://symas.com/mdb/doc/group__errors.html#ga05dc5bbcc7da81a7345bd8676e8e0e3b
143  */
144 class lmdb::key_exist_error final : public lmdb::runtime_error {
145 public:
146   using runtime_error::runtime_error;
147 };
148 
149 /**
150  * Exception class for `MDB_NOTFOUND` errors.
151  *
152  * @see http://symas.com/mdb/doc/group__errors.html#gabeb52e4c4be21b329e31c4add1b71926
153  */
154 class lmdb::not_found_error final : public lmdb::runtime_error {
155 public:
156   using runtime_error::runtime_error;
157 };
158 
159 /**
160  * Exception class for `MDB_CORRUPTED` errors.
161  *
162  * @see http://symas.com/mdb/doc/group__errors.html#gaf8148bf1b85f58e264e57194bafb03ef
163  */
164 class lmdb::corrupted_error final : public lmdb::fatal_error {
165 public:
166   using fatal_error::fatal_error;
167 };
168 
169 /**
170  * Exception class for `MDB_PANIC` errors.
171  *
172  * @see http://symas.com/mdb/doc/group__errors.html#gae37b9aedcb3767faba3de8c1cf6d3473
173  */
174 class lmdb::panic_error final : public lmdb::fatal_error {
175 public:
176   using fatal_error::fatal_error;
177 };
178 
179 /**
180  * Exception class for `MDB_VERSION_MISMATCH` errors.
181  *
182  * @see http://symas.com/mdb/doc/group__errors.html#ga909b2db047fa90fb0d37a78f86a6f99b
183  */
184 class lmdb::version_mismatch_error final : public lmdb::fatal_error {
185 public:
186   using fatal_error::fatal_error;
187 };
188 
189 /**
190  * Exception class for `MDB_MAP_FULL` errors.
191  *
192  * @see http://symas.com/mdb/doc/group__errors.html#ga0a83370402a060c9175100d4bbfb9f25
193  */
194 class lmdb::map_full_error final : public lmdb::runtime_error {
195 public:
196   using runtime_error::runtime_error;
197 };
198 
199 /**
200  * Exception class for `MDB_BAD_DBI` errors.
201  *
202  * @since 0.9.14 (2014/09/20)
203  * @see http://symas.com/mdb/doc/group__errors.html#gab4c82e050391b60a18a5df08d22a7083
204  */
205 class lmdb::bad_dbi_error final : public lmdb::runtime_error {
206 public:
207   using runtime_error::runtime_error;
208 };
209 
210 inline void
raise(const char * const origin,const int rc)211 lmdb::error::raise(const char* const origin,
212                    const int rc) {
213   switch (rc) {
214     case MDB_KEYEXIST:         throw key_exist_error{origin, rc};
215     case MDB_NOTFOUND:         throw not_found_error{origin, rc};
216     case MDB_CORRUPTED:        throw corrupted_error{origin, rc};
217     case MDB_PANIC:            throw panic_error{origin, rc};
218     case MDB_VERSION_MISMATCH: throw version_mismatch_error{origin, rc};
219     case MDB_MAP_FULL:         throw map_full_error{origin, rc};
220 #ifdef MDB_BAD_DBI
221     case MDB_BAD_DBI:          throw bad_dbi_error{origin, rc};
222 #endif
223     default:                   throw lmdb::runtime_error{origin, rc};
224   }
225 }
226 
227 ////////////////////////////////////////////////////////////////////////////////
228 /* Procedural Interface: Metadata */
229 
230 namespace lmdb {
231   // TODO: mdb_version()
232   // TODO: mdb_strerror()
233 }
234 
235 ////////////////////////////////////////////////////////////////////////////////
236 /* Procedural Interface: Environment */
237 
238 namespace lmdb {
239   static inline void env_create(MDB_env** env);
240   static inline void env_open(MDB_env* env,
241     const char* path, unsigned int flags, mode mode);
242 #if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14)
243   static inline void env_copy(MDB_env* env, const char* path, unsigned int flags);
244   static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd, unsigned int flags);
245 #else
246   static inline void env_copy(MDB_env* env, const char* path);
247   static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd);
248 #endif
249   static inline void env_stat(MDB_env* env, MDB_stat* stat);
250   static inline void env_info(MDB_env* env, MDB_envinfo* stat);
251   static inline void env_sync(MDB_env* env, bool force);
252   static inline void env_close(MDB_env* env) noexcept;
253   static inline void env_set_flags(MDB_env* env, unsigned int flags, bool onoff);
254   static inline void env_get_flags(MDB_env* env, unsigned int* flags);
255   static inline void env_get_path(MDB_env* env, const char** path);
256   static inline void env_get_fd(MDB_env* env, mdb_filehandle_t* fd);
257   static inline void env_set_mapsize(MDB_env* env, std::size_t size);
258   static inline void env_set_max_readers(MDB_env* env, unsigned int count);
259   static inline void env_get_max_readers(MDB_env* env, unsigned int* count);
260   static inline void env_set_max_dbs(MDB_env* env, MDB_dbi count);
261   static inline unsigned int env_get_max_keysize(MDB_env* env);
262 #if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11)
263   static inline void env_set_userctx(MDB_env* env, void* ctx);
264   static inline void* env_get_userctx(MDB_env* env);
265 #endif
266   // TODO: mdb_env_set_assert()
267   // TODO: mdb_reader_list()
268   // TODO: mdb_reader_check()
269 }
270 
271 /**
272  * @throws lmdb::error on failure
273  * @see http://symas.com/mdb/doc/group__mdb.html#gaad6be3d8dcd4ea01f8df436f41d158d4
274  */
275 static inline void
env_create(MDB_env ** env)276 lmdb::env_create(MDB_env** env) {
277   const int rc = ::mdb_env_create(env);
278   if (rc != MDB_SUCCESS) {
279     error::raise("mdb_env_create", rc);
280   }
281 }
282 
283 /**
284  * @throws lmdb::error on failure
285  * @see http://symas.com/mdb/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340
286  */
287 static inline void
env_open(MDB_env * const env,const char * const path,const unsigned int flags,const mode mode)288 lmdb::env_open(MDB_env* const env,
289                const char* const path,
290                const unsigned int flags,
291                const mode mode) {
292   const int rc = ::mdb_env_open(env, path, flags, mode);
293   if (rc != MDB_SUCCESS) {
294     error::raise("mdb_env_open", rc);
295   }
296 }
297 
298 /**
299  * @throws lmdb::error on failure
300  * @see http://symas.com/mdb/doc/group__mdb.html#ga3bf50d7793b36aaddf6b481a44e24244
301  * @see http://symas.com/mdb/doc/group__mdb.html#ga5d51d6130325f7353db0955dbedbc378
302  */
303 static inline void
304 lmdb::env_copy(MDB_env* const env,
305 #if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14)
306                const char* const path,
307                const unsigned int flags = 0) {
308   const int rc = ::mdb_env_copy2(env, path, flags);
309 #else
310                const char* const path) {
311   const int rc = ::mdb_env_copy(env, path);
312 #endif
313   if (rc != MDB_SUCCESS) {
314     error::raise("mdb_env_copy2", rc);
315   }
316 }
317 
318 /**
319  * @throws lmdb::error on failure
320  * @see http://symas.com/mdb/doc/group__mdb.html#ga5040d0de1f14000fa01fc0b522ff1f86
321  * @see http://symas.com/mdb/doc/group__mdb.html#ga470b0bcc64ac417de5de5930f20b1a28
322  */
323 static inline void
324 lmdb::env_copy_fd(MDB_env* const env,
325 #if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14)
326                  const mdb_filehandle_t fd,
327                  const unsigned int flags = 0) {
328   const int rc = ::mdb_env_copyfd2(env, fd, flags);
329 #else
330                  const mdb_filehandle_t fd) {
331   const int rc = ::mdb_env_copyfd(env, fd);
332 #endif
333   if (rc != MDB_SUCCESS) {
334     error::raise("mdb_env_copyfd2", rc);
335   }
336 }
337 
338 /**
339  * @throws lmdb::error on failure
340  * @see http://symas.com/mdb/doc/group__mdb.html#gaf881dca452050efbd434cd16e4bae255
341  */
342 static inline void
env_stat(MDB_env * const env,MDB_stat * const stat)343 lmdb::env_stat(MDB_env* const env,
344                MDB_stat* const stat) {
345   const int rc = ::mdb_env_stat(env, stat);
346   if (rc != MDB_SUCCESS) {
347     error::raise("mdb_env_stat", rc);
348   }
349 }
350 
351 /**
352  * @throws lmdb::error on failure
353  * @see http://symas.com/mdb/doc/group__mdb.html#ga18769362c7e7d6cf91889a028a5c5947
354  */
355 static inline void
env_info(MDB_env * const env,MDB_envinfo * const stat)356 lmdb::env_info(MDB_env* const env,
357                MDB_envinfo* const stat) {
358   const int rc = ::mdb_env_info(env, stat);
359   if (rc != MDB_SUCCESS) {
360     error::raise("mdb_env_info", rc);
361   }
362 }
363 
364 /**
365  * @throws lmdb::error on failure
366  * @see http://symas.com/mdb/doc/group__mdb.html#ga85e61f05aa68b520cc6c3b981dba5037
367  */
368 static inline void
369 lmdb::env_sync(MDB_env* const env,
370                const bool force = true) {
371   const int rc = ::mdb_env_sync(env, force);
372   if (rc != MDB_SUCCESS) {
373     error::raise("mdb_env_sync", rc);
374   }
375 }
376 
377 /**
378  * @see http://symas.com/mdb/doc/group__mdb.html#ga4366c43ada8874588b6a62fbda2d1e95
379  */
380 static inline void
env_close(MDB_env * const env)381 lmdb::env_close(MDB_env* const env) noexcept {
382   ::mdb_env_close(env);
383 }
384 
385 /**
386  * @throws lmdb::error on failure
387  * @see http://symas.com/mdb/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445
388  */
389 static inline void
390 lmdb::env_set_flags(MDB_env* const env,
391                     const unsigned int flags,
392                     const bool onoff = true) {
393   const int rc = ::mdb_env_set_flags(env, flags, onoff ? 1 : 0);
394   if (rc != MDB_SUCCESS) {
395     error::raise("mdb_env_set_flags", rc);
396   }
397 }
398 
399 /**
400  * @throws lmdb::error on failure
401  * @see http://symas.com/mdb/doc/group__mdb.html#ga2733aefc6f50beb49dd0c6eb19b067d9
402  */
403 static inline void
env_get_flags(MDB_env * const env,unsigned int * const flags)404 lmdb::env_get_flags(MDB_env* const env,
405                     unsigned int* const flags) {
406   const int rc = ::mdb_env_get_flags(env, flags);
407   if (rc != MDB_SUCCESS) {
408     error::raise("mdb_env_get_flags", rc);
409   }
410 }
411 
412 /**
413  * @throws lmdb::error on failure
414  * @see http://symas.com/mdb/doc/group__mdb.html#gac699fdd8c4f8013577cb933fb6a757fe
415  */
416 static inline void
env_get_path(MDB_env * const env,const char ** path)417 lmdb::env_get_path(MDB_env* const env,
418                    const char** path) {
419   const int rc = ::mdb_env_get_path(env, path);
420   if (rc != MDB_SUCCESS) {
421     error::raise("mdb_env_get_path", rc);
422   }
423 }
424 
425 /**
426  * @throws lmdb::error on failure
427  * @see http://symas.com/mdb/doc/group__mdb.html#gaf1570e7c0e5a5d860fef1032cec7d5f2
428  */
429 static inline void
env_get_fd(MDB_env * const env,mdb_filehandle_t * const fd)430 lmdb::env_get_fd(MDB_env* const env,
431                  mdb_filehandle_t* const fd) {
432   const int rc = ::mdb_env_get_fd(env, fd);
433   if (rc != MDB_SUCCESS) {
434     error::raise("mdb_env_get_fd", rc);
435   }
436 }
437 
438 /**
439  * @throws lmdb::error on failure
440  * @see http://symas.com/mdb/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5
441  */
442 static inline void
env_set_mapsize(MDB_env * const env,const std::size_t size)443 lmdb::env_set_mapsize(MDB_env* const env,
444                       const std::size_t size) {
445   const int rc = ::mdb_env_set_mapsize(env, size);
446   if (rc != MDB_SUCCESS) {
447     error::raise("mdb_env_set_mapsize", rc);
448   }
449 }
450 
451 /**
452  * @throws lmdb::error on failure
453  * @see http://symas.com/mdb/doc/group__mdb.html#gae687966c24b790630be2a41573fe40e2
454  */
455 static inline void
env_set_max_readers(MDB_env * const env,const unsigned int count)456 lmdb::env_set_max_readers(MDB_env* const env,
457                           const unsigned int count) {
458   const int rc = ::mdb_env_set_maxreaders(env, count);
459   if (rc != MDB_SUCCESS) {
460     error::raise("mdb_env_set_maxreaders", rc);
461   }
462 }
463 
464 /**
465  * @throws lmdb::error on failure
466  * @see http://symas.com/mdb/doc/group__mdb.html#ga70e143cf11760d869f754c9c9956e6cc
467  */
468 static inline void
env_get_max_readers(MDB_env * const env,unsigned int * const count)469 lmdb::env_get_max_readers(MDB_env* const env,
470                           unsigned int* const count) {
471   const int rc = ::mdb_env_get_maxreaders(env, count);
472   if (rc != MDB_SUCCESS) {
473     error::raise("mdb_env_get_maxreaders", rc);
474   }
475 }
476 
477 /**
478  * @throws lmdb::error on failure
479  * @see http://symas.com/mdb/doc/group__mdb.html#gaa2fc2f1f37cb1115e733b62cab2fcdbc
480  */
481 static inline void
env_set_max_dbs(MDB_env * const env,const MDB_dbi count)482 lmdb::env_set_max_dbs(MDB_env* const env,
483                       const MDB_dbi count) {
484   const int rc = ::mdb_env_set_maxdbs(env, count);
485   if (rc != MDB_SUCCESS) {
486     error::raise("mdb_env_set_maxdbs", rc);
487   }
488 }
489 
490 /**
491  * @see http://symas.com/mdb/doc/group__mdb.html#gaaf0be004f33828bf2fb09d77eb3cef94
492  */
493 static inline unsigned int
env_get_max_keysize(MDB_env * const env)494 lmdb::env_get_max_keysize(MDB_env* const env) {
495   const int rc = ::mdb_env_get_maxkeysize(env);
496 #ifdef LMDBXX_DEBUG
497   assert(rc >= 0);
498 #endif
499   return static_cast<unsigned int>(rc);
500 }
501 
502 #if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11)
503 /**
504  * @throws lmdb::error on failure
505  * @since 0.9.11 (2014/01/15)
506  * @see http://symas.com/mdb/doc/group__mdb.html#gaf2fe09eb9c96eeb915a76bf713eecc46
507  */
508 static inline void
env_set_userctx(MDB_env * const env,void * const ctx)509 lmdb::env_set_userctx(MDB_env* const env,
510                       void* const ctx) {
511   const int rc = ::mdb_env_set_userctx(env, ctx);
512   if (rc != MDB_SUCCESS) {
513     error::raise("mdb_env_set_userctx", rc);
514   }
515 }
516 #endif
517 
518 #if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11)
519 /**
520  * @since 0.9.11 (2014/01/15)
521  * @see http://symas.com/mdb/doc/group__mdb.html#ga45df6a4fb150cda2316b5ae224ba52f1
522  */
523 static inline void*
env_get_userctx(MDB_env * const env)524 lmdb::env_get_userctx(MDB_env* const env) {
525   return ::mdb_env_get_userctx(env);
526 }
527 #endif
528 
529 ////////////////////////////////////////////////////////////////////////////////
530 /* Procedural Interface: Transactions */
531 
532 namespace lmdb {
533   static inline void txn_begin(
534     MDB_env* env, MDB_txn* parent, unsigned int flags, MDB_txn** txn);
535   static inline MDB_env* txn_env(MDB_txn* txn) noexcept;
536 #ifdef LMDBXX_TXN_ID
537   static inline std::size_t txn_id(MDB_txn* txn) noexcept;
538 #endif
539   static inline void txn_commit(MDB_txn* txn);
540   static inline void txn_abort(MDB_txn* txn) noexcept;
541   static inline void txn_reset(MDB_txn* txn) noexcept;
542   static inline void txn_renew(MDB_txn* txn);
543 }
544 
545 /**
546  * @throws lmdb::error on failure
547  * @see http://symas.com/mdb/doc/group__mdb.html#gad7ea55da06b77513609efebd44b26920
548  */
549 static inline void
txn_begin(MDB_env * const env,MDB_txn * const parent,const unsigned int flags,MDB_txn ** txn)550 lmdb::txn_begin(MDB_env* const env,
551                 MDB_txn* const parent,
552                 const unsigned int flags,
553                 MDB_txn** txn) {
554   const int rc = ::mdb_txn_begin(env, parent, flags, txn);
555   if (rc != MDB_SUCCESS) {
556     error::raise("mdb_txn_begin", rc);
557   }
558 }
559 
560 /**
561  * @see http://symas.com/mdb/doc/group__mdb.html#gaeb17735b8aaa2938a78a45cab85c06a0
562  */
563 static inline MDB_env*
txn_env(MDB_txn * const txn)564 lmdb::txn_env(MDB_txn* const txn) noexcept {
565   return ::mdb_txn_env(txn);
566 }
567 
568 #ifdef LMDBXX_TXN_ID
569 /**
570  * @note Only available in HEAD, not yet in any 0.9.x release (as of 0.9.16).
571  */
572 static inline std::size_t
txn_id(MDB_txn * const txn)573 lmdb::txn_id(MDB_txn* const txn) noexcept {
574   return ::mdb_txn_id(txn);
575 }
576 #endif
577 
578 /**
579  * @throws lmdb::error on failure
580  * @see http://symas.com/mdb/doc/group__mdb.html#ga846fbd6f46105617ac9f4d76476f6597
581  */
582 static inline void
txn_commit(MDB_txn * const txn)583 lmdb::txn_commit(MDB_txn* const txn) {
584   const int rc = ::mdb_txn_commit(txn);
585   if (rc != MDB_SUCCESS) {
586     error::raise("mdb_txn_commit", rc);
587   }
588 }
589 
590 /**
591  * @see http://symas.com/mdb/doc/group__mdb.html#ga73a5938ae4c3239ee11efa07eb22b882
592  */
593 static inline void
txn_abort(MDB_txn * const txn)594 lmdb::txn_abort(MDB_txn* const txn) noexcept {
595   ::mdb_txn_abort(txn);
596 }
597 
598 /**
599  * @see http://symas.com/mdb/doc/group__mdb.html#ga02b06706f8a66249769503c4e88c56cd
600  */
601 static inline void
txn_reset(MDB_txn * const txn)602 lmdb::txn_reset(MDB_txn* const txn) noexcept {
603   ::mdb_txn_reset(txn);
604 }
605 
606 /**
607  * @throws lmdb::error on failure
608  * @see http://symas.com/mdb/doc/group__mdb.html#ga6c6f917959517ede1c504cf7c720ce6d
609  */
610 static inline void
txn_renew(MDB_txn * const txn)611 lmdb::txn_renew(MDB_txn* const txn) {
612   const int rc = ::mdb_txn_renew(txn);
613   if (rc != MDB_SUCCESS) {
614     error::raise("mdb_txn_renew", rc);
615   }
616 }
617 
618 ////////////////////////////////////////////////////////////////////////////////
619 /* Procedural Interface: Databases */
620 
621 namespace lmdb {
622   static inline void dbi_open(
623     MDB_txn* txn, const char* name, unsigned int flags, MDB_dbi* dbi);
624   static inline void dbi_stat(MDB_txn* txn, MDB_dbi dbi, MDB_stat* stat);
625   static inline void dbi_flags(MDB_txn* txn, MDB_dbi dbi, unsigned int* flags);
626   static inline void dbi_close(MDB_env* env, MDB_dbi dbi) noexcept;
627   static inline void dbi_drop(MDB_txn* txn, MDB_dbi dbi, bool del);
628   static inline void dbi_set_compare(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp);
629   static inline void dbi_set_dupsort(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp);
630   static inline void dbi_set_relfunc(MDB_txn* txn, MDB_dbi dbi, MDB_rel_func* rel);
631   static inline void dbi_set_relctx(MDB_txn* txn, MDB_dbi dbi, void* ctx);
632   static inline bool dbi_get(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data);
633   static inline bool dbi_put(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data, unsigned int flags);
634   static inline bool dbi_del(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, const MDB_val* data);
635   // TODO: mdb_cmp()
636   // TODO: mdb_dcmp()
637 }
638 
639 /**
640  * @throws lmdb::error on failure
641  * @see http://symas.com/mdb/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a
642  */
643 static inline void
dbi_open(MDB_txn * const txn,const char * const name,const unsigned int flags,MDB_dbi * const dbi)644 lmdb::dbi_open(MDB_txn* const txn,
645                const char* const name,
646                const unsigned int flags,
647                MDB_dbi* const dbi) {
648   const int rc = ::mdb_dbi_open(txn, name, flags, dbi);
649   if (rc != MDB_SUCCESS) {
650     error::raise("mdb_dbi_open", rc);
651   }
652 }
653 
654 /**
655  * @throws lmdb::error on failure
656  * @see http://symas.com/mdb/doc/group__mdb.html#gae6c1069febe94299769dbdd032fadef6
657  */
658 static inline void
dbi_stat(MDB_txn * const txn,const MDB_dbi dbi,MDB_stat * const result)659 lmdb::dbi_stat(MDB_txn* const txn,
660                const MDB_dbi dbi,
661                MDB_stat* const result) {
662   const int rc = ::mdb_stat(txn, dbi, result);
663   if (rc != MDB_SUCCESS) {
664     error::raise("mdb_stat", rc);
665   }
666 }
667 
668 /**
669  * @throws lmdb::error on failure
670  * @see http://symas.com/mdb/doc/group__mdb.html#ga95ba4cb721035478a8705e57b91ae4d4
671  */
672 static inline void
dbi_flags(MDB_txn * const txn,const MDB_dbi dbi,unsigned int * const flags)673 lmdb::dbi_flags(MDB_txn* const txn,
674                 const MDB_dbi dbi,
675                 unsigned int* const flags) {
676   const int rc = ::mdb_dbi_flags(txn, dbi, flags);
677   if (rc != MDB_SUCCESS) {
678     error::raise("mdb_dbi_flags", rc);
679   }
680 }
681 
682 /**
683  * @see http://symas.com/mdb/doc/group__mdb.html#ga52dd98d0c542378370cd6b712ff961b5
684  */
685 static inline void
dbi_close(MDB_env * const env,const MDB_dbi dbi)686 lmdb::dbi_close(MDB_env* const env,
687                 const MDB_dbi dbi) noexcept {
688   ::mdb_dbi_close(env, dbi);
689 }
690 
691 /**
692  * @see http://symas.com/mdb/doc/group__mdb.html#gab966fab3840fc54a6571dfb32b00f2db
693  */
694 static inline void
695 lmdb::dbi_drop(MDB_txn* const txn,
696                const MDB_dbi dbi,
697                const bool del = false) {
698   const int rc = ::mdb_drop(txn, dbi, del ? 1 : 0);
699   if (rc != MDB_SUCCESS) {
700     error::raise("mdb_drop", rc);
701   }
702 }
703 
704 /**
705  * @throws lmdb::error on failure
706  * @see http://symas.com/mdb/doc/group__mdb.html#ga68e47ffcf72eceec553c72b1784ee0fe
707  */
708 static inline void
709 lmdb::dbi_set_compare(MDB_txn* const txn,
710                       const MDB_dbi dbi,
711                       MDB_cmp_func* const cmp = nullptr) {
712   const int rc = ::mdb_set_compare(txn, dbi, cmp);
713   if (rc != MDB_SUCCESS) {
714     error::raise("mdb_set_compare", rc);
715   }
716 }
717 
718 /**
719  * @throws lmdb::error on failure
720  * @see http://symas.com/mdb/doc/group__mdb.html#gacef4ec3dab0bbd9bc978b73c19c879ae
721  */
722 static inline void
723 lmdb::dbi_set_dupsort(MDB_txn* const txn,
724                       const MDB_dbi dbi,
725                       MDB_cmp_func* const cmp = nullptr) {
726   const int rc = ::mdb_set_dupsort(txn, dbi, cmp);
727   if (rc != MDB_SUCCESS) {
728     error::raise("mdb_set_dupsort", rc);
729   }
730 }
731 
732 /**
733  * @throws lmdb::error on failure
734  * @see http://symas.com/mdb/doc/group__mdb.html#ga697d82c7afe79f142207ad5adcdebfeb
735  */
736 static inline void
dbi_set_relfunc(MDB_txn * const txn,const MDB_dbi dbi,MDB_rel_func * const rel)737 lmdb::dbi_set_relfunc(MDB_txn* const txn,
738                       const MDB_dbi dbi,
739                       MDB_rel_func* const rel) {
740   const int rc = ::mdb_set_relfunc(txn, dbi, rel);
741   if (rc != MDB_SUCCESS) {
742     error::raise("mdb_set_relfunc", rc);
743   }
744 }
745 
746 /**
747  * @throws lmdb::error on failure
748  * @see http://symas.com/mdb/doc/group__mdb.html#ga7c34246308cee01724a1839a8f5cc594
749  */
750 static inline void
dbi_set_relctx(MDB_txn * const txn,const MDB_dbi dbi,void * const ctx)751 lmdb::dbi_set_relctx(MDB_txn* const txn,
752                      const MDB_dbi dbi,
753                      void* const ctx) {
754   const int rc = ::mdb_set_relctx(txn, dbi, ctx);
755   if (rc != MDB_SUCCESS) {
756     error::raise("mdb_set_relctx", rc);
757   }
758 }
759 
760 /**
761  * @retval true  if the key/value pair was retrieved
762  * @retval false if the key wasn't found
763  * @see http://symas.com/mdb/doc/group__mdb.html#ga8bf10cd91d3f3a83a34d04ce6b07992d
764  */
765 static inline bool
dbi_get(MDB_txn * const txn,const MDB_dbi dbi,const MDB_val * const key,MDB_val * const data)766 lmdb::dbi_get(MDB_txn* const txn,
767               const MDB_dbi dbi,
768               const MDB_val* const key,
769               MDB_val* const data) {
770   const int rc = ::mdb_get(txn, dbi, const_cast<MDB_val*>(key), data);
771   if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
772     error::raise("mdb_get", rc);
773   }
774   return (rc == MDB_SUCCESS);
775 }
776 
777 /**
778  * @retval true  if the key/value pair was inserted
779  * @retval false if the key already existed
780  * @see http://symas.com/mdb/doc/group__mdb.html#ga4fa8573d9236d54687c61827ebf8cac0
781  */
782 static inline bool
783 lmdb::dbi_put(MDB_txn* const txn,
784               const MDB_dbi dbi,
785               const MDB_val* const key,
786               MDB_val* const data,
787               const unsigned int flags = 0) {
788   const int rc = ::mdb_put(txn, dbi, const_cast<MDB_val*>(key), data, flags);
789   if (rc != MDB_SUCCESS && rc != MDB_KEYEXIST) {
790     error::raise("mdb_put", rc);
791   }
792   return (rc == MDB_SUCCESS);
793 }
794 
795 /**
796  * @retval true  if the key/value pair was removed
797  * @retval false if the key wasn't found
798  * @see http://symas.com/mdb/doc/group__mdb.html#gab8182f9360ea69ac0afd4a4eaab1ddb0
799  */
800 static inline bool
801 lmdb::dbi_del(MDB_txn* const txn,
802               const MDB_dbi dbi,
803               const MDB_val* const key,
804               const MDB_val* const data = nullptr) {
805   const int rc = ::mdb_del(txn, dbi, const_cast<MDB_val*>(key), const_cast<MDB_val*>(data));
806   if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
807     error::raise("mdb_del", rc);
808   }
809   return (rc == MDB_SUCCESS);
810 }
811 
812 ////////////////////////////////////////////////////////////////////////////////
813 /* Procedural Interface: Cursors */
814 
815 namespace lmdb {
816   static inline void cursor_open(MDB_txn* txn, MDB_dbi dbi, MDB_cursor** cursor);
817   static inline void cursor_close(MDB_cursor* cursor) noexcept;
818   static inline void cursor_renew(MDB_txn* txn, MDB_cursor* cursor);
819   static inline MDB_txn* cursor_txn(MDB_cursor* cursor) noexcept;
820   static inline MDB_dbi cursor_dbi(MDB_cursor* cursor) noexcept;
821   static inline bool cursor_get(MDB_cursor* cursor, MDB_val* key, MDB_val* data, MDB_cursor_op op);
822   static inline void cursor_put(MDB_cursor* cursor, MDB_val* key, MDB_val* data, unsigned int flags);
823   static inline void cursor_del(MDB_cursor* cursor, unsigned int flags);
824   static inline void cursor_count(MDB_cursor* cursor, std::size_t& count);
825 }
826 
827 /**
828  * @throws lmdb::error on failure
829  * @see http://symas.com/mdb/doc/group__mdb.html#ga9ff5d7bd42557fd5ee235dc1d62613aa
830  */
831 static inline void
cursor_open(MDB_txn * const txn,const MDB_dbi dbi,MDB_cursor ** const cursor)832 lmdb::cursor_open(MDB_txn* const txn,
833                   const MDB_dbi dbi,
834                   MDB_cursor** const cursor) {
835   const int rc = ::mdb_cursor_open(txn, dbi, cursor);
836   if (rc != MDB_SUCCESS) {
837     error::raise("mdb_cursor_open", rc);
838   }
839 }
840 
841 /**
842  * @see http://symas.com/mdb/doc/group__mdb.html#gad685f5d73c052715c7bd859cc4c05188
843  */
844 static inline void
cursor_close(MDB_cursor * const cursor)845 lmdb::cursor_close(MDB_cursor* const cursor) noexcept {
846   ::mdb_cursor_close(cursor);
847 }
848 
849 /**
850  * @throws lmdb::error on failure
851  * @see http://symas.com/mdb/doc/group__mdb.html#gac8b57befb68793070c85ea813df481af
852  */
853 static inline void
cursor_renew(MDB_txn * const txn,MDB_cursor * const cursor)854 lmdb::cursor_renew(MDB_txn* const txn,
855                    MDB_cursor* const cursor) {
856   const int rc = ::mdb_cursor_renew(txn, cursor);
857   if (rc != MDB_SUCCESS) {
858     error::raise("mdb_cursor_renew", rc);
859   }
860 }
861 
862 /**
863  * @see http://symas.com/mdb/doc/group__mdb.html#ga7bf0d458f7f36b5232fcb368ebda79e0
864  */
865 static inline MDB_txn*
cursor_txn(MDB_cursor * const cursor)866 lmdb::cursor_txn(MDB_cursor* const cursor) noexcept {
867   return ::mdb_cursor_txn(cursor);
868 }
869 
870 /**
871  * @see http://symas.com/mdb/doc/group__mdb.html#ga2f7092cf70ee816fb3d2c3267a732372
872  */
873 static inline MDB_dbi
cursor_dbi(MDB_cursor * const cursor)874 lmdb::cursor_dbi(MDB_cursor* const cursor) noexcept {
875   return ::mdb_cursor_dbi(cursor);
876 }
877 
878 /**
879  * @throws lmdb::error on failure
880  * @see http://symas.com/mdb/doc/group__mdb.html#ga48df35fb102536b32dfbb801a47b4cb0
881  */
882 static inline bool
cursor_get(MDB_cursor * const cursor,MDB_val * const key,MDB_val * const data,const MDB_cursor_op op)883 lmdb::cursor_get(MDB_cursor* const cursor,
884                  MDB_val* const key,
885                  MDB_val* const data,
886                  const MDB_cursor_op op) {
887   const int rc = ::mdb_cursor_get(cursor, key, data, op);
888   if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
889     error::raise("mdb_cursor_get", rc);
890   }
891   return (rc == MDB_SUCCESS);
892 }
893 
894 /**
895  * @throws lmdb::error on failure
896  * @see http://symas.com/mdb/doc/group__mdb.html#ga1f83ccb40011837ff37cc32be01ad91e
897  */
898 static inline void
899 lmdb::cursor_put(MDB_cursor* const cursor,
900                  MDB_val* const key,
901                  MDB_val* const data,
902                  const unsigned int flags = 0) {
903   const int rc = ::mdb_cursor_put(cursor, key, data, flags);
904   if (rc != MDB_SUCCESS) {
905     error::raise("mdb_cursor_put", rc);
906   }
907 }
908 
909 /**
910  * @throws lmdb::error on failure
911  * @see http://symas.com/mdb/doc/group__mdb.html#ga26a52d3efcfd72e5bf6bd6960bf75f95
912  */
913 static inline void
914 lmdb::cursor_del(MDB_cursor* const cursor,
915                  const unsigned int flags = 0) {
916   const int rc = ::mdb_cursor_del(cursor, flags);
917   if (rc != MDB_SUCCESS) {
918     error::raise("mdb_cursor_del", rc);
919   }
920 }
921 
922 /**
923  * @throws lmdb::error on failure
924  * @see http://symas.com/mdb/doc/group__mdb.html#ga4041fd1e1862c6b7d5f10590b86ffbe2
925  */
926 static inline void
cursor_count(MDB_cursor * const cursor,std::size_t & count)927 lmdb::cursor_count(MDB_cursor* const cursor,
928                    std::size_t& count) {
929   const int rc = ::mdb_cursor_count(cursor, &count);
930   if (rc != MDB_SUCCESS) {
931     error::raise("mdb_cursor_count", rc);
932   }
933 }
934 
935 ////////////////////////////////////////////////////////////////////////////////
936 /* Resource Interface: Values */
937 
938 namespace lmdb {
939   class val;
940 }
941 
942 /**
943  * Wrapper class for `MDB_val` structures.
944  *
945  * @note Instances of this class are movable and copyable both.
946  * @see http://symas.com/mdb/doc/group__mdb.html#structMDB__val
947  */
948 class lmdb::val {
949 protected:
950   MDB_val _val;
951 
952 public:
953   /**
954    * Default constructor.
955    */
956   val() noexcept = default;
957 
958   /**
959    * Constructor.
960    */
val(const std::string & data)961   val(const std::string& data) noexcept
962     : val{data.data(), data.size()} {}
963 
964   /**
965    * Constructor.
966    */
val(const char * const data)967   val(const char* const data) noexcept
968     : val{data, std::strlen(data)} {}
969 
970   /**
971    * Constructor.
972    */
val(const void * const data,const std::size_t size)973   val(const void* const data,
974       const std::size_t size) noexcept
975     : _val{size, const_cast<void*>(data)} {}
976 
977   /**
978    * Move constructor.
979    */
980   val(val&& other) noexcept = default;
981 
982   /**
983    * Move assignment operator.
984    */
985   val& operator=(val&& other) noexcept = default;
986 
987   /**
988    * Destructor.
989    */
990   ~val() noexcept = default;
991 
992   /**
993    * Returns an `MDB_val*` pointer.
994    */
995   operator MDB_val*() noexcept {
996     return &_val;
997   }
998 
999   /**
1000    * Returns an `MDB_val*` pointer.
1001    */
1002   operator const MDB_val*() const noexcept {
1003     return &_val;
1004   }
1005 
1006   /**
1007    * Determines whether this value is empty.
1008    */
empty()1009   bool empty() const noexcept {
1010     return size() == 0;
1011   }
1012 
1013   /**
1014    * Returns the size of the data.
1015    */
size()1016   std::size_t size() const noexcept {
1017     return _val.mv_size;
1018   }
1019 
1020   /**
1021    * Returns a pointer to the data.
1022    */
1023   template<typename T>
data()1024   T* data() noexcept {
1025     return reinterpret_cast<T*>(_val.mv_data);
1026   }
1027 
1028   /**
1029    * Returns a pointer to the data.
1030    */
1031   template<typename T>
data()1032   const T* data() const noexcept {
1033     return reinterpret_cast<T*>(_val.mv_data);
1034   }
1035 
1036   /**
1037    * Returns a pointer to the data.
1038    */
data()1039   char* data() noexcept {
1040     return reinterpret_cast<char*>(_val.mv_data);
1041   }
1042 
1043   /**
1044    * Returns a pointer to the data.
1045    */
data()1046   const char* data() const noexcept {
1047     return reinterpret_cast<char*>(_val.mv_data);
1048   }
1049 
1050   /**
1051    * Assigns the value.
1052    */
1053   template<typename T>
assign(const T * const data,const std::size_t size)1054   val& assign(const T* const data,
1055               const std::size_t size) noexcept {
1056     _val.mv_size = size;
1057     _val.mv_data = const_cast<void*>(reinterpret_cast<const void*>(data));
1058     return *this;
1059   }
1060 
1061   /**
1062    * Assigns the value.
1063    */
assign(const char * const data)1064   val& assign(const char* const data) noexcept {
1065     return assign(data, std::strlen(data));
1066   }
1067 
1068   /**
1069    * Assigns the value.
1070    */
assign(const std::string & data)1071   val& assign(const std::string& data) noexcept {
1072     return assign(data.data(), data.size());
1073   }
1074 };
1075 
1076 #if !(defined(__COVERITY__) || defined(_MSC_VER))
1077 static_assert(std::is_pod<lmdb::val>::value, "lmdb::val must be a POD type");
1078 static_assert(sizeof(lmdb::val) == sizeof(MDB_val), "sizeof(lmdb::val) != sizeof(MDB_val)");
1079 #endif
1080 
1081 ////////////////////////////////////////////////////////////////////////////////
1082 /* Resource Interface: Environment */
1083 
1084 namespace lmdb {
1085   class env;
1086 }
1087 
1088 /**
1089  * Resource class for `MDB_env*` handles.
1090  *
1091  * @note Instances of this class are movable, but not copyable.
1092  * @see http://symas.com/mdb/doc/group__internal.html#structMDB__env
1093  */
1094 class lmdb::env {
1095 protected:
1096   MDB_env* _handle{nullptr};
1097 
1098 public:
1099   static constexpr unsigned int default_flags = 0;
1100   static constexpr mode default_mode = 0644; /* -rw-r--r-- */
1101 
1102   /**
1103    * Creates a new LMDB environment.
1104    *
1105    * @param flags
1106    * @throws lmdb::error on failure
1107    */
1108   static env create(const unsigned int flags = default_flags) {
1109     MDB_env* handle{nullptr};
1110     lmdb::env_create(&handle);
1111 #ifdef LMDBXX_DEBUG
1112     assert(handle != nullptr);
1113 #endif
1114     if (flags) {
1115       try {
1116         lmdb::env_set_flags(handle, flags);
1117       }
catch(const lmdb::error &)1118       catch (const lmdb::error&) {
1119         lmdb::env_close(handle);
1120         throw;
1121       }
1122     }
1123     return env{handle};
1124   }
1125 
1126   /**
1127    * Constructor.
1128    *
1129    * @param handle a valid `MDB_env*` handle
1130    */
env(MDB_env * const handle)1131   env(MDB_env* const handle) noexcept
1132     : _handle{handle} {}
1133 
1134   /**
1135    * Move constructor.
1136    */
env(env && other)1137   env(env&& other) noexcept {
1138     std::swap(_handle, other._handle);
1139   }
1140 
1141   /**
1142    * Move assignment operator.
1143    */
1144   env& operator=(env&& other) noexcept {
1145     if (this != &other) {
1146       std::swap(_handle, other._handle);
1147     }
1148     return *this;
1149   }
1150 
1151   /**
1152    * Destructor.
1153    */
~env()1154   ~env() noexcept {
1155     try { close(); } catch (...) {}
1156   }
1157 
1158   /**
1159    * Returns the underlying `MDB_env*` handle.
1160    */
1161   operator MDB_env*() const noexcept {
1162     return _handle;
1163   }
1164 
1165   /**
1166    * Returns the underlying `MDB_env*` handle.
1167    */
handle()1168   MDB_env* handle() const noexcept {
1169     return _handle;
1170   }
1171 
1172   /**
1173    * Flushes data buffers to disk.
1174    *
1175    * @param force
1176    * @throws lmdb::error on failure
1177    */
1178   void sync(const bool force = true) {
1179     lmdb::env_sync(handle(), force);
1180   }
1181 
1182   /**
1183    * Closes this environment, releasing the memory map.
1184    *
1185    * @note this method is idempotent
1186    * @post `handle() == nullptr`
1187    */
close()1188   void close() noexcept {
1189     if (handle()) {
1190       lmdb::env_close(handle());
1191       _handle = nullptr;
1192     }
1193   }
1194 
1195   /**
1196    * Opens this environment.
1197    *
1198    * @param path
1199    * @param flags
1200    * @param mode
1201    * @throws lmdb::error on failure
1202    */
1203   env& open(const char* const path,
1204             const unsigned int flags = default_flags,
1205             const mode mode = default_mode) {
1206     lmdb::env_open(handle(), path, flags, mode);
1207     return *this;
1208   }
1209 
1210   /**
1211    * @param flags
1212    * @param onoff
1213    * @throws lmdb::error on failure
1214    */
1215   env& set_flags(const unsigned int flags,
1216                  const bool onoff = true) {
1217     lmdb::env_set_flags(handle(), flags, onoff);
1218     return *this;
1219   }
1220 
1221   /**
1222    * @param size
1223    * @throws lmdb::error on failure
1224    */
set_mapsize(const std::size_t size)1225   env& set_mapsize(const std::size_t size) {
1226     lmdb::env_set_mapsize(handle(), size);
1227     return *this;
1228   }
1229 
1230   /**
1231    * @param count
1232    * @throws lmdb::error on failure
1233    */
set_max_readers(const unsigned int count)1234   env& set_max_readers(const unsigned int count) {
1235     lmdb::env_set_max_readers(handle(), count);
1236     return *this;
1237   }
1238 
1239   /**
1240    * @param count
1241    * @throws lmdb::error on failure
1242    */
set_max_dbs(const MDB_dbi count)1243   env& set_max_dbs(const MDB_dbi count) {
1244     lmdb::env_set_max_dbs(handle(), count);
1245     return *this;
1246   }
1247 };
1248 
1249 ////////////////////////////////////////////////////////////////////////////////
1250 /* Resource Interface: Transactions */
1251 
1252 namespace lmdb {
1253   class txn;
1254 }
1255 
1256 /**
1257  * Resource class for `MDB_txn*` handles.
1258  *
1259  * @note Instances of this class are movable, but not copyable.
1260  * @see http://symas.com/mdb/doc/group__internal.html#structMDB__txn
1261  */
1262 class lmdb::txn {
1263 protected:
1264   MDB_txn* _handle{nullptr};
1265 
1266 public:
1267   static constexpr unsigned int default_flags = 0;
1268 
1269   /**
1270    * Creates a new LMDB transaction.
1271    *
1272    * @param env the environment handle
1273    * @param parent
1274    * @param flags
1275    * @throws lmdb::error on failure
1276    */
1277   static txn begin(MDB_env* const env,
1278                    MDB_txn* const parent = nullptr,
1279                    const unsigned int flags = default_flags) {
1280     MDB_txn* handle{nullptr};
1281     lmdb::txn_begin(env, parent, flags, &handle);
1282 #ifdef LMDBXX_DEBUG
1283     assert(handle != nullptr);
1284 #endif
1285     return txn{handle};
1286   }
1287 
1288   /**
1289    * Constructor.
1290    *
1291    * @param handle a valid `MDB_txn*` handle
1292    */
txn(MDB_txn * const handle)1293   txn(MDB_txn* const handle) noexcept
1294     : _handle{handle} {}
1295 
1296   /**
1297    * Move constructor.
1298    */
txn(txn && other)1299   txn(txn&& other) noexcept {
1300     std::swap(_handle, other._handle);
1301   }
1302 
1303   /**
1304    * Move assignment operator.
1305    */
1306   txn& operator=(txn&& other) noexcept {
1307     if (this != &other) {
1308       std::swap(_handle, other._handle);
1309     }
1310     return *this;
1311   }
1312 
1313   /**
1314    * Destructor.
1315    */
~txn()1316   ~txn() noexcept {
1317     if (_handle) {
1318       try { abort(); } catch (...) {}
1319       _handle = nullptr;
1320     }
1321   }
1322 
1323   /**
1324    * Returns the underlying `MDB_txn*` handle.
1325    */
1326   operator MDB_txn*() const noexcept {
1327     return _handle;
1328   }
1329 
1330   /**
1331    * Returns the underlying `MDB_txn*` handle.
1332    */
handle()1333   MDB_txn* handle() const noexcept {
1334     return _handle;
1335   }
1336 
1337   /**
1338    * Returns the transaction's `MDB_env*` handle.
1339    */
env()1340   MDB_env* env() const noexcept {
1341     return lmdb::txn_env(handle());
1342   }
1343 
1344   /**
1345    * Commits this transaction.
1346    *
1347    * @throws lmdb::error on failure
1348    * @post `handle() == nullptr`
1349    */
commit()1350   void commit() {
1351     lmdb::txn_commit(_handle);
1352     _handle = nullptr;
1353   }
1354 
1355   /**
1356    * Aborts this transaction.
1357    *
1358    * @post `handle() == nullptr`
1359    */
abort()1360   void abort() noexcept {
1361     lmdb::txn_abort(_handle);
1362     _handle = nullptr;
1363   }
1364 
1365   /**
1366    * Resets this read-only transaction.
1367    */
reset()1368   void reset() noexcept {
1369     lmdb::txn_reset(_handle);
1370   }
1371 
1372   /**
1373    * Renews this read-only transaction.
1374    *
1375    * @throws lmdb::error on failure
1376    */
renew()1377   void renew() {
1378     lmdb::txn_renew(_handle);
1379   }
1380 };
1381 
1382 ////////////////////////////////////////////////////////////////////////////////
1383 /* Resource Interface: Databases */
1384 
1385 namespace lmdb {
1386   class dbi;
1387 }
1388 
1389 /**
1390  * Resource class for `MDB_dbi` handles.
1391  *
1392  * @note Instances of this class are movable, but not copyable.
1393  * @see http://symas.com/mdb/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b
1394  */
1395 class lmdb::dbi {
1396 protected:
1397   MDB_dbi _handle{0};
1398 
1399 public:
1400   static constexpr unsigned int default_flags     = 0;
1401   static constexpr unsigned int default_put_flags = 0;
1402 
1403   /**
1404    * Opens a database handle.
1405    *
1406    * @param txn the transaction handle
1407    * @param name
1408    * @param flags
1409    * @throws lmdb::error on failure
1410    */
1411   static dbi
1412   open(MDB_txn* const txn,
1413        const char* const name = nullptr,
1414        const unsigned int flags = default_flags) {
1415     MDB_dbi handle{};
1416     lmdb::dbi_open(txn, name, flags, &handle);
1417     return dbi{handle};
1418   }
1419 
1420   /**
1421    * Constructor.
1422    *
1423    * @param handle a valid `MDB_dbi` handle
1424    */
dbi(const MDB_dbi handle)1425   dbi(const MDB_dbi handle) noexcept
1426     : _handle{handle} {}
1427 
1428   /**
1429    * Move constructor.
1430    */
dbi(dbi && other)1431   dbi(dbi&& other) noexcept {
1432     std::swap(_handle, other._handle);
1433   }
1434 
1435   /**
1436    * Move assignment operator.
1437    */
1438   dbi& operator=(dbi&& other) noexcept {
1439     if (this != &other) {
1440       std::swap(_handle, other._handle);
1441     }
1442     return *this;
1443   }
1444 
1445   /**
1446    * Destructor.
1447    */
~dbi()1448   ~dbi() noexcept {
1449     if (_handle) {
1450       /* No need to call close() here. */
1451     }
1452   }
1453 
1454   /**
1455    * Returns the underlying `MDB_dbi` handle.
1456    */
MDB_dbi()1457   operator MDB_dbi() const noexcept {
1458     return _handle;
1459   }
1460 
1461   /**
1462    * Returns the underlying `MDB_dbi` handle.
1463    */
handle()1464   MDB_dbi handle() const noexcept {
1465     return _handle;
1466   }
1467 
1468   /**
1469    * Returns statistics for this database.
1470    *
1471    * @param txn a transaction handle
1472    * @throws lmdb::error on failure
1473    */
stat(MDB_txn * const txn)1474   MDB_stat stat(MDB_txn* const txn) const {
1475     MDB_stat result;
1476     lmdb::dbi_stat(txn, handle(), &result);
1477     return result;
1478   }
1479 
1480   /**
1481    * Retrieves the flags for this database handle.
1482    *
1483    * @param txn a transaction handle
1484    * @throws lmdb::error on failure
1485    */
flags(MDB_txn * const txn)1486   unsigned int flags(MDB_txn* const txn) const {
1487     unsigned int result{};
1488     lmdb::dbi_flags(txn, handle(), &result);
1489     return result;
1490   }
1491 
1492   /**
1493    * Returns the number of records in this database.
1494    *
1495    * @param txn a transaction handle
1496    * @throws lmdb::error on failure
1497    */
size(MDB_txn * const txn)1498   std::size_t size(MDB_txn* const txn) const {
1499     return stat(txn).ms_entries;
1500   }
1501 
1502   /**
1503    * @param txn a transaction handle
1504    * @param del
1505    * @throws lmdb::error on failure
1506    */
1507   void drop(MDB_txn* const txn,
1508             const bool del = false) {
1509     lmdb::dbi_drop(txn, handle(), del);
1510   }
1511 
1512   /**
1513    * Sets a custom key comparison function for this database.
1514    *
1515    * @param txn a transaction handle
1516    * @param cmp the comparison function
1517    * @throws lmdb::error on failure
1518    */
1519   dbi& set_compare(MDB_txn* const txn,
1520                    MDB_cmp_func* const cmp = nullptr) {
1521     lmdb::dbi_set_compare(txn, handle(), cmp);
1522     return *this;
1523   }
1524 
1525   /**
1526    * Retrieves a key/value pair from this database.
1527    *
1528    * @param txn a transaction handle
1529    * @param key
1530    * @param data
1531    * @throws lmdb::error on failure
1532    */
get(MDB_txn * const txn,const val & key,val & data)1533   bool get(MDB_txn* const txn,
1534            const val& key,
1535            val& data) {
1536     return lmdb::dbi_get(txn, handle(), key, data);
1537   }
1538 
1539   /**
1540    * Retrieves a key from this database.
1541    *
1542    * @param txn a transaction handle
1543    * @param key
1544    * @throws lmdb::error on failure
1545    */
1546   template<typename K>
get(MDB_txn * const txn,const K & key)1547   bool get(MDB_txn* const txn,
1548            const K& key) const {
1549     const lmdb::val k{&key, sizeof(K)};
1550     lmdb::val v{};
1551     return lmdb::dbi_get(txn, handle(), k, v);
1552   }
1553 
1554   /**
1555    * Retrieves a key/value pair from this database.
1556    *
1557    * @param txn a transaction handle
1558    * @param key
1559    * @param val
1560    * @throws lmdb::error on failure
1561    */
1562   template<typename K, typename V>
get(MDB_txn * const txn,const K & key,V & val)1563   bool get(MDB_txn* const txn,
1564            const K& key,
1565            V& val) const {
1566     const lmdb::val k{&key, sizeof(K)};
1567     lmdb::val v{};
1568     const bool result = lmdb::dbi_get(txn, handle(), k, v);
1569     if (result) {
1570       val = *v.data<const V>();
1571     }
1572     return result;
1573   }
1574 
1575   /**
1576    * Retrieves a key/value pair from this database.
1577    *
1578    * @param txn a transaction handle
1579    * @param key a NUL-terminated string key
1580    * @param val
1581    * @throws lmdb::error on failure
1582    */
1583   template<typename V>
get(MDB_txn * const txn,const char * const key,V & val)1584   bool get(MDB_txn* const txn,
1585            const char* const key,
1586            V& val) const {
1587     const lmdb::val k{key, std::strlen(key)};
1588     lmdb::val v{};
1589     const bool result = lmdb::dbi_get(txn, handle(), k, v);
1590     if (result) {
1591       val = *v.data<const V>();
1592     }
1593     return result;
1594   }
1595 
1596   /**
1597    * Stores a key/value pair into this database.
1598    *
1599    * @param txn a transaction handle
1600    * @param key
1601    * @param data
1602    * @param flags
1603    * @throws lmdb::error on failure
1604    */
1605   bool put(MDB_txn* const txn,
1606            const val& key,
1607            val& data,
1608            const unsigned int flags = default_put_flags) {
1609     return lmdb::dbi_put(txn, handle(), key, data, flags);
1610   }
1611 
1612   /**
1613    * Stores a key into this database.
1614    *
1615    * @param txn a transaction handle
1616    * @param key
1617    * @param flags
1618    * @throws lmdb::error on failure
1619    */
1620   template<typename K>
1621   bool put(MDB_txn* const txn,
1622            const K& key,
1623            const unsigned int flags = default_put_flags) {
1624     const lmdb::val k{&key, sizeof(K)};
1625     lmdb::val v{};
1626     return lmdb::dbi_put(txn, handle(), k, v, flags);
1627   }
1628 
1629   /**
1630    * Stores a key/value pair into this database.
1631    *
1632    * @param txn a transaction handle
1633    * @param key
1634    * @param val
1635    * @param flags
1636    * @throws lmdb::error on failure
1637    */
1638   template<typename K, typename V>
1639   bool put(MDB_txn* const txn,
1640            const K& key,
1641            const V& val,
1642            const unsigned int flags = default_put_flags) {
1643     const lmdb::val k{&key, sizeof(K)};
1644     lmdb::val v{&val, sizeof(V)};
1645     return lmdb::dbi_put(txn, handle(), k, v, flags);
1646   }
1647 
1648   /**
1649    * Stores a key/value pair into this database.
1650    *
1651    * @param txn a transaction handle
1652    * @param key a NUL-terminated string key
1653    * @param val
1654    * @param flags
1655    * @throws lmdb::error on failure
1656    */
1657   template<typename V>
1658   bool put(MDB_txn* const txn,
1659            const char* const key,
1660            const V& val,
1661            const unsigned int flags = default_put_flags) {
1662     const lmdb::val k{key, std::strlen(key)};
1663     lmdb::val v{&val, sizeof(V)};
1664     return lmdb::dbi_put(txn, handle(), k, v, flags);
1665   }
1666 
1667   /**
1668    * Stores a key/value pair into this database.
1669    *
1670    * @param txn a transaction handle
1671    * @param key a NUL-terminated string key
1672    * @param val a NUL-terminated string key
1673    * @param flags
1674    * @throws lmdb::error on failure
1675    */
1676   bool put(MDB_txn* const txn,
1677            const char* const key,
1678            const char* const val,
1679            const unsigned int flags = default_put_flags) {
1680     const lmdb::val k{key, std::strlen(key)};
1681     lmdb::val v{val, std::strlen(val)};
1682     return lmdb::dbi_put(txn, handle(), k, v, flags);
1683   }
1684 
1685   /**
1686    * Removes a key/value pair from this database.
1687    *
1688    * @param txn a transaction handle
1689    * @param key
1690    * @throws lmdb::error on failure
1691    */
del(MDB_txn * const txn,const val & key)1692   bool del(MDB_txn* const txn,
1693            const val& key) {
1694     return lmdb::dbi_del(txn, handle(), key);
1695   }
1696 
1697   /**
1698    * Removes a key/value pair from this database.
1699    *
1700    * @param txn a transaction handle
1701    * @param key
1702    * @throws lmdb::error on failure
1703    */
1704   template<typename K>
del(MDB_txn * const txn,const K & key)1705   bool del(MDB_txn* const txn,
1706            const K& key) {
1707     const lmdb::val k{&key, sizeof(K)};
1708     return lmdb::dbi_del(txn, handle(), k);
1709   }
1710 };
1711 
1712 ////////////////////////////////////////////////////////////////////////////////
1713 /* Resource Interface: Cursors */
1714 
1715 namespace lmdb {
1716   class cursor;
1717 }
1718 
1719 /**
1720  * Resource class for `MDB_cursor*` handles.
1721  *
1722  * @note Instances of this class are movable, but not copyable.
1723  * @see http://symas.com/mdb/doc/group__internal.html#structMDB__cursor
1724  */
1725 class lmdb::cursor {
1726 protected:
1727   MDB_cursor* _handle{nullptr};
1728 
1729 public:
1730   static constexpr unsigned int default_flags = 0;
1731 
1732   /**
1733    * Creates an LMDB cursor.
1734    *
1735    * @param txn the transaction handle
1736    * @param dbi the database handle
1737    * @throws lmdb::error on failure
1738    */
1739   static cursor
open(MDB_txn * const txn,const MDB_dbi dbi)1740   open(MDB_txn* const txn,
1741        const MDB_dbi dbi) {
1742     MDB_cursor* handle{};
1743     lmdb::cursor_open(txn, dbi, &handle);
1744 #ifdef LMDBXX_DEBUG
1745     assert(handle != nullptr);
1746 #endif
1747     return cursor{handle};
1748   }
1749 
1750   /**
1751    * Constructor.
1752    *
1753    * @param handle a valid `MDB_cursor*` handle
1754    */
cursor(MDB_cursor * const handle)1755   cursor(MDB_cursor* const handle) noexcept
1756     : _handle{handle} {}
1757 
1758   /**
1759    * Move constructor.
1760    */
cursor(cursor && other)1761   cursor(cursor&& other) noexcept {
1762     std::swap(_handle, other._handle);
1763   }
1764 
1765   /**
1766    * Move assignment operator.
1767    */
1768   cursor& operator=(cursor&& other) noexcept {
1769     if (this != &other) {
1770       std::swap(_handle, other._handle);
1771     }
1772     return *this;
1773   }
1774 
1775   /**
1776    * Destructor.
1777    */
~cursor()1778   ~cursor() noexcept {
1779     try { close(); } catch (...) {}
1780   }
1781 
1782   /**
1783    * Returns the underlying `MDB_cursor*` handle.
1784    */
1785   operator MDB_cursor*() const noexcept {
1786     return _handle;
1787   }
1788 
1789   /**
1790    * Returns the underlying `MDB_cursor*` handle.
1791    */
handle()1792   MDB_cursor* handle() const noexcept {
1793     return _handle;
1794   }
1795 
1796   /**
1797    * Closes this cursor.
1798    *
1799    * @note this method is idempotent
1800    * @post `handle() == nullptr`
1801    */
close()1802   void close() noexcept {
1803     if (_handle) {
1804       lmdb::cursor_close(_handle);
1805       _handle = nullptr;
1806     }
1807   }
1808 
1809   /**
1810    * Renews this cursor.
1811    *
1812    * @param txn the transaction scope
1813    * @throws lmdb::error on failure
1814    */
renew(MDB_txn * const txn)1815   void renew(MDB_txn* const txn) {
1816     lmdb::cursor_renew(txn, handle());
1817   }
1818 
1819   /**
1820    * Returns the cursor's transaction handle.
1821    */
txn()1822   MDB_txn* txn() const noexcept {
1823     return lmdb::cursor_txn(handle());
1824   }
1825 
1826   /**
1827    * Returns the cursor's database handle.
1828    */
dbi()1829   MDB_dbi dbi() const noexcept {
1830     return lmdb::cursor_dbi(handle());
1831   }
1832 
1833   /**
1834    * Retrieves a key from the database.
1835    *
1836    * @param key
1837    * @param op
1838    * @throws lmdb::error on failure
1839    */
get(MDB_val * const key,const MDB_cursor_op op)1840   bool get(MDB_val* const key,
1841            const MDB_cursor_op op) {
1842     return get(key, nullptr, op);
1843   }
1844 
1845   /**
1846    * Retrieves a key from the database.
1847    *
1848    * @param key
1849    * @param op
1850    * @throws lmdb::error on failure
1851    */
get(lmdb::val & key,const MDB_cursor_op op)1852   bool get(lmdb::val& key,
1853            const MDB_cursor_op op) {
1854     return get(key, nullptr, op);
1855   }
1856 
1857   /**
1858    * Retrieves a key/value pair from the database.
1859    *
1860    * @param key
1861    * @param val (may be `nullptr`)
1862    * @param op
1863    * @throws lmdb::error on failure
1864    */
get(MDB_val * const key,MDB_val * const val,const MDB_cursor_op op)1865   bool get(MDB_val* const key,
1866            MDB_val* const val,
1867            const MDB_cursor_op op) {
1868     return lmdb::cursor_get(handle(), key, val, op);
1869   }
1870 
1871   /**
1872    * Retrieves a key/value pair from the database.
1873    *
1874    * @param key
1875    * @param val
1876    * @param op
1877    * @throws lmdb::error on failure
1878    */
get(lmdb::val & key,lmdb::val & val,const MDB_cursor_op op)1879   bool get(lmdb::val& key,
1880            lmdb::val& val,
1881            const MDB_cursor_op op) {
1882     return lmdb::cursor_get(handle(), key, val, op);
1883   }
1884 
1885   /**
1886    * Retrieves a key/value pair from the database.
1887    *
1888    * @param key
1889    * @param val
1890    * @param op
1891    * @throws lmdb::error on failure
1892    */
get(std::string & key,std::string & val,const MDB_cursor_op op)1893   bool get(std::string& key,
1894            std::string& val,
1895            const MDB_cursor_op op) {
1896     lmdb::val k{}, v{};
1897     const bool found = get(k, v, op);
1898     if (found) {
1899       key.assign(k.data(), k.size());
1900       val.assign(v.data(), v.size());
1901     }
1902     return found;
1903   }
1904 
1905   /**
1906    * Positions this cursor at the given key.
1907    *
1908    * @param key
1909    * @param op
1910    * @throws lmdb::error on failure
1911    */
1912   template<typename K>
1913   bool find(const K& key,
1914             const MDB_cursor_op op = MDB_SET) {
1915     lmdb::val k{&key, sizeof(K)};
1916     return get(k, nullptr, op);
1917   }
1918 };
1919 
1920 ////////////////////////////////////////////////////////////////////////////////
1921 
1922 #endif /* LMDBXX_H */
1923