Appearance
Database
The vix::db module is a small, explicit database layer built around:
- A tiny driver abstraction (
Connection,Statement) - A thread safe connection pool (
ConnectionPool) - RAII transactions (
Transaction) - A minimal, type erased value model (
DbValue) - Migrations (code and file based)
- Optional schema snapshot utilities (
vix::db::schema)
This is a guide, not a showcase: it explains the mental model and the core APIs you will use in real apps.
All examples assume you include the umbrella header:
cpp
#include <vix/db/db.hpp>Namespace used in examples:
cpp
using namespace vix::db;1) Build and feature flags
Vix DB can be built with different backends.
Common macros you will see in the codebase:
VIX_DB_HAS_MYSQLenables MySQL driver integrationVIX_DB_HAS_SQLITEenables SQLite driver integration
If a driver is disabled, its header is typically guarded with the macro (example: #if VIX_DB_HAS_SQLITE).
2) Core model
The public API is built around a few concepts.
Connection
A live database connection.
Key operations:
prepare(sql)returns a preparedStatementbegin(),commit(),rollback()manage transactionslastInsertId()returns the last generated id (semantics depend on engine)ping()checks if the connection is still usable (driver dependent)
Statement
A prepared SQL statement with positional parameters.
Key operations:
bind(idx, value)binds a parameterbindNull(idx)binds SQL NULLexec()runs a non returning statement (INSERT, UPDATE, DELETE)query()runs a SELECT and returns aResultSet
Vix binds values through a small type erased wrapper: DbValue.
ResultSet and ResultRow
Query results are forward only.
ResultSet::next()advances to the next rowResultSet::row()returns a reference to the current rowResultRow::getString(i),getInt64(i),getDouble(i)read columns by index- Helper methods
getStringOr,getInt64Or,getDoubleOrprovide defaults for NULL
Column indexes are zero based and match the SELECT order.
3) DbValue and binding
The binding model is intentionally simple.
DbValue supports:
nullptrfor SQL NULLboolstd::int64_tdoublestd::stringBlob(bytes)
Helpers:
null(),b(x),i64(x),f64(x),str(x),blob(bytes)
Statement::bind also provides convenience overloads for common C++ types:
bind(idx, int),bind(idx, std::int64_t),bind(idx, double),bind(idx, bool),bind(idx, std::string),bind(idx, const char*)
When in doubt for integers, bind std::int64_t.
4) Connection pool
ConnectionPool owns a factory function and reuses connections.
acquire()blocks if the pool is at max and no idle connection existsrelease(c)returns a connection to the idle queuewarmup()pre creates at leastPoolConfig::minconnections
A convenience RAII type exists:
PooledConnacquires on construction and releases on destruction
5) Database facade and config
Database is a small facade that owns a ConnectionPool.
The configuration is represented by:
Engineenum:MySQLorSQLiteDbConfigholding engine specific configMySQLConfigwith host, user, password, database, and pool sizingSQLiteConfigwith path and pool sizing
There is also a helper that maps your main Vix config:
cpp
DbConfig make_db_config_from_vix_config(const vix::config::Config& cfg);Use it when you want DB config to come from config.json and your vix::config::Config loader.
6) Minimal examples
6.1 Connect and ping
cpp
#include <vix/db/db.hpp>
#include <iostream>
int main()
{
using namespace vix::db;
DbConfig cfg;
cfg.engine = Engine::MySQL;
cfg.mysql.host = "tcp://127.0.0.1:3306";
cfg.mysql.user = "root";
cfg.mysql.password = "";
cfg.mysql.database = "vixdb";
Database db(cfg);
auto conn = db.pool().acquire();
if (!conn->ping())
{
std::cerr << "DB ping failed\n";
return 1;
}
std::cout << "DB connected successfully\n";
return 0;
}6.2 Transaction, create table, insert, query
This shows:
TransactionRAII- Prepared statements and binding
- Iterating results
cpp
#include <vix/db/db.hpp>
#include <cstdint>
#include <iostream>
#include <string>
int main()
{
using namespace vix::db;
DbConfig cfg;
cfg.engine = Engine::MySQL;
cfg.mysql.host = "tcp://127.0.0.1:3306";
cfg.mysql.user = "root";
cfg.mysql.password = "";
cfg.mysql.database = "vixdb";
cfg.mysql.pool.min = 1;
cfg.mysql.pool.max = 8;
Database db(cfg);
try
{
Transaction tx(db.pool());
tx.conn().prepare(
"CREATE TABLE IF NOT EXISTS users ("
" id BIGINT PRIMARY KEY AUTO_INCREMENT,"
" name VARCHAR(255) NOT NULL,"
" age INT NOT NULL"
");"
)->exec();
{
auto st = tx.conn().prepare("INSERT INTO users (name, age) VALUES (?, ?)");
st->bind(1, std::string("Alice"));
st->bind(2, static_cast<std::int64_t>(20));
st->exec();
}
{
auto st = tx.conn().prepare("SELECT id, name, age FROM users WHERE age >= ?");
st->bind(1, static_cast<std::int64_t>(18));
auto rs = st->query();
while (rs->next())
{
const auto& row = rs->row();
std::cout
<< row.getInt64(0) << " "
<< row.getString(1) << " "
<< row.getInt64(2) << "\n";
}
}
tx.commit();
std::cout << "Committed\n";
return 0;
}
catch (const std::exception& e)
{
std::cerr << "DB error: " << e.what() << "\n";
return 1;
}
}6.3 Prepared SELECT with parameter
cpp
#include <vix/db/db.hpp>
#include <cstdint>
#include <iostream>
int main()
{
using namespace vix::db;
DbConfig cfg;
cfg.engine = Engine::MySQL;
cfg.mysql.host = "tcp://127.0.0.1:3306";
cfg.mysql.user = "root";
cfg.mysql.password = "";
cfg.mysql.database = "vixdb";
Database db(cfg);
auto conn = db.pool().acquire();
auto st = conn->prepare("SELECT id, name FROM users WHERE age > ?");
st->bind(1, static_cast<std::int64_t>(18));
auto rs = st->query();
while (rs->next())
{
const auto& row = rs->row();
std::cout << row.getInt64(0) << " " << row.getString(1) << "\n";
}
return 0;
}7) Transactions
Transaction is designed to be exception safe:
- Begins a transaction in the constructor
- Rolls back in the destructor if still active
commit()marks it inactive so the destructor does nothingrollback()explicitly rolls back and marks inactive
Guideline:
- Use one transaction per unit of work
- Keep it short
- Prefer committing at the end
8) Migrations
Vix supports two styles.
Code migrations
You write a Migration class with id(), up(), down(). Then run it through MigrationsRunner.
File migrations
You store SQL files:
./migrations/<id>.up.sql./migrations/<id>.down.sql
Then run them via FileMigrationsRunner.
Example that shows both:
cpp
#include <vix/db/db.hpp>
#include <filesystem>
#include <iostream>
#include <string>
int main()
{
using namespace vix::db;
DbConfig cfg;
cfg.engine = Engine::MySQL;
cfg.mysql.host = "tcp://127.0.0.1:3306";
cfg.mysql.user = "root";
cfg.mysql.password = "";
cfg.mysql.database = "vixdb";
cfg.mysql.pool.min = 1;
cfg.mysql.pool.max = 4;
class CreateUsersTable final : public Migration
{
public:
std::string id() const override { return "2026-01-22-create-users"; }
void up(Connection& c) override
{
c.prepare(
"CREATE TABLE IF NOT EXISTS users ("
" id BIGINT PRIMARY KEY AUTO_INCREMENT,"
" name VARCHAR(255) NOT NULL,"
" age INT NOT NULL"
");"
)->exec();
}
void down(Connection& c) override
{
c.prepare("DROP TABLE IF EXISTS users;")->exec();
}
};
try
{
Database db(cfg);
{
Transaction tx(db.pool());
CreateUsersTable m1;
MigrationsRunner runner(tx.conn());
runner.add(&m1);
runner.runAll();
tx.commit();
std::cout << "[migrations] code ok\n";
}
{
Transaction tx(db.pool());
FileMigrationsRunner runner(tx.conn(), std::filesystem::path{"migrations"});
runner.setTable("schema_migrations"); // optional
runner.applyAll();
tx.commit();
std::cout << "[migrations] files ok\n";
}
return 0;
}
catch (const std::exception& e)
{
std::cerr << "ERROR: " << e.what() << "\n";
return 1;
}
}9) Drivers overview
SQLite driver
- Implements
Connectionon top ofsqlite3* ping()is a lightweight validity check- Lifetime is managed by the
SQLiteConnectiondestructor
MySQL driver
- Implements
Connectionon top of MySQL Connector/C++ ping()usesisValid()with exception protection- Transactions are implemented via autocommit toggling
Driver factories:
make_sqlite_factory(path)returns aConnectionFactorymake_mysql_factory(host, user, pass, db)returns aConnectionFactory
Those factories are what you feed to ConnectionPool.
10) Schema utilities
The vix::db::schema namespace provides a minimal schema model:
Schemacontains multipleTableTablecontainsColumnandIndexTypedescribes column type with optional size (example:VARCHAR(n))Dialectindicates SQL emission target (MySQL or SQLite)
JSON snapshot helpers:
to_json_string(schema, pretty)from_json_string_or_throw(json)
This layer is useful for:
- Persisting schema snapshots
- Diffing schemas
- Building migration tooling
11) Common errors
- Cannot connect: verify host, port, user, password, database, and driver enable flags
- Pool blocks forever: max connections reached and callers never release connections
- Bind mismatch: prefer
std::int64_tfor integer parameters - NULL handling: use
row.isNull(i)or thegetXOrhelpers