|
1 | | -# lmdb-cpp: A simple OOP-style wrapper around [lmdb](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) |
| 1 | +# lmdb-cpp |
2 | 2 |
|
3 | | -This library is designed to be as simple as possible to use; however, we do not guarantee full coverage of the entire |
4 | | -LMDB API. If you're in need of something, please feel free to submit an issue or open a pull request to add the |
5 | | -functionality you need. Otherwise, WYSIWYG. |
| 3 | +A straightforward C++17 wrapper around [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) that handles the fiddly bits for you -- RAII resource management, thread-safe singletons, automatic map expansion, and optional [Snappy](https://github.com/google/snappy) compression. |
6 | 4 |
|
7 | | -The following *features* have been baked in: |
| 5 | +## Features |
8 | 6 |
|
9 | | -* Builds with CMake making it easy to add to other projects |
10 | | -* GibHub Actions verifies that it builds on Ubuntu, Windows, and MacOS using various compilers |
11 | | -* The *option* to compress values stored in the database using |
12 | | - [snappy compression](https://github.com/google/snappy) at a small performance cost. |
13 | | -* Use of shared pointers and singleton patterns to attempt thread-safety while maintaining LMDB sanity. |
14 | | -* Underlying LMDB instances are closed up as required as the shared pointers are destructed (ie. when the last instance |
15 | | - of the shared pointer leaves scope). |
16 | | -* Transactions are **automatically aborted** unless you explicitly commit them. |
| 7 | +- **RAII everywhere** -- Environments, Databases, Transactions, and Cursors clean up after themselves. Transactions auto-abort if you don't commit. |
| 8 | +- **Thread-safe singletons** -- `Environment::instance()` and `Environment::database()` return the same shared pointer for the same path/name, so you can call them from any thread without worrying about duplicates. |
| 9 | +- **Automatic map expansion** -- `Database::put()` and `Database::del()` detect `MDB_MAP_FULL` and transparently grow the memory map, then retry. |
| 10 | +- **Optional Snappy compression** -- Enable per-database at creation time. Reads decompress automatically, even if the compression setting changes later. |
| 11 | +- **Template convenience methods** -- `put_key()`, `get_key()`, `del_key()`, and `exists_key()` work directly with `std::string`, `std::vector`, or anything with `.data()` and `.size()`. |
17 | 12 |
|
18 | | -## Documentation |
| 13 | +## Quick Start |
19 | 14 |
|
20 | | -C++ API documentation can be found in the headers (.h) |
21 | | - |
22 | | -### Example Use |
23 | | - |
24 | | -```c++ |
| 15 | +```cpp |
25 | 16 | #include <lmdb_cpp.hpp> |
26 | 17 | #include <iostream> |
| 18 | +#include <string> |
| 19 | +#include <vector> |
| 20 | + |
| 21 | +int main() |
| 22 | +{ |
| 23 | + // Open (or reuse) an environment -- singleton per path |
| 24 | + auto env = LMDB::Environment::instance("my.db"); |
| 25 | + |
| 26 | + // Open a named database (singleton per name within the environment) |
| 27 | + auto db = env->database("users"); |
| 28 | + |
| 29 | + // Write a value |
| 30 | + std::string key = "user:42"; |
| 31 | + std::string value = "Alice"; |
| 32 | + if (auto err = db->put_key(key, value)) |
| 33 | + { |
| 34 | + std::cerr << "put failed: " << err.to_string() << "\n"; |
| 35 | + return 1; |
| 36 | + } |
| 37 | + |
| 38 | + // Read it back |
| 39 | + auto [err, data] = db->get_key(key); |
| 40 | + if (err) |
| 41 | + { |
| 42 | + std::cerr << "get failed: " << err.to_string() << "\n"; |
| 43 | + return 1; |
| 44 | + } |
| 45 | + |
| 46 | + std::string result(data.begin(), data.end()); |
| 47 | + std::cout << key << " => " << result << "\n"; |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +### Multi-Database Transactions |
| 52 | + |
| 53 | +When you need atomic writes across multiple databases, use an Environment-level transaction and switch between databases with `use()`: |
| 54 | + |
| 55 | +```cpp |
| 56 | +auto env = LMDB::Environment::instance("my.db"); |
| 57 | +auto db_users = env->database("users"); |
| 58 | +auto db_sessions = env->database("sessions"); |
| 59 | + |
| 60 | +auto txn = env->transaction(); |
| 61 | +txn->use(db_users); |
| 62 | +txn->put_key(user_id, user_data); |
| 63 | +txn->use(db_sessions); |
| 64 | +txn->put_key(session_id, session_data); |
| 65 | + |
| 66 | +if (auto err = txn->commit()) |
| 67 | + std::cerr << "commit failed: " << err.to_string() << "\n"; |
| 68 | +``` |
| 69 | +
|
| 70 | +### Compression |
| 71 | +
|
| 72 | +Enable Snappy compression per-database. Values are compressed on write and decompressed on read -- completely transparent: |
| 73 | +
|
| 74 | +```cpp |
| 75 | +auto db = env->database("logs", true); // second arg enables compression |
| 76 | +db->put_key(key, large_payload); // stored compressed |
| 77 | +auto [err, data] = db->get_key(key); // returned decompressed |
| 78 | +``` |
| 79 | + |
| 80 | +### Cursors |
| 81 | + |
| 82 | +Walk through the database in order: |
27 | 83 |
|
28 | | -int main () { |
29 | | - auto env = LMDB::Environment::instance("sample.db"); |
30 | | - |
31 | | - auto db = env->database(); |
32 | | - |
33 | | - const auto key = std::string("akey"); |
34 | | - const auto value = std::vector<bool>(10, true); |
35 | | - |
36 | | - db->put(key.data(), key.size(), value.data(), value.size()); |
37 | | - |
38 | | - const auto [error, temp_value] = db->get(key.data(), key.size()); |
| 84 | +```cpp |
| 85 | +auto txn = db->transaction(true); // read-only |
| 86 | +auto cursor = txn->cursor(); |
| 87 | + |
| 88 | +auto [err, key, value] = cursor->get(MDB_FIRST); |
| 89 | +while (!err) |
| 90 | +{ |
| 91 | + // process key/value ... |
| 92 | + std::tie(err, key, value) = cursor->get(MDB_NEXT); |
39 | 93 | } |
40 | 94 | ``` |
41 | 95 |
|
42 | | -### Cloning the Repository |
| 96 | +## Building |
| 97 | +
|
| 98 | +This project uses CMake. Dependencies (lmdb, snappy, cppfs) are included as git submodules. |
43 | 99 |
|
44 | | -This repository uses submodules, make sure you pull those before doing anything if you are cloning the project. |
| 100 | +### Cloning |
45 | 101 |
|
46 | 102 | ```bash |
47 | 103 | git clone --recursive https://github.com/gibme-c/lmdb-cpp |
48 | 104 | ``` |
49 | 105 |
|
50 | | -### As a dependency |
| 106 | +### As a Submodule Dependency |
51 | 107 |
|
52 | 108 | ```bash |
53 | | -git submodule add https://github.com/gibme-c/lmdb-cpp external/lmdb |
| 109 | +git submodule add https://github.com/gibme-c/lmdb-cpp external/lmdb-cpp |
54 | 110 | git submodule update --init --recursive |
55 | 111 | ``` |
56 | 112 |
|
| 113 | +Then in your `CMakeLists.txt`: |
| 114 | + |
| 115 | +```cmake |
| 116 | +add_subdirectory(external/lmdb-cpp) |
| 117 | +target_link_libraries(your_target PRIVATE lmdb-cpp) |
| 118 | +``` |
| 119 | + |
| 120 | +### Build Commands |
| 121 | + |
| 122 | +```bash |
| 123 | +# Configure |
| 124 | +cmake -B build -DBUILD_TESTS=1 |
| 125 | + |
| 126 | +# Build |
| 127 | +cmake --build build |
| 128 | + |
| 129 | +# Run tests |
| 130 | +ctest --test-dir build --output-on-failure |
| 131 | +``` |
| 132 | + |
| 133 | +**CMake options:** |
| 134 | + |
| 135 | +| Flag | Default | Description | |
| 136 | +|------|---------|-------------| |
| 137 | +| `BUILD_TESTS` | `OFF` | Build the test suite | |
| 138 | +| `STATIC_LIBC` | `OFF` | Statically link libc | |
| 139 | +| `ARCH` | *(unset)* | Target architecture (e.g. `native`) | |
| 140 | + |
| 141 | +## API Overview |
| 142 | + |
| 143 | +| Class | Purpose | |
| 144 | +|-------|---------| |
| 145 | +| `Environment` | Singleton-per-path manager. Opens/creates the LMDB environment file, owns databases and transactions. | |
| 146 | +| `Database` | Named key-value store within an Environment. Convenience methods handle transactions automatically. | |
| 147 | +| `Transaction` | RAII transaction wrapper. Auto-aborts on scope exit if not committed. Can span multiple databases. | |
| 148 | +| `Cursor` | Positioned iterator for walking key-value pairs in order. | |
| 149 | +| `Error` | Lightweight error type with LMDB error code mapping. Converts to `true` on failure for easy `if (auto err = ...)` checks. | |
| 150 | + |
| 151 | +Full API documentation lives in the header files under `include/`. |
| 152 | + |
57 | 153 | ## License |
58 | 154 |
|
59 | | -This wrapper is provided under the [BSD-3-Clause license](https://en.wikipedia.org/wiki/BSD_licenses) found in LICENSE. |
| 155 | +This wrapper is provided under the [BSD-3-Clause license](https://en.wikipedia.org/wiki/BSD_licenses). See LICENSE for details. |
60 | 156 |
|
61 | | -* LMDB is licensed under [The OpenLDAP Public License v2.8](http://www.OpenLDAP.org/license.html) |
62 | | -* cppfs is licensed under the [MIT License](https://en.wikipedia.org/wiki/MIT_License) |
63 | | -* snappy is licensed under the [BSD-3-Clause license](https://en.wikipedia.org/wiki/BSD_licenses) |
| 157 | +- **LMDB** -- [The OpenLDAP Public License v2.8](http://www.OpenLDAP.org/license.html) |
| 158 | +- **cppfs** -- [MIT License](https://en.wikipedia.org/wiki/MIT_License) |
| 159 | +- **snappy** -- [BSD-3-Clause](https://en.wikipedia.org/wiki/BSD_licenses) |
0 commit comments