Header-only C++17 HTTP client for MongrelDB - embedded+server database with SQL, vector search, full-text search, and AI-native retrieval.
RAII wrapper, std::string/std::vector/std::optional, and an exception hierarchy. Built on libcurl. No external runtime dependencies beyond libc and libcurl. Also bundles the engine's C ABI header for native embedding.
| Surface | Package | Install |
|---|---|---|
| C++ client | MongrelDB-CPP |
header-only; build from source with CMake + libcurl |
The daemon keeps a rolling window of prior commit epochs. You can inspect and configure that window through the client:
// Current configuration
std::uint64_t epochs = db.history_retention_epochs();
std::uint64_t earliest = db.earliest_retained_epoch();
// Resize the window. Requires ADMIN permission on an auth-enabled daemon.
auto cfg = db.set_history_retention_epochs(1024);- Admin-only: on a daemon started with
--auth-tokenor--auth-users, the caller must be an admin user. In open mode the call is allowed without credentials. - Cannot restore lost history: the first time you enable retention it starts at the current epoch, and increasing the window later cannot recreate epochs that were already discarded by compaction or a smaller prior window.
- A C++17 compiler (g++ 7+, clang++ 5+, MSVC 19.14+)
- libcurl (the HTTP transport). On Debian/Ubuntu install
libcurl4-openssl-dev; on Fedoracurl-devel; on macOS it ships with the system. - CMake 3.16 or newer (to build the tests; the library itself is header-only)
- A running
mongreldb-serverdaemon
- Typed CRUD over the Kit transaction endpoint:
put,upsert(insert-or-update on PK conflict),delby row id,delete_by_pk, with idempotency keys for safe retries. - Query builder that pushes conditions down to the engine's specialized indexes for sub-millisecond lookups: bitmap equality, learned-range, null checks, and FM-index full-text search. Conditions are AND-ed.
- Idempotent batch transactions - all operations staged locally and committed atomically, with the engine enforcing unique, foreign key, and check constraints at commit time. Idempotency keys return the original response on duplicate commits, even after a crash.
- Full SQL access through the DataFusion-backed
/sqlendpoint: recursive CTEs, window functions,CREATE TABLE AS SELECT, materialized views, and multi-statement execution. - Schema management: typed table creation with optional
enum_variantsanddefault_valueper column, full schema catalog, and per-table descriptors. - RAII and move semantics: the client owns its libcurl state; destructor cleans up. Not copyable, movable.
- Native Tier-1 embedding: bundle the C ABI headers (
mongreldb/mongreldb_engine.h+mongreldb/mongreldb_kit.h) and linklibmongreldb+libmongreldb_kitfor in-process access with zero serialization overhead. SQL, migrations, and the query builder are all available through the FFI. Download prebuilt libraries from the MongrelDB releases page (mongreldb-native-*andmongreldb-kit-native-*archives). - Exception hierarchy:
MongrelDBException(base),AuthException(401/403),NotFoundException(404),ConflictException(409, with structured code and op index),QueryException(everything else). - Modern types:
std::vector,std::optional,std::string,std::int64_t. No raw pointers in the public API.
Runnable, commented examples live in the docs:
- Quickstart - install, start the daemon, write and run a complete program.
- Transactions - batch commits, idempotency keys, constraint handling.
- Queries - every native condition type and the index it pushes down to.
- SQL - recursive CTEs, window functions, advanced SQL.
- Authentication - bearer token, HTTP Basic, and open modes.
- Errors - the exception hierarchy and recovery patterns.
examples/column_constraints.cpp-enum_variantsanddefault_valuecolumns.
#include <mongreldb/mongreldb.hpp>
#include <iostream>
int main() {
// Connect to a running mongreldb-server daemon.
mongreldb::MongrelDBClient db("http://127.0.0.1:8453");
// Create a table. Column ids are stable on-wire identifiers.
db.create_table("orders", {
{1, "id", "int64", /*primary_key=*/true, /*nullable=*/false},
{2, "customer", "varchar", /*primary_key=*/false, /*nullable=*/false},
{3, "amount", "float64", /*primary_key=*/false, /*nullable=*/false},
});
// Insert rows (cells pair column id + value).
db.put("orders", {{1, mongreldb::Value::integer(1)},
{2, mongreldb::Value::string("Alice")},
{3, mongreldb::Value::floating(99.50)}});
db.put("orders", {{1, mongreldb::Value::integer(2)},
{2, mongreldb::Value::string("Bob")},
{3, mongreldb::Value::floating(150.00)}});
// Query with a native index condition (learned-range index).
mongreldb::Condition cond;
cond.kind = mongreldb::CondKind::Range;
cond.column_id = 3;
cond.lo = 100.0; cond.lo_set = true;
auto res = db.query("orders", {cond}, {1, 2}, 100);
std::cout << "rows: " << res.rows.size() << "\n";
std::cout << "count: " << db.count("orders") << "\n"; // 2
// Run SQL.
db.sql("UPDATE orders SET amount = 200.0 WHERE customer = 'Bob'");
return 0;
}// Bearer token (--auth-token mode)
mongreldb::MongrelDBClient db("http://127.0.0.1:8453", "my-secret-token");
// HTTP Basic (--auth-users mode)
mongreldb::MongrelDBClient db("http://127.0.0.1:8453",
mongreldb::MongrelDBClient::BasicAuth{"admin", "s3cret"});A token takes precedence over basic auth if both are supplied.
Operations are staged locally and committed atomically. The engine enforces unique, foreign key, and check constraints at commit time.
std::vector<mongreldb::Op> ops(3);
ops[0].type = mongreldb::OpType::Put;
ops[0].table = "orders";
ops[0].cells = {{1, mongreldb::Value::integer(10)},
{2, mongreldb::Value::string("Dave")},
{3, mongreldb::Value::floating(50.00)}};
ops[1].type = mongreldb::OpType::Put;
ops[1].table = "orders";
ops[1].cells = {{1, mongreldb::Value::integer(11)},
{2, mongreldb::Value::string("Eve")},
{3, mongreldb::Value::floating(75.00)}};
ops[2].type = mongreldb::OpType::DeleteByPk;
ops[2].table = "orders";
ops[2].pk_value = mongreldb::Value::integer(2);
try {
db.commit(ops, "batch-1"); // atomic - all or nothing
} catch (const mongreldb::ConflictException &e) {
std::cerr << "constraint " << e.code() << " at op "
<< e.op_index().value_or(0) << ": " << e.what() << "\n";
}Conditions push down to the engine's specialized indexes. Each Condition
targets one index; multiple conditions are AND-ed.
// Bitmap equality (low-cardinality columns)
mongreldb::Condition bitmap;
bitmap.kind = mongreldb::CondKind::BitmapEq;
bitmap.column_id = 2; bitmap.str_value = "Alice";
// Range query (learned-range index)
mongreldb::Condition range;
range.kind = mongreldb::CondKind::Range;
range.column_id = 3; range.lo = 50.0; range.lo_set = true;
range.hi = 150.0; range.hi_set = true;
// Full-text search (FM-index)
mongreldb::Condition fts;
fts.kind = mongreldb::CondKind::FmContains;
fts.column_id = 2; fts.str_value = "database performance";
auto res = db.query("orders", {bitmap, range}, {1, 3}, 50);
if (res.truncated) {
// result set hit the limit; more matches exist on the server
}Each mongreldb::Column carries optional fields that the engine
enforces at commit time:
enum_variants(std::vector<std::string>) - restricts a varchar column to a fixed variant set. The engine rejects writes outside the set.default_value_jsonsends a caller-validated raw JSON scalar;default_exprsends"now"or"uuid"and takes precedence. The legacy stringdefault_valueremains supported.
These fields are omitted from the wire payload when unset, so older servers that don't recognise them still accept the request.
db.create_table("orders", {
{1, "id", "int64", /*primary_key=*/true, /*nullable=*/false},
{2, "status", "varchar", /*primary_key=*/false, /*nullable=*/false,
/*enum_variants=*/{"pending", "active", "closed"}},
{3, "amount", "float64", /*primary_key=*/false, /*nullable=*/true,
/*default_value=*/std::optional<std::string>{"0.0"}},
{4, "label", "varchar", /*primary_key=*/false, /*nullable=*/true,
/*default_value_json=*/std::optional<std::string>{"\"none\""}},
{5, "created", "timestamp_nanos", /*primary_key=*/false, /*nullable=*/false,
/*default_expr=*/std::optional<std::string>{"now"}},
});default_value_json is sent verbatim, so string literals must be quoted on
the client side ("\"none\""). default_expr: "now" asks the engine to
insert the current timestamp on every omitted write.
Table CHECKs use the additive raw constraints overload:
db.create_table("orders", columns,
R"({"checks":[{"id":1,"name":"amount_nonneg","expr":{"Ge":[{"Col":3},{"Lit":{"Float64":0.0}}]}}]})");Trying to insert "cancelled" into status throws a ConflictException at
commit time. A put that omits the amount cell writes the default 0.0
instead.
db.sql("INSERT INTO orders (id, customer, amount) VALUES (99, 'Zoe', 999.0)");
db.sql("CREATE TABLE archive AS SELECT * FROM orders WHERE amount > 500");
// Recursive CTEs and window functions
db.sql("WITH RECURSIVE r(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM r WHERE n<10) "
"SELECT n FROM r");
db.sql("SELECT id, ROW_NUMBER() OVER (PARTITION BY customer ORDER BY amount DESC) "
"FROM orders");User, role, and permission management is performed through SQL against the daemon's catalog. Passwords are Argon2id-hashed server-side.
db.sql("CREATE USER admin WITH PASSWORD 's3cret-pw'");
db.sql("ALTER USER admin SET ADMIN TRUE");
db.sql("CREATE ROLE analyst");
db.sql("GRANT select ON orders TO analyst"); // table-level permission
db.sql("GRANT analyst TO alice");
db.sql("SELECT username FROM catalog.users"); // list users
db.sql("SELECT name FROM catalog.roles"); // list rolesEvery failure throws a subclass of MongrelDBException. Catch the base for
"anything went wrong", or a specific subclass to branch on the category.
try {
auto body = db.schema_for("missing_table");
} catch (const mongreldb::ConflictException &e) {
std::cerr << "constraint " << e.code() << ": " << e.what() << "\n";
} catch (const mongreldb::NotFoundException &e) {
std::cerr << "not found: " << e.what() << "\n";
} catch (const mongreldb::AuthException &e) {
std::cerr << "not authorized: " << e.what() << "\n";
} catch (const mongreldb::QueryException &e) {
std::cerr << "query/server error: " << e.what() << "\n";
} catch (const mongreldb::MongrelDBException &e) {
std::cerr << "error: " << e.what() << "\n";
}| Method | Description |
|---|---|
MongrelDBClient(url) |
Construct a client (empty url defaults to http://127.0.0.1:8453) |
MongrelDBClient(url, token) |
Bearer token auth (--auth-token mode) |
MongrelDBClient(url, BasicAuth{user, pass}) |
HTTP Basic auth (--auth-users mode) |
set_timeout(seconds) |
Per-request timeout (default 30) |
health() |
Check daemon health (returns bool) |
table_names() |
List table names (vector<string>) |
history_retention() |
Full retention response (HistoryRetention) |
history_retention_epochs() |
Configured MVCC window size (uint64_t) |
earliest_retained_epoch() |
Oldest epoch currently retained (uint64_t) |
set_history_retention_epochs(n) |
Resize the MVCC window; returns HistoryRetention |
create_table(name, columns) |
Create a table (returns table id); each Column may set enum_variants, default_value, default_value_json, or default_expr |
create_table(name, columns, constraints_json) |
Create a table with native constraints JSON (including CHECKs) |
drop_table(name) |
Drop a table |
count(table) |
Row count |
put(table, cells, key) |
Insert a row |
upsert(table, cells, update_cells, key) |
Upsert a row |
del(table, row_id) |
Delete by row id |
delete_by_pk(table, pk) |
Delete by primary key |
commit(ops, key) |
Commit a batch atomically |
query(table, conds, proj, limit, offset) |
Run a paged native query (returns Result) |
sql(statement) |
Execute SQL (returns raw body) |
schema() |
Full schema catalog (raw JSON) |
schema_for(table) |
Single-table descriptor (raw JSON) |
| Field | Type | Purpose |
|---|---|---|
id |
int64 |
Stable on-wire column identifier. |
name |
string |
Human-readable name (not used on the wire). |
ty |
string |
Engine type tag: int64, float64, varchar, bool, ... |
primary_key |
bool |
Marks the primary key column. |
nullable |
bool |
Allows NULL cells. |
enum_variants |
vector<string> |
Optional. Restricts a varchar column to these values; omitted from the wire payload when empty. |
default_value |
optional<string> |
Optional. Legacy string default-value DSL expression; omitted when unset. |
default_value_json |
optional<string> |
Optional. Raw JSON scalar sent verbatim as default_value; omitted when unset. |
default_expr |
optional<string> |
Optional. Dynamic default ("now", "uuid"); takes precedence and is omitted when unset. |
| Class | HTTP status | Notes |
|---|---|---|
MongrelDBException |
- | Base; catch for "anything went wrong" |
AuthException |
401, 403 | Bad/missing credentials |
NotFoundException |
404 | Missing table, schema, or resource |
ConflictException |
409 | Carries .code() and .op_index() |
QueryException |
400, 5xx, network | Everything else |
include/mongreldb/mongreldb_engine.h is a verbatim copy of the engine's C ABI
for users who link the engine natively (via libmongreldb). See the comments in
that header for the handle-based put/query/transaction/auth surface.
The library is header-only; consumers only need the include path and libcurl. The CMake project builds the tests:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
# Run the live test suite (boots mongreldb-server itself if it can find the
# binary in this order: MONGRELDB_SERVER env var, ./bin/mongreldb-server,
# or mongreldb-server on PATH). Set MONGRELDB_URL to use an already-running
# daemon. Tests self-skip when no binary is available.
ctest --test-dir build --output-on-failureFetch a prebuilt server binary from the MongrelDB releases:
mkdir -p bin
curl -fsSL -o bin/mongreldb-server \
https://github.com/visorcraft/MongrelDB/releases/download/v0.52.3/mongreldb-server-linux-x64
chmod +x bin/mongreldb-serverWith CMake:
# In your CMakeLists.txt
find_package(CURL REQUIRED)
add_subdirectory(mongreldb_cpp)
target_link_libraries(your_app PRIVATE mongreldb_cpp)Or compile directly (header-only):
c++ -std=c++17 -I/path/to/MongrelDB-CPP/include your_app.cpp -lcurl -o your_appFor in-process access with zero serialization overhead, link the prebuilt
libmongreldb (core engine) and optionally libmongreldb_kit (schema model,
migrations, query builder) instead of connecting to a daemon. Download the
prebuilt libraries from the
MongrelDB releases page:
# Download for your platform, e.g. linux-x64-gnu
curl -fsSL -o native.tar.gz \
https://github.com/visorcraft/MongrelDB/releases/download/v0.52.3/mongreldb-native-linux-x64-gnu.tar.gz
tar xzf native.tar.gz # produces mongreldb-native/{lib,include}/
curl -fsSL -o kit-native.tar.gz \
https://github.com/visorcraft/MongrelDB/releases/download/v0.52.3/mongreldb-kit-native-linux-x64-gnu.tar.gz
tar xzf kit-native.tar.gz # produces mongreldb-kit-native/{lib,include}/Then link against the bundled headers and call the C ABI directly from C++:
# Core engine: create database, put rows, run SQL
c++ -std=c++17 -Iinclude -Imongreldb-native/include your_engine_app.cpp \
-Lmongreldb-native/lib -lmongreldb -lpthread -ldl -lm \
-Wl,-rpath,mongreldb-native/lib -o your_engine_app
# Kit layer: schema model, migrations, query builder
c++ -std=c++17 -Iinclude -Imongreldb-native/include -Imongreldb-kit-native/include \
your_kit_app.cpp \
-Lmongreldb-kit-native/lib -lmongreldb_kit \
-Lmongreldb-native/lib -lmongreldb \
-lpthread -ldl -lm \
-Wl,-rpath,mongreldb-native/lib -Wl,-rpath,mongreldb-kit-native/lib \
-o your_kit_appThe core ABI header mongreldb/mongreldb_engine.h and Kit header
mongreldb/mongreldb_kit.h are bundled in the include/mongreldb/ directory.
Wrap the C ABI handles in RAII C++ classes (std::unique_ptr with custom
deleters) for idiomatic use.
See the FFI crate's docs/migrations.md
for the full MigrationOp to FFI call mapping when running migrations via the
native ABI.
Contributions are welcome. Please:
- Open an issue first for non-trivial changes.
- Add focused tests near your change - the suite must stay green.
- Keep the code C++17, warning-clean under
-Wall -Wextra -Wpedantic. - Match the existing style: 4-space indent,
snake_casemethods, RAII, no raw pointers in the public API.
Dual-licensed under the MIT License or the Apache License, Version 2.0, at your option. See MIT OR Apache-2.0 for the full text.
SPDX-License-Identifier: MIT OR Apache-2.0
