diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e950645
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,70 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Sphinx documentation
+docs/build/
+
+# pyenv
+.python-version
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+venv3/
+venv-doc/
+
+# mypy
+.mypy_cache/
+
+# IDE
+.idea
+
+# Archive
+obsolete/
+
+# Project specific
+messages/
+example/
+benchmarks/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8992dbe
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,40 @@
+matrix:
+ include:
+ - language: python
+ python:
+ - "3.5"
+ - "3.6"
+ addons:
+ apt:
+ packages:
+ before_script:
+ - cd py
+ install:
+ - pip3 install -e .[dev]
+ - pip3 install coveralls
+ script:
+ - python3 precommit.py
+ - coveralls
+ - language: cpp
+ os: linux
+ dist: xenial
+ compiler:
+ - clang
+ - gcc
+ addons:
+ apt:
+ sources:
+ - ubuntu-toolchain-r-test
+ - llvm-toolchain-precise-3.8
+ packages:
+ - g++-6
+ - clang-3.8
+ install:
+ - make get-deps
+ - "[ $CXX = g++ ] && export CXX=g++-6 || true"
+ - "[ $CXX = clang++ ] && export CXX=clang++-3.8 || true"
+ before_script:
+ - cd cpp
+ script:
+ - make
+ - make test
\ No newline at end of file
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..43bb01b
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,3 @@
+1.0.0
+=====
+* Initial version
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..706450c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Parquery AG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index fab3032..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# persipubsub
-TODO
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..b794c08
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,367 @@
+persipubsub
+===========
+
+.. image:: https://api.travis-ci.com/Parquery/persipubsub.svg?branch=master
+ :target: https://api.travis-ci.com/Parquery/persipubsub.svg?branch=master
+ :alt: Build Status
+
+.. image:: https://coveralls.io/repos/github/Parquery/persipubsub/badge.svg?branch=master
+ :target: https://coveralls.io/github/Parquery/persipubsub?branch=master
+ :alt: Coverage
+
+.. image:: https://readthedocs.org/projects/persipubsub/badge/?version=latest
+ :target: https://persipubsub.readthedocs.io/en/latest/?badge=latest
+ :alt: Documentation Status
+
+.. image:: https://badge.fury.io/py/persipubsub.svg
+ :target: https://badge.fury.io/py/persipubsub
+ :alt: PyPI - version
+
+.. image:: https://img.shields.io/pypi/pyversions/persipubsub.svg
+ :alt: PyPI - Python Version
+
+.. image:: https://badges.frapsoft.com/os/mit/mit.png?v=103
+ :target: https://opensource.org/licenses/mit-license.php
+ :alt: MIT License
+
+``persipubsub`` implements a persistent, thread-safe and process-safe queue for
+inter-process communication, based on `lmdb `_.
+
+Primarily, we used `zeromq `_ for inter-process
+communication with a slight improvement through `persizmq
+`_. This still did not fulfill the level
+of persistence we wanted.
+
+Our motivation was to replace our previous library with a one which is
+similarly easy to setup and to use. Additionally, it should make it possible to
+send `protobuf `_ messages
+(bytes) thread-safely and process-safely from many publishers to many
+subscribers.
+
+Besides basic publisher and subscriber classes the library offers control
+methods for easy deployment from a config JSON file and maintenance in case
+needed.
+
+Related projects
+================
+
+persist-queue
+-------------
+
+* The library offers not all functionality expected from a queue. It has put
+ and get function which are basically only push and pop. Therefore ``front``
+ functionality is missing. In consequence neither can the queue have multiple
+ subscribers nor can be guaranteed that no data is lost when a thread fails.
+* All messages in queues are serialized by ``pickle`` which was for us the main
+ reason not to use this library.
+
+Kafka
+-----
+
+* Hence we only need Inter Process Communication, the TCP ability of `Kafka
+ `_ is an unnecessary overhead.
+* Integration of ``Kafka`` written in Scala and Java in our C++/Python/Go
+ codebase is challenging.
+* Hard to setup and maintain ``Kafka``.
+* broker system eventually a bottleneck.
+
+RabbitMQ
+--------
+
+* Hence we only need Inter Process Communication, the TCP ability of `RabbitMQ
+ `_ is an unnecessary overhead.
+* broker system eventually a bottleneck.
+* ``RabbitMQ`` is less scalable than ``Kafka``, but
+ is supported officially by more languages.
+
+zeromq persistence pattern
+--------------------------
+
+* `Titanic `_ is the only persistence
+ pattern of `zeromq `_ which is also a broker system.
+ This takes away the purpose and advantage of ``zeromq`` to be a
+ lightweight library which requires no broker.
+
+Usage
+=====
+
+The usage of the library consists of two steps: deployment and runtime
+
+Python
+======
+
+Environment
+-----------
+
+To improve the accessibility of the library, an environment class lets you
+create and initialize any ``persipubsub`` component which you need in
+deployment or runtime step.
+
+.. warning::
+
+ Only one environment of each queue per process allowed and it's forbidden to
+ fork environment or any child components to multiple processes.
+ In that case, persipubsub is multi-threading and multi-process safe.
+ If multiple environments of the same queue are active on the same process,
+ or environment is forked to multiple processes the lock is broken and
+ correctness can't be guaranteed.
+
+Initialize environment
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+ import persipubsub.environment
+
+ env = persipubsub.environment.new_environment(path="/home/user/queue/")
+
+Deployment
+----------
+
+In the deployment stage the library sets up the queue structure with the control.
+
+Control
+^^^^^^^
+
+A control unit to initialize and maintain queues.
+
+.. note::
+
+ The high water marks are limits for the queue. The message is deleted in
+ case that it reaches the timeout. In the other case of an overflow one
+ of two strategies is used to prune half of the queue. The choice is between
+ prune_first, which deletes the oldest messages, and prune_last, which
+ deletes the latest messages.
+
+Initialize queue
+""""""""""""""""
+
+.. code-block:: python
+
+ import persipubsub.environment
+ import persipubsub.queue
+
+ env = persipubsub.environment.new_environment(path="/home/user/new-queue/")
+
+ # Initialize a queue with default values.
+ control = env.new_control()
+ # Or define all optional parameters of the queue.
+ hwm = persipubsub.queue._HighWaterMark()
+ strategy = persipubsub.queue._Strategy.prune_first
+ control = env.new_control(subscriber_ids=["sub1", "sub2"],
+ high_watermark=hwm,
+ strategy=strategy)
+
+Prune all dangling messages
+"""""""""""""""""""""""""""
+
+.. code-block:: python
+
+ import persipubsub.environment
+
+ env = persipubsub.environment.new_environment(
+ path="/home/user/queue-with-dangling-messages/")
+ control = env.new_control()
+
+ control.prune_dangling_messages()
+
+Clear all messages
+""""""""""""""""""
+
+.. code-block:: python
+
+ import persipubsub.environment
+
+ env = persipubsub.environment.new_environment(
+ path="/home/user/queue-with-subscribers-and-messages/")
+ control = env.new_control()
+
+ control.clear_all_subscribers()
+
+
+Runtime
+-------
+
+During runtime only publisher and subscriber are needed.
+
+.. note::
+
+ Control can be optionally be used for pruning although the queues prune
+ itself on a regular basis when high water mark is reached. The high water
+ mark includes a timeout, maximum number of messages and the maximum bytes
+ size of the queue.
+
+Publisher
+^^^^^^^^^
+
+Initialization
+""""""""""""""
+
+Assuming that all queues were initialized during deployment the publisher can
+be initialized as following.
+
+.. code-block:: python
+
+ import persipubsub.environment
+
+ env = persipubsub.environment.new_environment(path="/home/user/queue/")
+
+ pub = env.new_publisher()
+
+Send a message
+""""""""""""""
+
+.. code-block:: python
+
+ msg = "Hello there!".encode('utf-8')
+ pub.send(msg=msg)
+
+ # Subscribers have now a message in the queue.
+
+Send many messages at once
+""""""""""""""""""""""""""
+
+.. code-block:: python
+
+ msgs = ["What's up?".encode('utf-8'),
+ "Do you like the README?".encode('utf-8')]
+ pub.send_many(msgs=msgs)
+
+ # Both messages are now available for the subscribers. Note that the order
+ # of the messages are not necessarily kept.
+
+Subscriber
+^^^^^^^^^^
+
+Initialization
+""""""""""""""
+
+Assuming that all queues were initialized during deployment the subscriber can
+be initialized as following.
+
+.. code-block:: python
+
+ import persipubsub.environment
+
+ env = persipubsub.environment.new_environment(path="/home/user/queue/")
+
+ sub = env.new_subscriber(identifier="sub")
+
+Receive a message
+"""""""""""""""""
+
+.. code-block:: python
+
+ # One message in queue
+ with sub.receive() as msg:
+ # do something with the message
+ print(msg) # b'Hello there!'
+
+ # This subscriber's queue is now empty
+
+Catch up with latest message
+""""""""""""""""""""""""""""
+
+Can be used in the case when a particular subscriber cares only about the very
+last message. The messages are not popped for other subscribers.
+
+.. note::
+ If you want to store only the latest message for all subscribers, then use
+ high water mark max_msgs_num = 1.
+
+
+.. code-block:: python
+
+ # Many outdated messages in queue
+
+ with sub.receive_to_top() as msg:
+ # do something with the latest message
+
+ # This subscriber's queue is now empty.
+
+Documentation
+=============
+
+The documentation is available on `readthedocs
+`_.
+
+Installation
+============
+
+* Install persipubsub with pip:
+
+.. code-block:: bash
+
+ pip3 install persipubsub
+
+Development
+===========
+
+* Check out the repository.
+
+* In the repository root, create the virtual environment:
+
+.. code-block:: bash
+
+ python3 -m venv venv3
+
+* Activate the virtual environment:
+
+.. code-block:: bash
+
+ source venv3/bin/activate
+
+* Install the development dependencies:
+
+.. code-block:: bash
+
+ pip3 install -e .[dev]
+
+We use tox for testing and packaging the distribution. Assuming that the virtual
+environment has been activated and the development dependencies have been
+installed, run:
+
+.. code-block:: bash
+
+ tox
+
+Pre-commit Checks
+-----------------
+
+We provide a set of pre-commit checks that lint and check code for formatting.
+
+Namely, we use:
+
+* `yapf `_ to check the formatting.
+* The style of the docstrings is checked with `pydocstyle `_.
+* Static type analysis is performed with `mypy `_.
+* `isort `_ to sort your imports for you.
+* Various linter checks are done with `pylint `_.
+* Doctests are executed using the Python `doctest module `_.
+* `pyicontract-lint `_ lints contracts
+ in Python code defined with `icontract library `_.
+* `twine `_ to check the README for invalid markup
+ which prevents it from rendering correctly on PyPI.
+
+Run the pre-commit checks locally from an activated virtual environment with
+development dependencies:
+
+.. code-block:: bash
+
+ ./precommit.py
+
+* The pre-commit script can also automatically format the code:
+
+.. code-block:: bash
+
+ ./precommit.py --overwrite
+
+Versioning
+==========
+
+We follow `Semantic Versioning `_.
+The version X.Y.Z indicates:
+
+* X is the major version (backward-incompatible),
+* Y is the minor version (backward-compatible), and
+* Z is the patch version (backward-compatible bug fix).
\ No newline at end of file
diff --git a/cpp/.gitignore b/cpp/.gitignore
new file mode 100644
index 0000000..05d2a2a
--- /dev/null
+++ b/cpp/.gitignore
@@ -0,0 +1,22 @@
+# Distribution / packaging
+cmake-build-debug/
+TODO
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+venv3/
+venv-doc/
+
+# IDE
+.idea
+
+# Archive
+obsolete/
+
+# Project specific
\ No newline at end of file
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
new file mode 100644
index 0000000..17aab6e
--- /dev/null
+++ b/cpp/CMakeLists.txt
@@ -0,0 +1,11 @@
+cmake_minimum_required(VERSION 3.12)
+project(persipubsub)
+
+set(CMAKE_CXX_STANDARD 17)
+
+include_directories(include)
+
+enable_testing()
+add_subdirectory(src)
+add_subdirectory(test)
+add_test(NAME myTest COMMAND Test)
\ No newline at end of file
diff --git a/cpp/include/filesystem.h b/cpp/include/filesystem.h
new file mode 100755
index 0000000..039266c
--- /dev/null
+++ b/cpp/include/filesystem.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2016 Parquery AG. All rights reserved.
+// Created by mristin on 10/12/16.
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+namespace pqry {
+namespace filesystem {
+
+/**
+ * Simulates Unix ls -lt
+ * @param directory to list
+ * @return absolute paths sorted by modification time
+ */
+std::vector ls_lt(const boost::filesystem::path& directory);
+
+/**
+ * @param directory to be listed
+ * @return list of absolute paths to files contained in the directory
+ */
+std::vector listdir(const boost::filesystem::path& directory);
+
+/**
+ * @param path to the file
+ * @return modified time in nanoseconds since epoch
+ */
+int64_t modified_time(const boost::filesystem::path& path);
+
+/**
+ * simulates `mkdtemp` command
+ * @return path to the temporary directory
+ */
+boost::filesystem::path mkdtemp();
+
+/**
+ * Reads a file to the string
+ * @param path to the file
+ * @return content of the file
+ */
+std::string read(const boost::filesystem::path& path);
+
+/**
+ * Writes the whole text to the file
+ * @param path to the file
+ * @param text to be written
+ */
+void write(const boost::filesystem::path& path, const std::string& text);
+
+/**
+ * Writes the `bytes` to the file
+ * @param path to write to
+ * @param bytes that are to be written
+ * @param size of the `bytes`
+ */
+void write(const boost::filesystem::path& path, const char* bytes, int size);
+
+/**
+ * Copies a source directory with all contents into a new target directory recursively
+ * @param source directory that gets copied
+ * @param dest directory where source gets copied to. Will be created if non-existent
+ */
+void copy_directory(const boost::filesystem::path& source, const boost::filesystem::path& dest);
+
+/**
+ * Unzips an archive.
+ * @param path to the archive
+ * @param dest_dir destination where the archive will be unzipped. Expects it to exist
+ */
+void unzip_archive(const boost::filesystem::path& path, const boost::filesystem::path& dest_dir);
+
+/**
+ * Uses POSIX flock() for named mutexes. Use an instance of this class to prevent multiple processes running at the
+ * same time.
+ *
+ * Mind that the instance keeps a file descriptor open during its lifetime.
+ * The implementation is crash-safe, i.e. if the program crashes, the system automatically releases the lock.
+ * Unlike boost::interprocess::file_lock, we do not have to create the file manually first.
+ * boost::interprocess:named_mutex did not work on Linux with boost 1.62 (marko tested it manually).
+ */
+class LockFileGuard {
+ public:
+ LockFileGuard() : locked_(false) {}
+
+ /**
+ * Creates the lock file and writes the given PID to it
+ * @param lock_file to use for locking
+ * @param pid of the process
+ * @return true if the file could be successfully locked.
+ */
+ bool lock(const boost::filesystem::path& lock_file, int pid);
+
+ void unlock();
+
+ virtual ~LockFileGuard();
+
+ private:
+ bool locked_;
+ boost::filesystem::path lock_file_;
+ int fid_;
+};
+
+/**
+ * Wait for file to exist. Panics if timeout exceeded (0 means no timeout).
+ * @param path to the file
+ * @param timeout in seconds
+ */
+void wait_for_file(const boost::filesystem::path& path, unsigned int timeout);
+
+/**
+ * Expands path using wordexp
+ * @param path to be expanded
+ * @return expanded path
+ */
+boost::filesystem::path expand_path(const boost::filesystem::path& path);
+
+/**
+ * Temporary scope file, created from a path with a random suffix and ".tmp".
+ * If the file exists at the destruction, it will be deleted.
+ * All errors will be ignored in the destructor.
+ */
+class NamedTempfile {
+ public:
+ explicit NamedTempfile(const boost::filesystem::path& path);
+
+ /**
+ * Deletes the temporary file if it was not renamed before.
+ */
+ ~NamedTempfile();
+
+ const boost::filesystem::path& path() const { return tmp_pth_; }
+
+ /**
+ * Renames the temporary file to the path passed in at the constructor.
+ */
+ void rename();
+
+ private:
+ const boost::filesystem::path pth_;
+ const boost::filesystem::path tmp_pth_;
+ bool renamed_;
+};
+
+} // namespace filesystem
+} // namespace pqry
diff --git a/cpp/include/lmdb++.h b/cpp/include/lmdb++.h
new file mode 100644
index 0000000..ab75f8c
--- /dev/null
+++ b/cpp/include/lmdb++.h
@@ -0,0 +1,1913 @@
+/* This is free and unencumbered software released into the public domain. */
+
+#ifndef LMDBXX_H
+#define LMDBXX_H
+
+/**
+ * - C++11 wrapper for LMDB.
+ *
+ * @author Arto Bendiken
+ * @see https://sourceforge.net/projects/lmdbxx/
+ */
+
+#ifndef __cplusplus
+#error " requires a C++ compiler"
+#endif
+
+#if __cplusplus < 201103L
+#if !defined(_MSC_VER) || _MSC_VER < 1900
+#error " requires a C++11 compiler (CXXFLAGS='-std=c++11')"
+#endif // _MSC_VER check
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include /* for MDB_*, mdb_*() */
+
+#ifdef LMDBXX_DEBUG
+#include /* for assert() */
+#endif
+#include /* for std::size_t */
+#include /* for std::snprintf() */
+#include /* for std::strlen() */
+#include /* for std::runtime_error */
+#include /* for std::string */
+#include /* for std::is_pod<> */
+
+namespace lmdb {
+ using mode = mdb_mode_t;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/* Error Handling */
+
+namespace lmdb {
+ class error;
+ class logic_error;
+ class fatal_error;
+ class runtime_error;
+ class key_exist_error;
+ class not_found_error;
+ class corrupted_error;
+ class panic_error;
+ class version_mismatch_error;
+ class map_full_error;
+ class bad_dbi_error;
+}
+
+/**
+ * Base class for LMDB exception conditions.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html
+ */
+class lmdb::error : public std::runtime_error {
+protected:
+ const int _code;
+
+public:
+ /**
+ * Throws an error based on the given LMDB return code.
+ */
+ [[noreturn]] static inline void raise(const char* origin, int rc);
+
+ /**
+ * Constructor.
+ */
+ error(const char* const origin,
+ const int rc) noexcept
+ : runtime_error{origin},
+ _code{rc} {}
+
+ /**
+ * Returns the underlying LMDB error code.
+ */
+ int code() const noexcept {
+ return _code;
+ }
+
+ /**
+ * Returns the origin of the LMDB error.
+ */
+ const char* origin() const noexcept {
+ return runtime_error::what();
+ }
+
+ /**
+ * Returns the underlying LMDB error code.
+ */
+ virtual const char* what() const noexcept {
+ static thread_local char buffer[1024];
+ std::snprintf(buffer, sizeof(buffer),
+ "%s: %s", origin(), ::mdb_strerror(code()));
+ return buffer;
+ }
+};
+
+/**
+ * Base class for logic error conditions.
+ */
+class lmdb::logic_error : public lmdb::error {
+public:
+ using error::error;
+};
+
+/**
+ * Base class for fatal error conditions.
+ */
+class lmdb::fatal_error : public lmdb::error {
+public:
+ using error::error;
+};
+
+/**
+ * Base class for runtime error conditions.
+ */
+class lmdb::runtime_error : public lmdb::error {
+public:
+ using error::error;
+};
+
+/**
+ * Exception class for `MDB_KEYEXIST` errors.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html#ga05dc5bbcc7da81a7345bd8676e8e0e3b
+ */
+class lmdb::key_exist_error final : public lmdb::runtime_error {
+public:
+ using runtime_error::runtime_error;
+};
+
+/**
+ * Exception class for `MDB_NOTFOUND` errors.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html#gabeb52e4c4be21b329e31c4add1b71926
+ */
+class lmdb::not_found_error final : public lmdb::runtime_error {
+public:
+ using runtime_error::runtime_error;
+};
+
+/**
+ * Exception class for `MDB_CORRUPTED` errors.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html#gaf8148bf1b85f58e264e57194bafb03ef
+ */
+class lmdb::corrupted_error final : public lmdb::fatal_error {
+public:
+ using fatal_error::fatal_error;
+};
+
+/**
+ * Exception class for `MDB_PANIC` errors.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html#gae37b9aedcb3767faba3de8c1cf6d3473
+ */
+class lmdb::panic_error final : public lmdb::fatal_error {
+public:
+ using fatal_error::fatal_error;
+};
+
+/**
+ * Exception class for `MDB_VERSION_MISMATCH` errors.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html#ga909b2db047fa90fb0d37a78f86a6f99b
+ */
+class lmdb::version_mismatch_error final : public lmdb::fatal_error {
+public:
+ using fatal_error::fatal_error;
+};
+
+/**
+ * Exception class for `MDB_MAP_FULL` errors.
+ *
+ * @see http://symas.com/mdb/doc/group__errors.html#ga0a83370402a060c9175100d4bbfb9f25
+ */
+class lmdb::map_full_error final : public lmdb::runtime_error {
+public:
+ using runtime_error::runtime_error;
+};
+
+/**
+ * Exception class for `MDB_BAD_DBI` errors.
+ *
+ * @since 0.9.14 (2014/09/20)
+ * @see http://symas.com/mdb/doc/group__errors.html#gab4c82e050391b60a18a5df08d22a7083
+ */
+class lmdb::bad_dbi_error final : public lmdb::runtime_error {
+public:
+ using runtime_error::runtime_error;
+};
+
+inline void
+lmdb::error::raise(const char* const origin,
+ const int rc) {
+ switch (rc) {
+ case MDB_KEYEXIST: throw key_exist_error{origin, rc};
+ case MDB_NOTFOUND: throw not_found_error{origin, rc};
+ case MDB_CORRUPTED: throw corrupted_error{origin, rc};
+ case MDB_PANIC: throw panic_error{origin, rc};
+ case MDB_VERSION_MISMATCH: throw version_mismatch_error{origin, rc};
+ case MDB_MAP_FULL: throw map_full_error{origin, rc};
+#ifdef MDB_BAD_DBI
+ case MDB_BAD_DBI: throw bad_dbi_error{origin, rc};
+#endif
+ default: throw lmdb::runtime_error{origin, rc};
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/* Procedural Interface: Metadata */
+
+namespace lmdb {
+ // TODO: mdb_version()
+ // TODO: mdb_strerror()
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/* Procedural Interface: Environment */
+
+namespace lmdb {
+ static inline void env_create(MDB_env** env);
+ static inline void env_open(MDB_env* env,
+ const char* path, unsigned int flags, mode mode);
+#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14)
+ static inline void env_copy(MDB_env* env, const char* path, unsigned int flags);
+ static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd, unsigned int flags);
+#else
+ static inline void env_copy(MDB_env* env, const char* path);
+ static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd);
+#endif
+ static inline void env_stat(MDB_env* env, MDB_stat* stat);
+ static inline void env_info(MDB_env* env, MDB_envinfo* stat);
+ static inline void env_sync(MDB_env* env, bool force);
+ static inline void env_close(MDB_env* env) noexcept;
+ static inline void env_set_flags(MDB_env* env, unsigned int flags, bool onoff);
+ static inline void env_get_flags(MDB_env* env, unsigned int* flags);
+ static inline void env_get_path(MDB_env* env, const char** path);
+ static inline void env_get_fd(MDB_env* env, mdb_filehandle_t* fd);
+ static inline void env_set_mapsize(MDB_env* env, std::size_t size);
+ static inline void env_set_max_readers(MDB_env* env, unsigned int count);
+ static inline void env_get_max_readers(MDB_env* env, unsigned int* count);
+ static inline void env_set_max_dbs(MDB_env* env, MDB_dbi count);
+ static inline unsigned int env_get_max_keysize(MDB_env* env);
+#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11)
+ static inline void env_set_userctx(MDB_env* env, void* ctx);
+ static inline void* env_get_userctx(MDB_env* env);
+#endif
+ // TODO: mdb_env_set_assert()
+ // TODO: mdb_reader_list()
+ // TODO: mdb_reader_check()
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaad6be3d8dcd4ea01f8df436f41d158d4
+ */
+static inline void
+lmdb::env_create(MDB_env** env) {
+ const int rc = ::mdb_env_create(env);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_create", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340
+ */
+static inline void
+lmdb::env_open(MDB_env* const env,
+ const char* const path,
+ const unsigned int flags,
+ const mode mode) {
+ const int rc = ::mdb_env_open(env, path, flags, mode);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_open", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga3bf50d7793b36aaddf6b481a44e24244
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga5d51d6130325f7353db0955dbedbc378
+ */
+static inline void
+lmdb::env_copy(MDB_env* const env,
+#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14)
+ const char* const path,
+ const unsigned int flags = 0) {
+ const int rc = ::mdb_env_copy2(env, path, flags);
+#else
+ const char* const path) {
+ const int rc = ::mdb_env_copy(env, path);
+#endif
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_copy2", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga5040d0de1f14000fa01fc0b522ff1f86
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga470b0bcc64ac417de5de5930f20b1a28
+ */
+static inline void
+lmdb::env_copy_fd(MDB_env* const env,
+#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14)
+ const mdb_filehandle_t fd,
+ const unsigned int flags = 0) {
+ const int rc = ::mdb_env_copyfd2(env, fd, flags);
+#else
+ const mdb_filehandle_t fd) {
+ const int rc = ::mdb_env_copyfd(env, fd);
+#endif
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_copyfd2", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaf881dca452050efbd434cd16e4bae255
+ */
+static inline void
+lmdb::env_stat(MDB_env* const env,
+ MDB_stat* const stat) {
+ const int rc = ::mdb_env_stat(env, stat);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_stat", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga18769362c7e7d6cf91889a028a5c5947
+ */
+static inline void
+lmdb::env_info(MDB_env* const env,
+ MDB_envinfo* const stat) {
+ const int rc = ::mdb_env_info(env, stat);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_info", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga85e61f05aa68b520cc6c3b981dba5037
+ */
+static inline void
+lmdb::env_sync(MDB_env* const env,
+ const bool force = true) {
+ const int rc = ::mdb_env_sync(env, force);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_sync", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga4366c43ada8874588b6a62fbda2d1e95
+ */
+static inline void
+lmdb::env_close(MDB_env* const env) noexcept {
+ ::mdb_env_close(env);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445
+ */
+static inline void
+lmdb::env_set_flags(MDB_env* const env,
+ const unsigned int flags,
+ const bool onoff = true) {
+ const int rc = ::mdb_env_set_flags(env, flags, onoff ? 1 : 0);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_set_flags", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga2733aefc6f50beb49dd0c6eb19b067d9
+ */
+static inline void
+lmdb::env_get_flags(MDB_env* const env,
+ unsigned int* const flags) {
+ const int rc = ::mdb_env_get_flags(env, flags);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_get_flags", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gac699fdd8c4f8013577cb933fb6a757fe
+ */
+static inline void
+lmdb::env_get_path(MDB_env* const env,
+ const char** path) {
+ const int rc = ::mdb_env_get_path(env, path);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_get_path", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaf1570e7c0e5a5d860fef1032cec7d5f2
+ */
+static inline void
+lmdb::env_get_fd(MDB_env* const env,
+ mdb_filehandle_t* const fd) {
+ const int rc = ::mdb_env_get_fd(env, fd);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_get_fd", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5
+ */
+static inline void
+lmdb::env_set_mapsize(MDB_env* const env,
+ const std::size_t size) {
+ const int rc = ::mdb_env_set_mapsize(env, size);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_set_mapsize", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gae687966c24b790630be2a41573fe40e2
+ */
+static inline void
+lmdb::env_set_max_readers(MDB_env* const env,
+ const unsigned int count) {
+ const int rc = ::mdb_env_set_maxreaders(env, count);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_set_maxreaders", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga70e143cf11760d869f754c9c9956e6cc
+ */
+static inline void
+lmdb::env_get_max_readers(MDB_env* const env,
+ unsigned int* const count) {
+ const int rc = ::mdb_env_get_maxreaders(env, count);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_get_maxreaders", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaa2fc2f1f37cb1115e733b62cab2fcdbc
+ */
+static inline void
+lmdb::env_set_max_dbs(MDB_env* const env,
+ const MDB_dbi count) {
+ const int rc = ::mdb_env_set_maxdbs(env, count);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_set_maxdbs", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaaf0be004f33828bf2fb09d77eb3cef94
+ */
+static inline unsigned int
+lmdb::env_get_max_keysize(MDB_env* const env) {
+ const int rc = ::mdb_env_get_maxkeysize(env);
+#ifdef LMDBXX_DEBUG
+ assert(rc >= 0);
+#endif
+ return static_cast(rc);
+}
+
+#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11)
+/**
+ * @throws lmdb::error on failure
+ * @since 0.9.11 (2014/01/15)
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaf2fe09eb9c96eeb915a76bf713eecc46
+ */
+static inline void
+lmdb::env_set_userctx(MDB_env* const env,
+ void* const ctx) {
+ const int rc = ::mdb_env_set_userctx(env, ctx);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_env_set_userctx", rc);
+ }
+}
+#endif
+
+#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11)
+/**
+ * @since 0.9.11 (2014/01/15)
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga45df6a4fb150cda2316b5ae224ba52f1
+ */
+static inline void*
+lmdb::env_get_userctx(MDB_env* const env) {
+ return ::mdb_env_get_userctx(env);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+/* Procedural Interface: Transactions */
+
+namespace lmdb {
+ static inline void txn_begin(
+ MDB_env* env, MDB_txn* parent, unsigned int flags, MDB_txn** txn);
+ static inline MDB_env* txn_env(MDB_txn* txn) noexcept;
+#ifdef LMDBXX_TXN_ID
+ static inline std::size_t txn_id(MDB_txn* txn) noexcept;
+#endif
+ static inline void txn_commit(MDB_txn* txn);
+ static inline void txn_abort(MDB_txn* txn) noexcept;
+ static inline void txn_reset(MDB_txn* txn) noexcept;
+ static inline void txn_renew(MDB_txn* txn);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gad7ea55da06b77513609efebd44b26920
+ */
+static inline void
+lmdb::txn_begin(MDB_env* const env,
+ MDB_txn* const parent,
+ const unsigned int flags,
+ MDB_txn** txn) {
+ const int rc = ::mdb_txn_begin(env, parent, flags, txn);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_txn_begin", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#gaeb17735b8aaa2938a78a45cab85c06a0
+ */
+static inline MDB_env*
+lmdb::txn_env(MDB_txn* const txn) noexcept {
+ return ::mdb_txn_env(txn);
+}
+
+#ifdef LMDBXX_TXN_ID
+/**
+ * @note Only available in HEAD, not yet in any 0.9.x release (as of 0.9.16).
+ */
+static inline std::size_t
+lmdb::txn_id(MDB_txn* const txn) noexcept {
+ return ::mdb_txn_id(txn);
+}
+#endif
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga846fbd6f46105617ac9f4d76476f6597
+ */
+static inline void
+lmdb::txn_commit(MDB_txn* const txn) {
+ const int rc = ::mdb_txn_commit(txn);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_txn_commit", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga73a5938ae4c3239ee11efa07eb22b882
+ */
+static inline void
+lmdb::txn_abort(MDB_txn* const txn) noexcept {
+ ::mdb_txn_abort(txn);
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga02b06706f8a66249769503c4e88c56cd
+ */
+static inline void
+lmdb::txn_reset(MDB_txn* const txn) noexcept {
+ ::mdb_txn_reset(txn);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga6c6f917959517ede1c504cf7c720ce6d
+ */
+static inline void
+lmdb::txn_renew(MDB_txn* const txn) {
+ const int rc = ::mdb_txn_renew(txn);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_txn_renew", rc);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/* Procedural Interface: Databases */
+
+namespace lmdb {
+ static inline void dbi_open(
+ MDB_txn* txn, const char* name, unsigned int flags, MDB_dbi* dbi);
+ static inline void dbi_stat(MDB_txn* txn, MDB_dbi dbi, MDB_stat* stat);
+ static inline void dbi_flags(MDB_txn* txn, MDB_dbi dbi, unsigned int* flags);
+ static inline void dbi_close(MDB_env* env, MDB_dbi dbi) noexcept;
+ static inline void dbi_drop(MDB_txn* txn, MDB_dbi dbi, bool del);
+ static inline void dbi_set_compare(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp);
+ static inline void dbi_set_dupsort(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp);
+ static inline void dbi_set_relfunc(MDB_txn* txn, MDB_dbi dbi, MDB_rel_func* rel);
+ static inline void dbi_set_relctx(MDB_txn* txn, MDB_dbi dbi, void* ctx);
+ static inline bool dbi_get(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data);
+ static inline bool dbi_put(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data, unsigned int flags);
+ static inline bool dbi_del(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, const MDB_val* data);
+ // TODO: mdb_cmp()
+ // TODO: mdb_dcmp()
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a
+ */
+static inline void
+lmdb::dbi_open(MDB_txn* const txn,
+ const char* const name,
+ const unsigned int flags,
+ MDB_dbi* const dbi) {
+ const int rc = ::mdb_dbi_open(txn, name, flags, dbi);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_dbi_open", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gae6c1069febe94299769dbdd032fadef6
+ */
+static inline void
+lmdb::dbi_stat(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ MDB_stat* const result) {
+ const int rc = ::mdb_stat(txn, dbi, result);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_stat", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga95ba4cb721035478a8705e57b91ae4d4
+ */
+static inline void
+lmdb::dbi_flags(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ unsigned int* const flags) {
+ const int rc = ::mdb_dbi_flags(txn, dbi, flags);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_dbi_flags", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga52dd98d0c542378370cd6b712ff961b5
+ */
+static inline void
+lmdb::dbi_close(MDB_env* const env,
+ const MDB_dbi dbi) noexcept {
+ ::mdb_dbi_close(env, dbi);
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#gab966fab3840fc54a6571dfb32b00f2db
+ */
+static inline void
+lmdb::dbi_drop(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ const bool del = false) {
+ const int rc = ::mdb_drop(txn, dbi, del ? 1 : 0);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_drop", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga68e47ffcf72eceec553c72b1784ee0fe
+ */
+static inline void
+lmdb::dbi_set_compare(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ MDB_cmp_func* const cmp = nullptr) {
+ const int rc = ::mdb_set_compare(txn, dbi, cmp);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_set_compare", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gacef4ec3dab0bbd9bc978b73c19c879ae
+ */
+static inline void
+lmdb::dbi_set_dupsort(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ MDB_cmp_func* const cmp = nullptr) {
+ const int rc = ::mdb_set_dupsort(txn, dbi, cmp);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_set_dupsort", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga697d82c7afe79f142207ad5adcdebfeb
+ */
+static inline void
+lmdb::dbi_set_relfunc(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ MDB_rel_func* const rel) {
+ const int rc = ::mdb_set_relfunc(txn, dbi, rel);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_set_relfunc", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga7c34246308cee01724a1839a8f5cc594
+ */
+static inline void
+lmdb::dbi_set_relctx(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ void* const ctx) {
+ const int rc = ::mdb_set_relctx(txn, dbi, ctx);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_set_relctx", rc);
+ }
+}
+
+/**
+ * @retval true if the key/value pair was retrieved
+ * @retval false if the key wasn't found
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga8bf10cd91d3f3a83a34d04ce6b07992d
+ */
+static inline bool
+lmdb::dbi_get(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ const MDB_val* const key,
+ MDB_val* const data) {
+ const int rc = ::mdb_get(txn, dbi, const_cast(key), data);
+ if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
+ error::raise("mdb_get", rc);
+ }
+ return (rc == MDB_SUCCESS);
+}
+
+/**
+ * @retval true if the key/value pair was inserted
+ * @retval false if the key already existed
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga4fa8573d9236d54687c61827ebf8cac0
+ */
+static inline bool
+lmdb::dbi_put(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ const MDB_val* const key,
+ MDB_val* const data,
+ const unsigned int flags = 0) {
+ const int rc = ::mdb_put(txn, dbi, const_cast(key), data, flags);
+ if (rc != MDB_SUCCESS && rc != MDB_KEYEXIST) {
+ error::raise("mdb_put", rc);
+ }
+ return (rc == MDB_SUCCESS);
+}
+
+/**
+ * @retval true if the key/value pair was removed
+ * @retval false if the key wasn't found
+ * @see http://symas.com/mdb/doc/group__mdb.html#gab8182f9360ea69ac0afd4a4eaab1ddb0
+ */
+static inline bool
+lmdb::dbi_del(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ const MDB_val* const key,
+ const MDB_val* const data = nullptr) {
+ const int rc = ::mdb_del(txn, dbi, const_cast(key), const_cast(data));
+ if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
+ error::raise("mdb_del", rc);
+ }
+ return (rc == MDB_SUCCESS);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/* Procedural Interface: Cursors */
+
+namespace lmdb {
+ static inline void cursor_open(MDB_txn* txn, MDB_dbi dbi, MDB_cursor** cursor);
+ static inline void cursor_close(MDB_cursor* cursor) noexcept;
+ static inline void cursor_renew(MDB_txn* txn, MDB_cursor* cursor);
+ static inline MDB_txn* cursor_txn(MDB_cursor* cursor) noexcept;
+ static inline MDB_dbi cursor_dbi(MDB_cursor* cursor) noexcept;
+ static inline bool cursor_get(MDB_cursor* cursor, MDB_val* key, MDB_val* data, MDB_cursor_op op);
+ static inline void cursor_put(MDB_cursor* cursor, MDB_val* key, MDB_val* data, unsigned int flags);
+ static inline void cursor_del(MDB_cursor* cursor, unsigned int flags);
+ static inline void cursor_count(MDB_cursor* cursor, std::size_t& count);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga9ff5d7bd42557fd5ee235dc1d62613aa
+ */
+static inline void
+lmdb::cursor_open(MDB_txn* const txn,
+ const MDB_dbi dbi,
+ MDB_cursor** const cursor) {
+ const int rc = ::mdb_cursor_open(txn, dbi, cursor);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_cursor_open", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#gad685f5d73c052715c7bd859cc4c05188
+ */
+static inline void
+lmdb::cursor_close(MDB_cursor* const cursor) noexcept {
+ ::mdb_cursor_close(cursor);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#gac8b57befb68793070c85ea813df481af
+ */
+static inline void
+lmdb::cursor_renew(MDB_txn* const txn,
+ MDB_cursor* const cursor) {
+ const int rc = ::mdb_cursor_renew(txn, cursor);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_cursor_renew", rc);
+ }
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga7bf0d458f7f36b5232fcb368ebda79e0
+ */
+static inline MDB_txn*
+lmdb::cursor_txn(MDB_cursor* const cursor) noexcept {
+ return ::mdb_cursor_txn(cursor);
+}
+
+/**
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga2f7092cf70ee816fb3d2c3267a732372
+ */
+static inline MDB_dbi
+lmdb::cursor_dbi(MDB_cursor* const cursor) noexcept {
+ return ::mdb_cursor_dbi(cursor);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga48df35fb102536b32dfbb801a47b4cb0
+ */
+static inline bool
+lmdb::cursor_get(MDB_cursor* const cursor,
+ MDB_val* const key,
+ MDB_val* const data,
+ const MDB_cursor_op op) {
+ const int rc = ::mdb_cursor_get(cursor, key, data, op);
+ if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
+ error::raise("mdb_cursor_get", rc);
+ }
+ return (rc == MDB_SUCCESS);
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga1f83ccb40011837ff37cc32be01ad91e
+ */
+static inline void
+lmdb::cursor_put(MDB_cursor* const cursor,
+ MDB_val* const key,
+ MDB_val* const data,
+ const unsigned int flags = 0) {
+ const int rc = ::mdb_cursor_put(cursor, key, data, flags);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_cursor_put", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga26a52d3efcfd72e5bf6bd6960bf75f95
+ */
+static inline void
+lmdb::cursor_del(MDB_cursor* const cursor,
+ const unsigned int flags = 0) {
+ const int rc = ::mdb_cursor_del(cursor, flags);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_cursor_del", rc);
+ }
+}
+
+/**
+ * @throws lmdb::error on failure
+ * @see http://symas.com/mdb/doc/group__mdb.html#ga4041fd1e1862c6b7d5f10590b86ffbe2
+ */
+static inline void
+lmdb::cursor_count(MDB_cursor* const cursor,
+ std::size_t& count) {
+ const int rc = ::mdb_cursor_count(cursor, &count);
+ if (rc != MDB_SUCCESS) {
+ error::raise("mdb_cursor_count", rc);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/* Resource Interface: Values */
+
+namespace lmdb {
+ class val;
+}
+
+/**
+ * Wrapper class for `MDB_val` structures.
+ *
+ * @note Instances of this class are movable and copyable both.
+ * @see http://symas.com/mdb/doc/group__mdb.html#structMDB__val
+ */
+class lmdb::val {
+protected:
+ MDB_val _val;
+
+public:
+ /**
+ * Default constructor.
+ */
+ val() noexcept = default;
+
+ /**
+ * Constructor.
+ */
+ val(const std::string& data) noexcept
+ : val{data.data(), data.size()} {}
+
+ /**
+ * Constructor.
+ */
+ val(const char* const data) noexcept
+ : val{data, std::strlen(data)} {}
+
+ /**
+ * Constructor.
+ */
+ val(const void* const data,
+ const std::size_t size) noexcept
+ : _val{size, const_cast(data)} {}
+
+ /**
+ * Move constructor.
+ */
+ val(val&& other) noexcept = default;
+
+ /**
+ * Move assignment operator.
+ */
+ val& operator=(val&& other) noexcept = default;
+
+ /**
+ * Destructor.
+ */
+ ~val() noexcept = default;
+
+ /**
+ * Returns an `MDB_val*` pointer.
+ */
+ operator MDB_val*() noexcept {
+ return &_val;
+ }
+
+ /**
+ * Returns an `MDB_val*` pointer.
+ */
+ operator const MDB_val*() const noexcept {
+ return &_val;
+ }
+
+ /**
+ * Determines whether this value is empty.
+ */
+ bool empty() const noexcept {
+ return size() == 0;
+ }
+
+ /**
+ * Returns the size of the data.
+ */
+ std::size_t size() const noexcept {
+ return _val.mv_size;
+ }
+
+ /**
+ * Returns a pointer to the data.
+ */
+ template
+ T* data() noexcept {
+ return reinterpret_cast(_val.mv_data);
+ }
+
+ /**
+ * Returns a pointer to the data.
+ */
+ template
+ const T* data() const noexcept {
+ return reinterpret_cast(_val.mv_data);
+ }
+
+ /**
+ * Returns a pointer to the data.
+ */
+ char* data() noexcept {
+ return reinterpret_cast(_val.mv_data);
+ }
+
+ /**
+ * Returns a pointer to the data.
+ */
+ const char* data() const noexcept {
+ return reinterpret_cast(_val.mv_data);
+ }
+
+ /**
+ * Assigns the value.
+ */
+ template
+ val& assign(const T* const data,
+ const std::size_t size) noexcept {
+ _val.mv_size = size;
+ _val.mv_data = const_cast(reinterpret_cast(data));
+ return *this;
+ }
+
+ /**
+ * Assigns the value.
+ */
+ val& assign(const char* const data) noexcept {
+ return assign(data, std::strlen(data));
+ }
+
+ /**
+ * Assigns the value.
+ */
+ val& assign(const std::string& data) noexcept {
+ return assign(data.data(), data.size());
+ }
+};
+
+#if !(defined(__COVERITY__) || defined(_MSC_VER))
+static_assert(std::is_pod::value, "lmdb::val must be a POD type");
+static_assert(sizeof(lmdb::val) == sizeof(MDB_val), "sizeof(lmdb::val) != sizeof(MDB_val)");
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+/* Resource Interface: Environment */
+
+namespace lmdb {
+ class env;
+}
+
+/**
+ * Resource class for `MDB_env*` handles.
+ *
+ * @note Instances of this class are movable, but not copyable.
+ * @see http://symas.com/mdb/doc/group__internal.html#structMDB__env
+ */
+class lmdb::env {
+protected:
+ MDB_env* _handle{nullptr};
+
+public:
+ static constexpr unsigned int default_flags = 0;
+ static constexpr mode default_mode = 0644; /* -rw-r--r-- */
+
+ /**
+ * Creates a new LMDB environment.
+ *
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ static env create(const unsigned int flags = default_flags) {
+ MDB_env* handle{nullptr};
+ lmdb::env_create(&handle);
+#ifdef LMDBXX_DEBUG
+ assert(handle != nullptr);
+#endif
+ if (flags) {
+ try {
+ lmdb::env_set_flags(handle, flags);
+ }
+ catch (const lmdb::error&) {
+ lmdb::env_close(handle);
+ throw;
+ }
+ }
+ return env{handle};
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param handle a valid `MDB_env*` handle
+ */
+ env(MDB_env* const handle) noexcept
+ : _handle{handle} {}
+
+ /**
+ * Move constructor.
+ */
+ env(env&& other) noexcept {
+ std::swap(_handle, other._handle);
+ }
+
+ /**
+ * Move assignment operator.
+ */
+ env& operator=(env&& other) noexcept {
+ if (this != &other) {
+ std::swap(_handle, other._handle);
+ }
+ return *this;
+ }
+
+ /**
+ * Destructor.
+ */
+ ~env() noexcept {
+ try { close(); } catch (...) {}
+ }
+
+ /**
+ * Returns the underlying `MDB_env*` handle.
+ */
+ operator MDB_env*() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Returns the underlying `MDB_env*` handle.
+ */
+ MDB_env* handle() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Flushes data buffers to disk.
+ *
+ * @param force
+ * @throws lmdb::error on failure
+ */
+ void sync(const bool force = true) {
+ lmdb::env_sync(handle(), force);
+ }
+
+ /**
+ * Closes this environment, releasing the memory map.
+ *
+ * @note this method is idempotent
+ * @post `handle() == nullptr`
+ */
+ void close() noexcept {
+ if (handle()) {
+ lmdb::env_close(handle());
+ _handle = nullptr;
+ }
+ }
+
+ /**
+ * Opens this environment.
+ *
+ * @param path
+ * @param flags
+ * @param mode
+ * @throws lmdb::error on failure
+ */
+ env& open(const char* const path,
+ const unsigned int flags = default_flags,
+ const mode mode = default_mode) {
+ lmdb::env_open(handle(), path, flags, mode);
+ return *this;
+ }
+
+ /**
+ * @param flags
+ * @param onoff
+ * @throws lmdb::error on failure
+ */
+ env& set_flags(const unsigned int flags,
+ const bool onoff = true) {
+ lmdb::env_set_flags(handle(), flags, onoff);
+ return *this;
+ }
+
+ /**
+ * @param size
+ * @throws lmdb::error on failure
+ */
+ env& set_mapsize(const std::size_t size) {
+ lmdb::env_set_mapsize(handle(), size);
+ return *this;
+ }
+
+ /**
+ * @param count
+ * @throws lmdb::error on failure
+ */
+ env& set_max_readers(const unsigned int count) {
+ lmdb::env_set_max_readers(handle(), count);
+ return *this;
+ }
+
+ /**
+ * @param count
+ * @throws lmdb::error on failure
+ */
+ env& set_max_dbs(const MDB_dbi count) {
+ lmdb::env_set_max_dbs(handle(), count);
+ return *this;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/* Resource Interface: Transactions */
+
+namespace lmdb {
+ class txn;
+}
+
+/**
+ * Resource class for `MDB_txn*` handles.
+ *
+ * @note Instances of this class are movable, but not copyable.
+ * @see http://symas.com/mdb/doc/group__internal.html#structMDB__txn
+ */
+class lmdb::txn {
+protected:
+ MDB_txn* _handle{nullptr};
+
+public:
+ static constexpr unsigned int default_flags = 0;
+
+ /**
+ * Creates a new LMDB transaction.
+ *
+ * @param env the environment handle
+ * @param parent
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ static txn begin(MDB_env* const env,
+ MDB_txn* const parent = nullptr,
+ const unsigned int flags = default_flags) {
+ MDB_txn* handle{nullptr};
+ lmdb::txn_begin(env, parent, flags, &handle);
+#ifdef LMDBXX_DEBUG
+ assert(handle != nullptr);
+#endif
+ return txn{handle};
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param handle a valid `MDB_txn*` handle
+ */
+ txn(MDB_txn* const handle) noexcept
+ : _handle{handle} {}
+
+ /**
+ * Move constructor.
+ */
+ txn(txn&& other) noexcept {
+ std::swap(_handle, other._handle);
+ }
+
+ /**
+ * Move assignment operator.
+ */
+ txn& operator=(txn&& other) noexcept {
+ if (this != &other) {
+ std::swap(_handle, other._handle);
+ }
+ return *this;
+ }
+
+ /**
+ * Destructor.
+ */
+ ~txn() noexcept {
+ if (_handle) {
+ try { abort(); } catch (...) {}
+ _handle = nullptr;
+ }
+ }
+
+ /**
+ * Returns the underlying `MDB_txn*` handle.
+ */
+ operator MDB_txn*() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Returns the underlying `MDB_txn*` handle.
+ */
+ MDB_txn* handle() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Returns the transaction's `MDB_env*` handle.
+ */
+ MDB_env* env() const noexcept {
+ return lmdb::txn_env(handle());
+ }
+
+ /**
+ * Commits this transaction.
+ *
+ * @throws lmdb::error on failure
+ * @post `handle() == nullptr`
+ */
+ void commit() {
+ lmdb::txn_commit(_handle);
+ _handle = nullptr;
+ }
+
+ /**
+ * Aborts this transaction.
+ *
+ * @post `handle() == nullptr`
+ */
+ void abort() noexcept {
+ lmdb::txn_abort(_handle);
+ _handle = nullptr;
+ }
+
+ /**
+ * Resets this read-only transaction.
+ */
+ void reset() noexcept {
+ lmdb::txn_reset(_handle);
+ }
+
+ /**
+ * Renews this read-only transaction.
+ *
+ * @throws lmdb::error on failure
+ */
+ void renew() {
+ lmdb::txn_renew(_handle);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/* Resource Interface: Databases */
+
+namespace lmdb {
+ class dbi;
+}
+
+/**
+ * Resource class for `MDB_dbi` handles.
+ *
+ * @note Instances of this class are movable, but not copyable.
+ * @see http://symas.com/mdb/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b
+ */
+class lmdb::dbi {
+protected:
+ MDB_dbi _handle{0};
+
+public:
+ static constexpr unsigned int default_flags = 0;
+ static constexpr unsigned int default_put_flags = 0;
+
+ /**
+ * Opens a database handle.
+ *
+ * @param txn the transaction handle
+ * @param name
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ static dbi
+ open(MDB_txn* const txn,
+ const char* const name = nullptr,
+ const unsigned int flags = default_flags) {
+ MDB_dbi handle{};
+ lmdb::dbi_open(txn, name, flags, &handle);
+ return dbi{handle};
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param handle a valid `MDB_dbi` handle
+ */
+ dbi(const MDB_dbi handle) noexcept
+ : _handle{handle} {}
+
+ /**
+ * Move constructor.
+ */
+ dbi(dbi&& other) noexcept {
+ std::swap(_handle, other._handle);
+ }
+
+ /**
+ * Move assignment operator.
+ */
+ dbi& operator=(dbi&& other) noexcept {
+ if (this != &other) {
+ std::swap(_handle, other._handle);
+ }
+ return *this;
+ }
+
+ /**
+ * Destructor.
+ */
+ ~dbi() noexcept {
+ if (_handle) {
+ /* No need to call close() here. */
+ }
+ }
+
+ /**
+ * Returns the underlying `MDB_dbi` handle.
+ */
+ operator MDB_dbi() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Returns the underlying `MDB_dbi` handle.
+ */
+ MDB_dbi handle() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Returns statistics for this database.
+ *
+ * @param txn a transaction handle
+ * @throws lmdb::error on failure
+ */
+ MDB_stat stat(MDB_txn* const txn) const {
+ MDB_stat result;
+ lmdb::dbi_stat(txn, handle(), &result);
+ return result;
+ }
+
+ /**
+ * Retrieves the flags for this database handle.
+ *
+ * @param txn a transaction handle
+ * @throws lmdb::error on failure
+ */
+ unsigned int flags(MDB_txn* const txn) const {
+ unsigned int result{};
+ lmdb::dbi_flags(txn, handle(), &result);
+ return result;
+ }
+
+ /**
+ * Returns the number of records in this database.
+ *
+ * @param txn a transaction handle
+ * @throws lmdb::error on failure
+ */
+ std::size_t size(MDB_txn* const txn) const {
+ return stat(txn).ms_entries;
+ }
+
+ /**
+ * @param txn a transaction handle
+ * @param del
+ * @throws lmdb::error on failure
+ */
+ void drop(MDB_txn* const txn,
+ const bool del = false) {
+ lmdb::dbi_drop(txn, handle(), del);
+ }
+
+ /**
+ * Sets a custom key comparison function for this database.
+ *
+ * @param txn a transaction handle
+ * @param cmp the comparison function
+ * @throws lmdb::error on failure
+ */
+ dbi& set_compare(MDB_txn* const txn,
+ MDB_cmp_func* const cmp = nullptr) {
+ lmdb::dbi_set_compare(txn, handle(), cmp);
+ return *this;
+ }
+
+ /**
+ * Retrieves a key/value pair from this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @param data
+ * @throws lmdb::error on failure
+ */
+ bool get(MDB_txn* const txn,
+ const val& key,
+ val& data) {
+ return lmdb::dbi_get(txn, handle(), key, data);
+ }
+
+ /**
+ * Retrieves a key from this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @throws lmdb::error on failure
+ */
+ template
+ bool get(MDB_txn* const txn,
+ const K& key) const {
+ const lmdb::val k{&key, sizeof(K)};
+ lmdb::val v{};
+ return lmdb::dbi_get(txn, handle(), k, v);
+ }
+
+ /**
+ * Retrieves a key/value pair from this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @param val
+ * @throws lmdb::error on failure
+ */
+ template
+ bool get(MDB_txn* const txn,
+ const K& key,
+ V& val) const {
+ const lmdb::val k{&key, sizeof(K)};
+ lmdb::val v{};
+ const bool result = lmdb::dbi_get(txn, handle(), k, v);
+ if (result) {
+ val = *v.data();
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves a key/value pair from this database.
+ *
+ * @param txn a transaction handle
+ * @param key a NUL-terminated string key
+ * @param val
+ * @throws lmdb::error on failure
+ */
+ template
+ bool get(MDB_txn* const txn,
+ const char* const key,
+ V& val) const {
+ const lmdb::val k{key, std::strlen(key)};
+ lmdb::val v{};
+ const bool result = lmdb::dbi_get(txn, handle(), k, v);
+ if (result) {
+ val = *v.data();
+ }
+ return result;
+ }
+
+ /**
+ * Stores a key/value pair into this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @param data
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ bool put(MDB_txn* const txn,
+ const val& key,
+ val& data,
+ const unsigned int flags = default_put_flags) {
+ return lmdb::dbi_put(txn, handle(), key, data, flags);
+ }
+
+ /**
+ * Stores a key into this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ template
+ bool put(MDB_txn* const txn,
+ const K& key,
+ const unsigned int flags = default_put_flags) {
+ const lmdb::val k{&key, sizeof(K)};
+ lmdb::val v{};
+ return lmdb::dbi_put(txn, handle(), k, v, flags);
+ }
+
+ /**
+ * Stores a key/value pair into this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @param val
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ template
+ bool put(MDB_txn* const txn,
+ const K& key,
+ const V& val,
+ const unsigned int flags = default_put_flags) {
+ const lmdb::val k{&key, sizeof(K)};
+ lmdb::val v{&val, sizeof(V)};
+ return lmdb::dbi_put(txn, handle(), k, v, flags);
+ }
+
+ /**
+ * Stores a key/value pair into this database.
+ *
+ * @param txn a transaction handle
+ * @param key a NUL-terminated string key
+ * @param val
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ template
+ bool put(MDB_txn* const txn,
+ const char* const key,
+ const V& val,
+ const unsigned int flags = default_put_flags) {
+ const lmdb::val k{key, std::strlen(key)};
+ lmdb::val v{&val, sizeof(V)};
+ return lmdb::dbi_put(txn, handle(), k, v, flags);
+ }
+
+ /**
+ * Stores a key/value pair into this database.
+ *
+ * @param txn a transaction handle
+ * @param key a NUL-terminated string key
+ * @param val a NUL-terminated string key
+ * @param flags
+ * @throws lmdb::error on failure
+ */
+ bool put(MDB_txn* const txn,
+ const char* const key,
+ const char* const val,
+ const unsigned int flags = default_put_flags) {
+ const lmdb::val k{key, std::strlen(key)};
+ lmdb::val v{val, std::strlen(val)};
+ return lmdb::dbi_put(txn, handle(), k, v, flags);
+ }
+
+ /**
+ * Removes a key/value pair from this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @throws lmdb::error on failure
+ */
+ bool del(MDB_txn* const txn,
+ const val& key) {
+ return lmdb::dbi_del(txn, handle(), key);
+ }
+
+ /**
+ * Removes a key/value pair from this database.
+ *
+ * @param txn a transaction handle
+ * @param key
+ * @throws lmdb::error on failure
+ */
+ template
+ bool del(MDB_txn* const txn,
+ const K& key) {
+ const lmdb::val k{&key, sizeof(K)};
+ return lmdb::dbi_del(txn, handle(), k);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/* Resource Interface: Cursors */
+
+namespace lmdb {
+ class cursor;
+}
+
+/**
+ * Resource class for `MDB_cursor*` handles.
+ *
+ * @note Instances of this class are movable, but not copyable.
+ * @see http://symas.com/mdb/doc/group__internal.html#structMDB__cursor
+ */
+class lmdb::cursor {
+protected:
+ MDB_cursor* _handle{nullptr};
+
+public:
+ static constexpr unsigned int default_flags = 0;
+
+ /**
+ * Creates an LMDB cursor.
+ *
+ * @param txn the transaction handle
+ * @param dbi the database handle
+ * @throws lmdb::error on failure
+ */
+ static cursor
+ open(MDB_txn* const txn,
+ const MDB_dbi dbi) {
+ MDB_cursor* handle{};
+ lmdb::cursor_open(txn, dbi, &handle);
+#ifdef LMDBXX_DEBUG
+ assert(handle != nullptr);
+#endif
+ return cursor{handle};
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param handle a valid `MDB_cursor*` handle
+ */
+ cursor(MDB_cursor* const handle) noexcept
+ : _handle{handle} {}
+
+ /**
+ * Move constructor.
+ */
+ cursor(cursor&& other) noexcept {
+ std::swap(_handle, other._handle);
+ }
+
+ /**
+ * Move assignment operator.
+ */
+ cursor& operator=(cursor&& other) noexcept {
+ if (this != &other) {
+ std::swap(_handle, other._handle);
+ }
+ return *this;
+ }
+
+ /**
+ * Destructor.
+ */
+ ~cursor() noexcept {
+ try { close(); } catch (...) {}
+ }
+
+ /**
+ * Returns the underlying `MDB_cursor*` handle.
+ */
+ operator MDB_cursor*() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Returns the underlying `MDB_cursor*` handle.
+ */
+ MDB_cursor* handle() const noexcept {
+ return _handle;
+ }
+
+ /**
+ * Closes this cursor.
+ *
+ * @note this method is idempotent
+ * @post `handle() == nullptr`
+ */
+ void close() noexcept {
+ if (_handle) {
+ lmdb::cursor_close(_handle);
+ _handle = nullptr;
+ }
+ }
+
+ /**
+ * Renews this cursor.
+ *
+ * @param txn the transaction scope
+ * @throws lmdb::error on failure
+ */
+ void renew(MDB_txn* const txn) {
+ lmdb::cursor_renew(txn, handle());
+ }
+
+ /**
+ * Returns the cursor's transaction handle.
+ */
+ MDB_txn* txn() const noexcept {
+ return lmdb::cursor_txn(handle());
+ }
+
+ /**
+ * Returns the cursor's database handle.
+ */
+ MDB_dbi dbi() const noexcept {
+ return lmdb::cursor_dbi(handle());
+ }
+
+ /**
+ * Retrieves a key from the database.
+ *
+ * @param key
+ * @param op
+ * @throws lmdb::error on failure
+ */
+ bool get(MDB_val* const key,
+ const MDB_cursor_op op) {
+ return get(key, nullptr, op);
+ }
+
+ /**
+ * Retrieves a key from the database.
+ *
+ * @param key
+ * @param op
+ * @throws lmdb::error on failure
+ */
+ bool get(lmdb::val& key,
+ const MDB_cursor_op op) {
+ return get(key, nullptr, op);
+ }
+
+ /**
+ * Retrieves a key/value pair from the database.
+ *
+ * @param key
+ * @param val (may be `nullptr`)
+ * @param op
+ * @throws lmdb::error on failure
+ */
+ bool get(MDB_val* const key,
+ MDB_val* const val,
+ const MDB_cursor_op op) {
+ return lmdb::cursor_get(handle(), key, val, op);
+ }
+
+ /**
+ * Retrieves a key/value pair from the database.
+ *
+ * @param key
+ * @param val
+ * @param op
+ * @throws lmdb::error on failure
+ */
+ bool get(lmdb::val& key,
+ lmdb::val& val,
+ const MDB_cursor_op op) {
+ return lmdb::cursor_get(handle(), key, val, op);
+ }
+
+ /**
+ * Retrieves a key/value pair from the database.
+ *
+ * @param key
+ * @param val
+ * @param op
+ * @throws lmdb::error on failure
+ */
+ bool get(std::string& key,
+ std::string& val,
+ const MDB_cursor_op op) {
+ lmdb::val k{}, v{};
+ const bool found = get(k, v, op);
+ if (found) {
+ key.assign(k.data(), k.size());
+ val.assign(v.data(), v.size());
+ }
+ return found;
+ }
+
+ /**
+ * Positions this cursor at the given key.
+ *
+ * @param key
+ * @param op
+ * @throws lmdb::error on failure
+ */
+ template
+ bool find(const K& key,
+ const MDB_cursor_op op = MDB_SET) {
+ lmdb::val k{&key, sizeof(K)};
+ return get(k, nullptr, op);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif /* LMDBXX_H */
diff --git a/cpp/include/prettyprint.hpp b/cpp/include/prettyprint.hpp
new file mode 100644
index 0000000..ebba086
--- /dev/null
+++ b/cpp/include/prettyprint.hpp
@@ -0,0 +1,446 @@
+// https://github.com/louisdx/cxx-prettyprint/blob/master/prettyprint.hpp 27cad81cabff867ed3f72fdf7a195d2c2a90552f
+// Copyright Louis Delacroix 2010 - 2014.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+//
+// A pretty printing library for C++
+//
+// Usage:
+// Include this header, and operator<< will "just work".
+
+#ifndef H_PRETTY_PRINT
+#define H_PRETTY_PRINT
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace pretty_print
+{
+ namespace detail
+ {
+ // SFINAE type trait to detect whether T::const_iterator exists.
+
+ struct sfinae_base
+ {
+ using yes = char;
+ using no = yes[2];
+ };
+
+ template
+ struct has_const_iterator : private sfinae_base
+ {
+ private:
+ template static yes & test(typename C::const_iterator*);
+ template static no & test(...);
+ public:
+ static const bool value = sizeof(test(nullptr)) == sizeof(yes);
+ using type = T;
+ };
+
+ template
+ struct has_begin_end : private sfinae_base
+ {
+ private:
+ template
+ static yes & f(typename std::enable_if<
+ std::is_same(&C::begin)),
+ typename C::const_iterator(C::*)() const>::value>::type *);
+
+ template static no & f(...);
+
+ template
+ static yes & g(typename std::enable_if<
+ std::is_same(&C::end)),
+ typename C::const_iterator(C::*)() const>::value, void>::type*);
+
+ template static no & g(...);
+
+ public:
+ static bool const beg_value = sizeof(f(nullptr)) == sizeof(yes);
+ static bool const end_value = sizeof(g(nullptr)) == sizeof(yes);
+ };
+
+ } // namespace detail
+
+
+ // Holds the delimiter values for a specific character type
+
+ template
+ struct delimiters_values
+ {
+ using char_type = TChar;
+ const char_type * prefix;
+ const char_type * delimiter;
+ const char_type * postfix;
+ };
+
+
+ // Defines the delimiter values for a specific container and character type
+
+ template
+ struct delimiters
+ {
+ using type = delimiters_values;
+ static const type values;
+ };
+
+
+ // Functor to print containers. You can use this directly if you want
+ // to specificy a non-default delimiters type. The printing logic can
+ // be customized by specializing the nested template.
+
+ template ,
+ typename TDelimiters = delimiters>
+ struct print_container_helper
+ {
+ using delimiters_type = TDelimiters;
+ using ostream_type = std::basic_ostream;
+
+ template
+ struct printer
+ {
+ static void print_body(const U & c, ostream_type & stream)
+ {
+ using std::begin;
+ using std::end;
+
+ auto it = begin(c);
+ const auto the_end = end(c);
+
+ if (it != the_end)
+ {
+ for ( ; ; )
+ {
+ stream << *it;
+
+ if (++it == the_end) break;
+
+ if (delimiters_type::values.delimiter != NULL)
+ stream << delimiters_type::values.delimiter;
+ }
+ }
+ }
+ };
+
+ print_container_helper(const T & container)
+ : container_(container)
+ { }
+
+ inline void operator()(ostream_type & stream) const
+ {
+ if (delimiters_type::values.prefix != NULL)
+ stream << delimiters_type::values.prefix;
+
+ printer::print_body(container_, stream);
+
+ if (delimiters_type::values.postfix != NULL)
+ stream << delimiters_type::values.postfix;
+ }
+
+ private:
+ const T & container_;
+ };
+
+ // Specialization for pairs
+
+ template
+ template
+ struct print_container_helper::printer>
+ {
+ using ostream_type = typename print_container_helper::ostream_type;
+
+ static void print_body(const std::pair & c, ostream_type & stream)
+ {
+ stream << c.first;
+ if (print_container_helper::delimiters_type::values.delimiter != NULL)
+ stream << print_container_helper::delimiters_type::values.delimiter;
+ stream << c.second;
+ }
+ };
+
+ // Specialization for tuples
+
+ template
+ template
+ struct print_container_helper::printer>
+ {
+ using ostream_type = typename print_container_helper::ostream_type;
+ using element_type = std::tuple;
+
+ template struct Int { };
+
+ static void print_body(const element_type & c, ostream_type & stream)
+ {
+ tuple_print(c, stream, Int<0>());
+ }
+
+ static void tuple_print(const element_type &, ostream_type &, Int)
+ {
+ }
+
+ static void tuple_print(const element_type & c, ostream_type & stream,
+ typename std::conditional, std::nullptr_t>::type)
+ {
+ stream << std::get<0>(c);
+ tuple_print(c, stream, Int<1>());
+ }
+
+ template
+ static void tuple_print(const element_type & c, ostream_type & stream, Int)
+ {
+ if (print_container_helper::delimiters_type::values.delimiter != NULL)
+ stream << print_container_helper::delimiters_type::values.delimiter;
+
+ stream << std::get(c);
+
+ tuple_print(c, stream, Int());
+ }
+ };
+
+ // Prints a print_container_helper to the specified stream.
+
+ template
+ inline std::basic_ostream & operator<<(
+ std::basic_ostream & stream,
+ const print_container_helper & helper)
+ {
+ helper(stream);
+ return stream;
+ }
+
+
+ // Basic is_container template; specialize to derive from std::true_type for all desired container types
+
+ template
+ struct is_container : public std::integral_constant::value &&
+ detail::has_begin_end::beg_value &&
+ detail::has_begin_end::end_value> { };
+
+ template
+ struct is_container : std::true_type { };
+
+ template
+ struct is_container : std::false_type { };
+
+ template
+ struct is_container> : std::true_type { };
+
+ template
+ struct is_container> : std::true_type { };
+
+ template
+ struct is_container> : std::true_type { };
+
+
+ // Default delimiters
+
+ template struct delimiters { static const delimiters_values values; };
+ template const delimiters_values delimiters::values = { "[", ", ", "]" };
+ template struct delimiters { static const delimiters_values values; };
+ template const delimiters_values delimiters::values = { L"[", L", ", L"]" };
+
+
+ // Delimiters for (multi)set and unordered_(multi)set
+
+ template
+ struct delimiters< ::std::set, char> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::set, char>::values = { "{", ", ", "}" };
+
+ template
+ struct delimiters< ::std::set, wchar_t> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::set, wchar_t>::values = { L"{", L", ", L"}" };
+
+ template
+ struct delimiters< ::std::multiset, char> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::multiset, char>::values = { "{", ", ", "}" };
+
+ template
+ struct delimiters< ::std::multiset, wchar_t> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::multiset, wchar_t>::values = { L"{", L", ", L"}" };
+
+ template
+ struct delimiters< ::std::unordered_set, char> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::unordered_set, char>::values = { "{", ", ", "}" };
+
+ template
+ struct delimiters< ::std::unordered_set, wchar_t> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::unordered_set, wchar_t>::values = { L"{", L", ", L"}" };
+
+ template
+ struct delimiters< ::std::unordered_multiset, char> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::unordered_multiset, char>::values = { "{", ", ", "}" };
+
+ template
+ struct delimiters< ::std::unordered_multiset, wchar_t> { static const delimiters_values values; };
+
+ template
+ const delimiters_values delimiters< ::std::unordered_multiset, wchar_t>::values = { L"{", L", ", L"}" };
+
+
+ // Delimiters for pair and tuple
+
+ template struct delimiters, char> { static const delimiters_values values; };
+ template const delimiters_values delimiters, char>::values = { "(", ", ", ")" };
+ template struct delimiters< ::std::pair, wchar_t> { static const delimiters_values values; };
+ template const delimiters_values delimiters< ::std::pair, wchar_t>::values = { L"(", L", ", L")" };
+
+ template struct delimiters, char> { static const delimiters_values values; };
+ template const delimiters_values delimiters, char>::values = { "(", ", ", ")" };
+ template struct delimiters< ::std::tuple, wchar_t> { static const delimiters_values values; };
+ template const delimiters_values delimiters< ::std::tuple, wchar_t>::values = { L"(", L", ", L")" };
+
+
+ // Type-erasing helper class for easy use of custom delimiters.
+ // Requires TCharTraits = std::char_traits and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
+ // Usage: "cout << pretty_print::custom_delims(x)".
+
+ struct custom_delims_base
+ {
+ virtual ~custom_delims_base() { }
+ virtual std::ostream & stream(::std::ostream &) = 0;
+ virtual std::wostream & stream(::std::wostream &) = 0;
+ };
+
+ template
+ struct custom_delims_wrapper : custom_delims_base
+ {
+ custom_delims_wrapper(const T & t_) : t(t_) { }
+
+ std::ostream & stream(std::ostream & s)
+ {
+ return s << print_container_helper, Delims>(t);
+ }
+
+ std::wostream & stream(std::wostream & s)
+ {
+ return s << print_container_helper, Delims>(t);
+ }
+
+ private:
+ const T & t;
+ };
+
+ template
+ struct custom_delims
+ {
+ template
+ custom_delims(const Container & c) : base(new custom_delims_wrapper(c)) { }
+
+ std::unique_ptr base;
+ };
+
+ template
+ inline std::basic_ostream & operator<<(std::basic_ostream & s, const custom_delims & p)
+ {
+ return p.base->stream(s);
+ }
+
+
+ // A wrapper for a C-style array given as pointer-plus-size.
+ // Usage: std::cout << pretty_print_array(arr, n) << std::endl;
+
+ template
+ struct array_wrapper_n
+ {
+ typedef const T * const_iterator;
+ typedef T value_type;
+
+ array_wrapper_n(const T * const a, size_t n) : _array(a), _n(n) { }
+ inline const_iterator begin() const { return _array; }
+ inline const_iterator end() const { return _array + _n; }
+
+ private:
+ const T * const _array;
+ size_t _n;
+ };
+
+
+ // A wrapper for hash-table based containers that offer local iterators to each bucket.
+ // Usage: std::cout << bucket_print(m, 4) << std::endl; (Prints bucket 5 of container m.)
+
+ template
+ struct bucket_print_wrapper
+ {
+ typedef typename T::const_local_iterator const_iterator;
+ typedef typename T::size_type size_type;
+
+ const_iterator begin() const
+ {
+ return m_map.cbegin(n);
+ }
+
+ const_iterator end() const
+ {
+ return m_map.cend(n);
+ }
+
+ bucket_print_wrapper(const T & m, size_type bucket) : m_map(m), n(bucket) { }
+
+ private:
+ const T & m_map;
+ const size_type n;
+ };
+
+} // namespace pretty_print
+
+
+// Global accessor functions for the convenience wrappers
+
+template
+inline pretty_print::array_wrapper_n pretty_print_array(const T * const a, size_t n)
+{
+ return pretty_print::array_wrapper_n(a, n);
+}
+
+template pretty_print::bucket_print_wrapper
+bucket_print(const T & m, typename T::size_type n)
+{
+ return pretty_print::bucket_print_wrapper(m, n);
+}
+
+
+// Main magic entry point: An overload snuck into namespace std.
+// Can we do better?
+
+namespace std
+{
+ // Prints a container to the stream using default delimiters
+
+ template
+ inline typename enable_if< ::pretty_print::is_container::value,
+ basic_ostream &>::type
+ operator<<(basic_ostream & stream, const T & container)
+ {
+ return stream << ::pretty_print::print_container_helper(container);
+ }
+}
+
+
+
+#endif // H_PRETTY_PRINT
diff --git a/cpp/include/scope_exit.hpp b/cpp/include/scope_exit.hpp
new file mode 100644
index 0000000..f7cab9c
--- /dev/null
+++ b/cpp/include/scope_exit.hpp
@@ -0,0 +1,39 @@
+// https://gist.github.com/socantre/b45b3e23e6f1f4715f08
+#include
+
+#pragma once
+
+namespace socantre {
+
+template struct scope_exit {
+ // construction
+ explicit scope_exit(EF &&f) noexcept(true)
+ : exit_function(std::move(f)), execute_on_destruction{true} {}
+ // move
+ scope_exit(scope_exit &&rhs) noexcept(true)
+ : exit_function(std::move(rhs.exit_function)),
+ execute_on_destruction{rhs.execute_on_destruction} {
+ rhs.release();
+ }
+ // release
+ ~scope_exit() noexcept(noexcept(this->exit_function())) {
+ if (execute_on_destruction)
+ this->exit_function();
+ }
+ void release() noexcept(true) { this->execute_on_destruction = false; }
+
+private:
+ scope_exit(scope_exit const &) = delete;
+ void operator=(scope_exit const &) = delete;
+ scope_exit &operator=(scope_exit &&) = delete;
+ EF exit_function;
+ bool execute_on_destruction; // exposition only
+};
+
+template
+auto make_scope_exit(EF &&exit_function) noexcept(true) -> decltype(
+ scope_exit>(std::forward(exit_function))) {
+ return scope_exit>(
+ std::forward(exit_function));
+}
+} // namespace socantre
diff --git a/cpp/include/tinyformat.h b/cpp/include/tinyformat.h
new file mode 100644
index 0000000..1194d75
--- /dev/null
+++ b/cpp/include/tinyformat.h
@@ -0,0 +1,1003 @@
+// tinyformat.h
+// Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com]
+//
+// Boost Software License - Version 1.0
+//
+// Permission is hereby granted, free of charge, to any person or organization
+// obtaining a copy of the software and accompanying documentation covered by
+// this license (the "Software") to use, reproduce, display, distribute,
+// execute, and transmit the Software, and to prepare derivative works of the
+// Software, and to permit third-parties to whom the Software is furnished to
+// do so, all subject to the following:
+//
+// The copyright notices in the Software and this entire statement, including
+// the above license grant, this restriction and the following disclaimer,
+// must be included in all copies of the Software, in whole or in part, and
+// all derivative works of the Software, unless such copies or derivative
+// works are solely in the form of machine-executable object code generated by
+// a source language processor.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+//------------------------------------------------------------------------------
+// Tinyformat: A minimal type safe printf replacement
+//
+// tinyformat.h is a type safe printf replacement library in a single C++
+// header file. Design goals include:
+//
+// * Type safety and extensibility for user defined types.
+// * C99 printf() compatibility, to the extent possible using std::ostream
+// * Simplicity and minimalism. A single header file to include and distribute
+// with your projects.
+// * Augment rather than replace the standard stream formatting mechanism
+// * C++98 support, with optional C++11 niceties
+//
+//
+// Main interface example usage
+// ----------------------------
+//
+// To print a date to std::cout:
+//
+// std::string weekday = "Wednesday";
+// const char* month = "July";
+// size_t day = 27;
+// long hour = 14;
+// int min = 44;
+//
+// tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min);
+//
+// The strange types here emphasize the type safety of the interface; it is
+// possible to print a std::string using the "%s" conversion, and a
+// size_t using the "%d" conversion. A similar result could be achieved
+// using either of the tfm::format() functions. One prints on a user provided
+// stream:
+//
+// tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n",
+// weekday, month, day, hour, min);
+//
+// The other returns a std::string:
+//
+// std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n",
+// weekday, month, day, hour, min);
+// std::cout << date;
+//
+// These are the three primary interface functions.
+//
+//
+// User defined format functions
+// -----------------------------
+//
+// Simulating variadic templates in C++98 is pretty painful since it requires
+// writing out the same function for each desired number of arguments. To make
+// this bearable tinyformat comes with a set of macros which are used
+// internally to generate the API, but which may also be used in user code.
+//
+// The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and
+// TINYFORMAT_PASSARGS(n) will generate a list of n argument types,
+// type/name pairs and argument names respectively when called with an integer
+// n between 1 and 16. We can use these to define a macro which generates the
+// desired user defined function with n arguments. To generate all 16 user
+// defined function bodies, use the macro TINYFORMAT_FOREACH_ARGNUM. For an
+// example, see the implementation of printf() at the end of the source file.
+//
+// Sometimes it's useful to be able to pass a list of format arguments through
+// to a non-template function. The FormatList class is provided as a way to do
+// this by storing the argument list in a type-opaque way. Continuing the
+// example from above, we construct a FormatList using makeFormatList():
+//
+// FormatListRef formatList = tfm::makeFormatList(weekday, month, day, hour, min);
+//
+// The format list can now be passed into any non-template function and used
+// via a call to the vformat() function:
+//
+// tfm::vformat(std::cout, "%s, %s %d, %.2d:%.2d\n", formatList);
+//
+//
+// Additional API information
+// --------------------------
+//
+// Error handling: Define TINYFORMAT_ERROR to customize the error handling for
+// format strings which are unsupported or have the wrong number of format
+// specifiers (calls assert() by default).
+//
+// User defined types: Uses operator<< for user defined types by default.
+// Overload formatValue() for more control.
+
+
+#ifndef TINYFORMAT_H_INCLUDED
+#define TINYFORMAT_H_INCLUDED
+
+namespace tinyformat {}
+//------------------------------------------------------------------------------
+// Config section. Customize to your liking!
+
+// Namespace alias to encourage brevity
+namespace tfm = tinyformat;
+
+// Error handling; calls assert() by default.
+// #define TINYFORMAT_ERROR(reasonString) your_error_handler(reasonString)
+
+// Define for C++11 variadic templates which make the code shorter & more
+// general. If you don't define this, C++11 support is autodetected below.
+// #define TINYFORMAT_USE_VARIADIC_TEMPLATES
+
+
+//------------------------------------------------------------------------------
+// Implementation details.
+#include
+#include
+#include
+#include
+
+#ifndef TINYFORMAT_ERROR
+# define TINYFORMAT_ERROR(reason) assert(0 && reason)
+#endif
+
+#if !defined(TINYFORMAT_USE_VARIADIC_TEMPLATES) && !defined(TINYFORMAT_NO_VARIADIC_TEMPLATES)
+# ifdef __GXX_EXPERIMENTAL_CXX0X__
+# define TINYFORMAT_USE_VARIADIC_TEMPLATES
+# endif
+#endif
+
+#if defined(__GLIBCXX__) && __GLIBCXX__ < 20080201
+// std::showpos is broken on old libstdc++ as provided with OSX. See
+// http://gcc.gnu.org/ml/libstdc++/2007-11/msg00075.html
+# define TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
+#endif
+
+namespace tinyformat {
+
+//------------------------------------------------------------------------------
+namespace detail {
+
+// Test whether type T1 is convertible to type T2
+template
+struct is_convertible
+{
+ private:
+ // two types of different size
+ struct fail { char dummy[2]; };
+ struct succeed { char dummy; };
+ // Try to convert a T1 to a T2 by plugging into tryConvert
+ static fail tryConvert(...);
+ static succeed tryConvert(const T2&);
+ static const T1& makeT1();
+ public:
+# ifdef _MSC_VER
+ // Disable spurious loss of precision warnings in tryConvert(makeT1())
+# pragma warning(push)
+# pragma warning(disable:4244)
+# pragma warning(disable:4267)
+# endif
+ // Standard trick: the (...) version of tryConvert will be chosen from
+ // the overload set only if the version taking a T2 doesn't match.
+ // Then we compare the sizes of the return types to check which
+ // function matched. Very neat, in a disgusting kind of way :)
+ static const bool value =
+ sizeof(tryConvert(makeT1())) == sizeof(succeed);
+# ifdef _MSC_VER
+# pragma warning(pop)
+# endif
+};
+
+
+// Detect when a type is not a wchar_t string
+template struct is_wchar { typedef int tinyformat_wchar_is_not_supported; };
+template<> struct is_wchar {};
+template<> struct is_wchar {};
+template struct is_wchar {};
+template struct is_wchar {};
+
+
+// Format the value by casting to type fmtT. This default implementation
+// should never be called.
+template