Skip to content

Commit 7fee7a0

Browse files
Security hardening, expanded tests, and documentation overhaul
- Fix TOCTOU race in Environment::database() with atomic find_or_insert - Fix undefined behavior: Database destructor self-erasure during Environment teardown - Replace goto-based retry macro with structured while loops - Add [[nodiscard]] to all Error-returning public methods - Add compiler warning flags (-Wall -Wextra -Wpedantic, /W4) and hardening flags - Fix all -Wreorder warnings across both GCC and MSVC - Rename BUILD_UNIT_TESTS to BUILD_TESTS - Bump cmake_minimum_required to 3.10, remove dead compat branches - Expand test suite from 4 print-only tests to 30 assertion-based tests - Rewrite inline documentation with approachable tone and usage examples - Rewrite README with working examples, build instructions, and API overview
1 parent 2707667 commit 7fee7a0

File tree

11 files changed

+1454
-879
lines changed

11 files changed

+1454
-879
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,11 @@ jobs:
8585
CXX: ${{ matrix.CXX }}
8686
run: |
8787
cd build
88-
${{ matrix.CMAKE_CMD }} -DBUILD_UNIT_TESTS=1
88+
${{ matrix.CMAKE_CMD }} -DBUILD_TESTS=1
8989
- name: Build Project
9090
run: |
9191
cd build
9292
cmake --build . -j2
9393
94-
- name: Unit Tests (Linux & OSX)
95-
if: matrix.os != 'windows-latest' || matrix.name == 'mingw-gcc'
96-
run: |
97-
cd build
98-
./db_test
99-
- name: Unit Tests (Windows)
100-
if: matrix.os == 'windows-latest' && matrix.name == 'msvc'
101-
run: |
102-
cd build/Debug
103-
./db_test.exe
94+
- name: Unit Tests
95+
run: ctest --test-dir build -C Debug --output-on-failure

CMakeLists.txt

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.5)
1+
cmake_minimum_required(VERSION 3.10)
22

33
if(DEFINED CMAKE_BUILD_TYPE)
44
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Choose the type of build, options are: Debug, Release, RelWithDebInfo")
@@ -16,13 +16,7 @@ set(LIB_MINOR_VERSION "0")
1616
set(LIB_PATCH_VERSION "0")
1717
set(LIB_VERSION_STRING "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_PATCH_VERSION}")
1818

19-
if(CMAKE_VERSION VERSION_LESS 3.0)
20-
project(lmdbcpp CXX)
21-
enable_language(CXX)
22-
else()
23-
cmake_policy(SET CMP0048 NEW)
24-
project(lmdbcpp VERSION "${LIB_VERSION_STRING}" LANGUAGES CXX)
25-
endif()
19+
project(lmdbcpp VERSION "${LIB_VERSION_STRING}" LANGUAGES CXX)
2620

2721
if(NOT MSVC)
2822
find_program(CCACHE_PROGRAM ccache)
@@ -37,7 +31,6 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
3731
set(CMAKE_SKIP_INSTALL_RULES OFF FORCE)
3832
set(CMAKE_SKIP_PACKAGE_ALL_DEPENDENCY ON FORCE)
3933
set(CMAKE_SUPPRESS_REGENERATION ON)
40-
set(CMAKE_POLICY_WARNING_CMP0048 OFF)
4134
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
4235
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
4336

@@ -58,18 +51,18 @@ if(STATIC_LIBC)
5851
message(STATUS "Forcing build with statically linked libc, binaries may not be portable")
5952
endif()
6053

61-
option(BUILD_UNIT_TESTS "Build included tests" OFF)
62-
if(DEFINED ENV{BUILD_UNIT_TESTS})
63-
set(BUILD_UNIT_TESTS $ENV{BUILD_UNIT_TESTS})
54+
option(BUILD_TESTS "Build included tests" OFF)
55+
if(DEFINED ENV{BUILD_TESTS})
56+
set(BUILD_TESTS $ENV{BUILD_TESTS})
6457
endif()
65-
if(BUILD_UNIT_TESTS)
58+
if(BUILD_TESTS)
6659
message(STATUS "Tests added to targets list")
6760
endif()
6861

6962

7063
if(MSVC)
71-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D_CRT_SECURE_NO_WARNINGS")
72-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std:c++17 /wd4267 /wd4804 /wd4996 /D_DLL /D_CRT_SECURE_NO_WARNINGS")
64+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /D_CRT_SECURE_NO_WARNINGS")
65+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std:c++17 /W4 /wd4267 /wd4804 /wd4996 /D_DLL /D_CRT_SECURE_NO_WARNINGS")
7366

7467
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2")
7568
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2")
@@ -89,14 +82,14 @@ else()
8982
set(MAES_FLAG "")
9083
endif ()
9184

92-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Wuninitialized ${MAES_FLAG}")
93-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wuninitialized ${MAES_FLAG}")
85+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra -Wpedantic -Wuninitialized ${MAES_FLAG}")
86+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -Wextra -Wpedantic -Wuninitialized ${MAES_FLAG}")
9487

9588
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3 -Og")
9689
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -Og")
9790

98-
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG -O3")
99-
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3")
91+
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG -O3 -fstack-protector-strong -D_FORTIFY_SOURCE=2")
92+
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3 -fstack-protector-strong -D_FORTIFY_SOURCE=2")
10093

10194
if(APPLE)
10295
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
@@ -143,12 +136,15 @@ if(WIN32)
143136
target_link_libraries(lmdbcpp-static ws2_32 advapi32 crypt32 gdi32 user32)
144137
endif()
145138

146-
if(BUILD_UNIT_TESTS)
139+
if(BUILD_TESTS)
140+
enable_testing()
141+
147142
set(TEST_SOURCES
148143
tests/db_test.cpp
149144
)
150145

151-
add_executable(dbtest ${TEST_SOURCES})
152-
target_link_libraries(dbtest lmdbcpp-static)
153-
set_property(TARGET dbtest PROPERTY OUTPUT_NAME "db_test")
146+
add_executable(lmdb-tests ${TEST_SOURCES})
147+
target_link_libraries(lmdb-tests lmdbcpp-static)
148+
149+
add_test(NAME lmdb-tests COMMAND lmdb-tests)
154150
endif()

README.md

Lines changed: 134 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,159 @@
1-
# lmdb-cpp: A simple OOP-style wrapper around [lmdb](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)
1+
# lmdb-cpp
22

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.
64

7-
The following *features* have been baked in:
5+
## Features
86

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()`.
1712

18-
## Documentation
13+
## Quick Start
1914

20-
C++ API documentation can be found in the headers (.h)
21-
22-
### Example Use
23-
24-
```c++
15+
```cpp
2516
#include <lmdb_cpp.hpp>
2617
#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:
2783

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);
3993
}
4094
```
4195
42-
### Cloning the Repository
96+
## Building
97+
98+
This project uses CMake. Dependencies (lmdb, snappy, cppfs) are included as git submodules.
4399
44-
This repository uses submodules, make sure you pull those before doing anything if you are cloning the project.
100+
### Cloning
45101
46102
```bash
47103
git clone --recursive https://github.com/gibme-c/lmdb-cpp
48104
```
49105

50-
### As a dependency
106+
### As a Submodule Dependency
51107

52108
```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
54110
git submodule update --init --recursive
55111
```
56112

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+
57153
## License
58154

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.
60156

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)

external/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ add_subdirectory(snappy)
44

55
set_property(TARGET cppfs lmdb snappy PROPERTY FOLDER "external")
66

7+
# Treat external headers as SYSTEM so their warnings don't pollute our build
8+
foreach(_target cppfs lmdb snappy)
9+
get_target_property(_dirs ${_target} INTERFACE_INCLUDE_DIRECTORIES)
10+
if(_dirs)
11+
set_target_properties(${_target} PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_dirs}")
12+
endif()
13+
endforeach()
14+
715
add_library(lmdbcpp-thirdparty INTERFACE)
816
target_link_libraries(lmdbcpp-thirdparty INTERFACE cppfs lmdb snappy)
917

external/snappy

0 commit comments

Comments
 (0)