tinydb.hpp gives you tables, rows, and a tiny subset of SQL in a single
header. It is designed for situations where you need structured, query-able
data inside a binary but pulling in SQLite (or anything larger) would be
overkill: CLI tools, test fixtures, local data pipelines, quick prototypes, and
embedded utilities.
It is intentionally small. One header, no external dependencies, no build-time code generation, no macros to configure.
- Features
- Quick start
- Typed table API
- Supported SQL
- Benchmark
- Build
- Using it in your own project
- Project layout
- Design notes
- License
| Header-only | Drop include/tinydb.hpp into your project and #include it. |
| Zero dependencies | Nothing beyond the C++17 standard library. |
| Typed values | std::variant of null, bool, int64, double, string. |
| Two APIs | A tinydb::engine for SQL strings and a tinydb::table for direct C++. |
| Lambda predicates | Filter rows with any callable, not just the built-in operators. |
| CMake target | tinydb::tinydb interface target, plays nicely with add_subdirectory. |
#include <iostream>
#include "tinydb.hpp"
int main() {
tinydb::engine db;
db.execute("CREATE TABLE users (id, name, age, city)");
db.execute("INSERT INTO users VALUES (1, 'Alice', 30, 'Berlin')");
db.execute("INSERT INTO users VALUES (2, 'Bob', 24, 'Tallinn')");
db.execute("INSERT INTO users VALUES (3, 'Carol', 35, 'Bursa')");
auto result = db.execute("SELECT id, name, city FROM users WHERE age > 25");
std::cout << tinydb::dump(result.rows);
}Output:
{city: Berlin, name: Alice, id: 1}
{city: Bursa, name: Carol, id: 3}
If you do not need SQL parsing, use the table API directly. It is faster, avoids stringly-typed queries, and lets you filter with any predicate.
tinydb::table users({"id", "name", "active"});
users.insert({
{"id", std::int64_t{1}},
{"name", std::string("Alice")},
{"active", true},
});
users.insert({
{"id", std::int64_t{2}},
{"name", std::string("Bob")},
{"active", false},
});
// Built-in helpers
auto adults = users.gt("id", std::int64_t{0});
// Custom predicate
auto active = users.where("active", [](const tinydb::value& v) {
return std::holds_alternative<bool>(v) && std::get<bool>(v);
});table::where, table::eq, table::gt, and table::lt all return a
result_set you can iterate with rows().
CREATE TABLE users (id, name, age)
INSERT INTO users VALUES (1, 'Alice', 30)
SELECT * FROM users
SELECT id, name FROM users WHERE age > 25
SELECT id, city FROM users WHERE city = 'Berlin'WHERE supports =, >, <, >=, and <=. String literals use single or
double quotes. Numeric literals are parsed as int64 when possible, otherwise
double. NULL, true, and false are recognized as literal keywords.
This is deliberately a small subset. No joins, no grouping, no subqueries, no transactions. If you need those, use a real database.
examples/benchmark.cpp inserts 50k typed rows and runs two scans. On a recent
laptop (g++ -O2, single thread) the output looks like:
rows inserted : 50000 (27.8 ms)
range scan : 4950 rows ( 4.2 ms)
equality scan : 25000 rows (14.2 ms)
It is not meant to compete with SQLite — it is here so that when you make a change you can see whether it hurts the insert and scan paths.
Any C++17 compiler and CMake 3.16+.
cmake -S . -B build
cmake --build build
ctest --test-dir buildThis produces three targets:
| Target | What it is |
|---|---|
tinydb_example |
The Quick start snippet. |
tinydb_benchmark |
The small insert / scan benchmark. |
tinydb_tests |
The test binary used by ctest. |
Examples and tests can be turned off when you embed the library:
cmake -S . -B build -DTINYDB_BUILD_EXAMPLES=OFF -DTINYDB_BUILD_TESTS=OFFBecause the library is header-only, the easiest integration is to copy
include/tinydb.hpp into your project and #include it.
With CMake you can also consume it via add_subdirectory:
add_subdirectory(third_party/tinydb.hpp)
target_link_libraries(my_app PRIVATE tinydb::tinydb)Or with FetchContent:
include(FetchContent)
FetchContent_Declare(
tinydb
GIT_REPOSITORY https://github.com/aykutsp/tinydb.hpp.git
GIT_TAG main
)
FetchContent_MakeAvailable(tinydb)
target_link_libraries(my_app PRIVATE tinydb::tinydb)include/tinydb.hpp header-only library
examples/basic.cpp SQL-style usage
examples/benchmark.cpp insert / scan microbenchmark
tests/test_basic.cpp ctest target
docs/images/ readme assets
CMakeLists.txt
- Values are variants. A row is a
std::unordered_map<std::string, value>wherevalueis astd::variant. This keeps the type system honest without introducing a row/column type schema. - Tables own their rows. Inserts copy-or-move into a
std::vector<row>. Queries return aresult_setholding projected copies, so the underlying table is never mutated by a read. - Parser is a tokenizer, not a grammar. The SQL frontend handles exactly the statements documented above. Anything fancier belongs in the typed API.
- No hidden allocations at query time beyond the result set. Predicates
are
std::function, so you can inline whatever logic you need.
MIT. See LICENSE.
Feel free to use this project however you like — fork it, ship it, tear it apart, build something bigger on top of it. If you end up using it in something public, a small credit or a link back would make my day, but it's not a requirement. Thanks for taking a look.