# ormpp C++17 ORM: MySQL, PostgreSQL and SQLite

> ormpp is a header-only C++ ORM that maps registered C++ structures to MySQL, PostgreSQL, or SQLite through a common template-based interface. It suits C++ applications that want typed query construction without separate database access layers, although compile-time registration adds constraints that lightweight projects may not need.

**qicosmos/ormpp** — modern C++ ORM, C++17, support mysql, postgresql,sqlite

- Repository: https://github.com/qicosmos/ormpp
- Stars: 1,513 · Forks: 301
- Language: C++
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/qicosmos-ormpp

## ormpp's Cross-Database C++ Interface

ormpp addresses database access in C++ applications, particularly projects that use more than one of MySQL, PostgreSQL, or SQLite. Its stated goal is to reduce the amount of database-specific code required for object persistence. The README describes support for C++11, C++14, C++17, and C++20, although the repository metadata identifies C++17 as its primary language. The common interface is selected through a database template parameter, so the intended migration path is to change the database type rather than redesign every query for each engine.

The library is aimed at developers who already have a database abstraction layer with a compile-time data model. It is particularly relevant to services that need typed CRUD and query operations against ordinary C++ structures. The README describes object-to-table mapping through compile-time reflection, optional fields, automatic keys, and specialized registration macros. Those features make ormpp more than a thin SQL string helper, but they also mean that database metadata is partly represented in C++ types rather than discovered dynamically.

The supported database set is narrower than a general-purpose persistence framework. The README names MySQL, PostgreSQL, and SQLite, and the repository includes CI workflows for each of them. SQLite receives additional attention through the `sqlcipher_example.cpp` example. No other relational database is listed, so ormpp should not be treated as a portable adapter for arbitrary SQL backends.

## Header-Only Mapping and Registered Keys

ormpp is header-only, so application code includes the library headers rather than linking a separately compiled ORM binary. Mapping is driven by C++ structure declarations and registration macros. A basic auto-increment key is associated with a structure through `REGISTER_AUTO_KEY`. The macro names the structure and its key member, which gives ormpp a compile-time rule it can use when constructing insert and update operations.

The following is the key registration form documented for a `person` structure. It declares `id` as the auto-increment key and leaves `name` and `age` as ordinary mapped members.

```cpp
struct person {
  int id;
  std::string name;
  int age;
};
REGISTER_AUTO_KEY(person, id)
```

A separate registration, `REGISTER_CONFLICT_KEY`, identifies the key used for update conflicts. The README says that an update uses that registered conflict key, while an application without one falls back to its auto-increment key. This distinction matters for tables whose natural identifier is not the generated numeric primary key. For example, a `student` record can register `code` as its conflict key.

The README also documents `REGISTER_SKIP_INSERT_FIELD`. This omits a selected member from generated inserts so a database default can supply its value. The documented example skips a timestamp field that would otherwise require an application value. Custom SQL remains the documented route when an insert must provide that value explicitly. `replace` and `update` are not affected by this insertion-only rule.

These registrations are intended for static initialization. The README instructs users to complete field registration before concurrent database operations begin. Registration therefore belongs in type-definition or translation-unit initialization, not in request handling.

## Typed Query Construction and Row Collection

The query API builds SQL through typed expressions based on member pointers such as `&test_optional::id`. A query starts with `select`, names a structure with `from<T>()`, adds predicates or joins, and executes through `collect()`. Selecting `all` requests all mapped fields, while explicit `col(...)` expressions can reduce the selected result set. Ordering, limits, offsets, grouping, aggregate functions, and joins are expressed as stages in the same chain.

This design does more than concatenate strings. The README describes a type-safe SQL builder whose field expressions are checked at compile time. A parameter is created by calling `param()`, and `collect()` receives the value to bind to that placeholder. The sequence matters because the builder must know which values belong to which parameterized expressions.

The documented SQLite query below selects every mapped field and limits the result to five rows. Its predicate uses `in(1, 2)`, while ordering and offset are explicit.

```cpp
auto l2 = sqlite.select(all)
              .from<test_optional>()
              .where(col(&test_optional::id).in(1, 2))
              .order_by(col(&test_optional::id).desc(),
                        col(&test_optional::name).desc())
              .limit(5)
              .offset(0)
              .collect();
```

Parameterized lookup follows the same structure. Calling `param()` marks the expression as a placeholder, and passing a value to `collect()` binds it.

```cpp
auto l0 = sqlite.select(all)
              .from<test_optional>()
              .where(col(&test_optional::id).param())
              .collect(2);
```

Aggregate operations include `count()`, `sum()`, `avg()`, `min()`, and `max()`. Grouped queries can add `group_by()` and `having()`. The API also documents inner joins, with selected columns coming from multiple mapped structures. Compared with writing SQL directly, this offers compile-time structure and field references, but it also ties common queries to the mapper's expression model.

## Connections, SQLite Encryption, and Async MySQL

ormpp includes a database connection pool with automatic reclamation and health checks. Pooling matters for database-backed services because repeatedly opening connections adds overhead and can exhaust server connection capacity. The README does not provide pool configuration commands or a complete initialization snippet, so pool size, timeout, and health-check settings cannot be confirmed.

The common database interface is instantiated in the examples as `sqlite`. The snippets assume that this object has already been configured and are not complete programs. Consequently, they do not establish the exact constructor signature, connection string format, port, or error-handling contract. A new user must consult the repository's current examples and dependency setup for those details.

For SQLite, the repository includes `example/sqlcipher_example.cpp`. The feature list identifies SQLCipher support for encrypted SQLite storage. That applies to SQLite deployments requiring database encryption, not automatically to MySQL or PostgreSQL connections.

The library also documents asynchronous MySQL queries based on `ASIO` and `async_simple`. This is a narrower capability than the common synchronous interface. It is relevant to MySQL applications with non-blocking I/O requirements, but the README does not show an asynchronous program from connection setup through event-loop completion. The project should not be judged as having one uniform asynchronous API across all three databases based on the MySQL-specific description.

The README also documents AOP-style concerns through `warper_connect`, with logging and validation given as examples. That hook can place cross-cutting behavior around database operations, although no configuration details appear in the repository examples.

## First Compile-Time ormpp Example

The README does not show a complete installation command or dependency command, so this tutorial cannot invent one. It states that ormpp is header-only and points to `iguana` version 1.0.9. The repository layout includes `CMakeLists.txt`, `example/`, `example/main.cpp`, and CI workflows for SQLite, MySQL, and PostgreSQL. Those are the concrete places to inspect when establishing the supported build procedure.

Start with the model and key registration used by the README examples. This code defines the static mapping rule but does not open a database or execute a query.

```cpp
struct person {
  int id;
  std::string name;
  int age;
};
REGISTER_AUTO_KEY(person, id)
```

After defining and registering the structure, a caller uses a configured database object with a typed query. The following is a first query shape, not a standalone program.

```cpp
auto l = sqlite.select(all)
             .from<test_optional>()
             .where(col(&test_optional::name).param())
             .collect(std::string("test"));
```

The string passed to `collect()` binds to the parameter generated by `param()`. The reader should receive a collection of mapped `test_optional` objects, with the query selecting rows whose `name` matches the bound value. Before running it, the corresponding table and database connection must exist. The README does not document rollback, schema migration, or a complete table-creation procedure.

## Maintenance, License, and ormpp Trade-offs

The repository is not archived. Its last push was on 2026-10-05, followed by release 0.2.2 on 2026-07-29. Release 0.2.1 was published on 2026-03-13, and release 0.2.0 on 2025-06-11. This history shows recent project activity, but the repository does not define a long-term support window or compatibility policy between those releases.

ormpp is licensed under Apache-2.0. That identifier matters for organizations reviewing redistribution and integration terms, but this review does not provide legal advice. Header-only use simplifies distribution because applications do not need to link a compiled ormpp library, although their own build still needs compatible headers and the required database client dependencies.

A real alternative is direct use of each database's native C++ driver while maintaining a small project-owned persistence interface. That approach provides direct access to engine-specific SQL and driver behavior, but it does not provide ormpp's automatic structure registration, typed member-pointer expressions, or template-based database switching. Another alternative is a data mapper with runtime schema discovery and migration tooling, although no such project is named in the repository documentation.

ormpp is the wrong choice when runtime-defined models are essential, when queries must escape the typed expression system frequently, or when an application needs documented schema migration and rollback workflows. It is also a questionable dependency when only SQLite is needed and the application values a direct driver API more than cross-database query construction.

## Conclusion

ormpp is appropriate for C++17 teams that need one typed database interface across MySQL, PostgreSQL, or SQLite and can accommodate compile-time structure registration. It is a poor fit for applications that depend on an untyped SQL builder, need runtime-defined record shapes, or prefer a database library with a more mature documented migration path. Before adoption, verify that the documented example and dependency setup match the selected database driver, then confirm the required connection and table-creation code because the README examples begin after database initialization. The first project-specific acceptance point is a small build that registers a real structure, performs a parameterized query, and maps returned rows into that structure.

## FAQ

### Does ormpp require compiling the ORM library?

No. The README describes ormpp as header-only, so application code includes its headers rather than compiling the ORM as a separate library.

### Which databases does ormpp support?

The documented database set is MySQL, PostgreSQL, and SQLite. The repository also contains CI workflows for all three.

### How does ormpp bind values to SQL queries?

A field expression marked with `param()` becomes a placeholder, and `collect()` receives the corresponding value. The README demonstrates binding an integer or `std::string` through this mechanism.

### Can ormpp leave a field unset so the database can supply its default?

Yes. `REGISTER_SKIP_INSERT_FIELD` excludes a field from generated inserts. Custom SQL is documented for cases where that field must be supplied explicitly.

### Does ormpp support asynchronous database queries?

The README documents asynchronous MySQL queries based on `ASIO` and `async_simple`. It does not describe the same asynchronous capability for PostgreSQL or SQLite.

### Can ormpp use SQLCipher with SQLite?

Yes. The feature list identifies SQLCipher encrypted storage for SQLite, and the repository includes `example/sqlcipher_example.cpp`.

## Sources

- [Issues](https://github.com/qicosmos/ormpp/issues)
- [License: Apache-2.0](https://github.com/qicosmos/ormpp/blob/master/LICENSE)
- [qicosmos/ormpp on GitHub](https://github.com/qicosmos/ormpp)
- [README](https://github.com/qicosmos/ormpp/blob/master/README.md)
- [Releases](https://github.com/qicosmos/ormpp/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qicosmos-ormpp
