upq¶
upq is an asynchronous PostgreSQL client and connection pool for modern C++23.
It’s designed for uvent, fully coroutine-based, and built directly on top of libpq without libpqxx or any
blocking calls.
Design Goals¶
- Fully non-blocking I/O integrated with
uvent - No background threads or runtime schedulers
- Coroutine-native query interface (
co_await) - Clear layering: pool → connection → transaction
- Minimal allocations and zero unnecessary copies
- Compile-time reflection for type-safe query/parameter mapping
Components¶
| Component | Purpose |
|---|---|
| PgPool | Global async connection pool (PGconn management) |
| PgConnectionLibpq | Non-blocking wrapper over libpq I/O and protocol state |
| PgTransaction | Transactional wrapper on pinned pooled connection |
| QueryResult | Lightweight structured query result |
| PgReflect | Header-only reflection bridge for struct ↔ SQL mapping |
Features¶
- Reflect-aware SELECT / EXEC via ureflect
query_reflect<T>(sql, ...)→ returnsstd::vector<T>query_reflect_one<T>(sql, ...)→ returnsstd::optional<T>exec_reflect(sql, obj)→ uses struct or tuple fields as parameters
- Name-based mapping
Struct fields are matched to SQL columns by name (aliases likeAS usernamesupported).
Falls back to positional order if names are unavailable. - Array and optional support
std::optional<T>↔NULLstd::vector<T>,std::array<T,N>, C arrays ↔ PostgreSQL arrays (INT4[],TEXT[], …)
- No hidden layers — stays close to raw
libpq, but coroutine-safe and zero-overhead. - Async everywhere —
connect,query,commit,LISTEN/NOTIFY,COPY, all awaitable.
Example¶
struct User
{
int64_t id;
std::string username;
std::optional<std::string> password;
std::vector<int> roles;
std::vector<std::string> tags;
};
// SELECT with name-based mapping
auto users = co_await pool.query_reflect<User>(
"SELECT id, name AS username, password, roles, tags FROM users ORDER BY id LIMIT 100;"
);
// INSERT from aggregate
struct NewUser
{
std::string name;
std::optional<std::string> password;
std::vector<int> roles;
};
NewUser nu{"alice", std::nullopt, {1, 2}};
auto res = co_await pool.exec_reflect(
"INSERT INTO users(name, password, roles) VALUES ($1,$2,$3);",
nu
);
What it doesn’t do¶
- No ORM, no hidden state machines
- No query builders or migrations
- No automatic retries or reconnection loops
- No dependencies beyond
libpqanduvent
Philosophy: minimal abstractions, predictable control, and compile-time reflection instead of boilerplate.