relx database connection
relx - A type-safe SQL library
relx result processing
relx query builder
relx::migrations - Database Schema Migration Library
This is the main include file for relx database connection functionality. It provides abstract classes and concrete implementations for connecting to databases.
Example usage:
struct Users {
static constexpr auto table_name = "users";
};
struct UserDTO {
int id;
std::string name;
std::string email;
};
.port = 5432,
.dbname = "mydb",
.user = "postgres",
.password = "postgres"
};
auto connect_result = conn.connect();
if (!connect_result) {
std::println("Connection error: {}", connect_result.error().message);
return 1;
}
Users u;
auto query = relx::select(u.id, u.name, u.email)
.from(u)
.where(u.age > 18);
auto result = conn.execute<UserDTO>(query);
if (!result) {
std::println("Query error: {}", result.error().message);
return 1;
}
for (const auto& user : *result) {
std::println("{}: {} <{}>", user.id, user.name, user.email);
}
auto raw_result = conn.execute(query);
if (raw_result) {
for (
const auto& [
id, name, email] : raw_result->
as<int,
std::string,
std::string>()) {
std::println("{}: {} <{}>", id, name, email);
}
}
conn.disconnect();
PostgreSQL implementation of the Connection interface.
Represents a column in a database table.
auto as(const Expr &expr, std::string alias)
Create an aliased column expression.
Basic parameters for a PostgreSQL connection.
This library provides automatic database migration generation by diffing table structures.
Core API (Most Users)
The main functions you'll need for 99% of use cases:
generate_migration(old_table, new_table, options) - Diff two table versions
generate_create_table_migration(table) - Create table migration
generate_drop_table_migration(table) - Drop table migration
Plus Migration (for .forward_sql()/.rollback_sql()) and MigrationOptions (for column mappings).
Advanced API (Power Users)
Available in relx::migrations::advanced:: namespace for:
- Building custom migrations
- Testing individual components
- Introspecting migration operations
- Extending the migration system
Example usage:
struct UsersV1 {
static constexpr auto table_name = "users";
};
struct UsersV2 {
static constexpr auto table_name = "users";
};
int main() {
UsersV1 old_users;
UsersV2 new_users;
auto forward_sqls = migration.forward_sql();
for (const auto& sql : forward_sqls) {
std::println("Forward: {}", sql);
}
auto rollback_sqls = migration.rollback_sql();
for (const auto& sql : rollback_sqls) {
std::println("Rollback: {}", sql);
}
if (!migration.empty()) {
std::println("Migration '{}' has {} operations", migration.name(), migration.size());
}
return 0;
}
Represents a UNIQUE constraint on a table.
MigrationResult< Migration > generate_migration(const OldTable &old_table, const NewTable &new_table, const MigrationOptions &options={})
Generate migration from old table to new table.
DEFAULT value for string literals.
Column and Constraint Renaming
You can specify mappings to handle column renames and avoid data loss:
struct EmployeesV1 {
static constexpr auto table_name = "employees";
relx::column<EmployeesV1,
"first_name", std::string> first_name;
relx::column<EmployeesV1,
"email_addr", std::string> email_addr;
};
struct EmployeesV2 {
static constexpr auto table_name = "employees";
relx::column<EmployeesV2,
"given_name", std::string> given_name;
};
EmployeesV1 old_table;
EmployeesV2 new_table;
{"first_name", "given_name"},
{"email_addr", "email"}
};
auto safe_sqls = migration_safe.forward_sql();
auto safe_rollback = migration_safe.rollback_sql();
Options for controlling migration generation.
std::unordered_map< std::string, std::string > column_mappings
Map of old column name to new column name for renames.
Column Rename + Type Change
You can also handle cases where a column is both renamed and has its type changed:
struct ProductsV1 {
static constexpr auto table_name = "products";
};
struct ProductsV2 {
static constexpr auto table_name = "products";
relx::column<ProductsV2,
"price_dollars", std::string> price_dollars;
};
ProductsV1 old_products;
ProductsV2 new_products;
{"price_cents", {"CAST(price_cents / 100.0 AS TEXT)", "CAST(REPLACE(price_dollars, '$', '')
AS DECIMAL) * 100"}}
};
auto forward_sqls = migration.forward_sql();
auto rollback_sqls = migration.rollback_sql();
std::unordered_map< std::string, std::pair< std::string, std::string > > column_transformations
Bidirectional SQL transformations for complex column changes Key: old column name,...
You can also generate migrations for creating or dropping entire tables:
struct NewTable {
static constexpr auto table_name = "new_table";
};
NewTable new_table;
auto create_sqls = create_migration.forward_sql();
auto create_rollback_sqls = create_migration.rollback_sql();
auto drop_sqls = drop_migration.forward_sql();
auto drop_rollback_sqls = drop_migration.rollback_sql();
MigrationResult< Migration > generate_create_table_migration(const Table &table)
Generate migration to create a new table.
MigrationResult< Migration > generate_drop_table_migration(const Table &table)
Generate migration to drop a table.
This is the main include file for relx query building functionality. It provides a fluent interface for constructing type-safe SQL queries.
Example usage:
struct Users {
static constexpr auto table_name = "users";
};
Users u;
auto query1 = relx::select(u.id, u.name, u.email)
.from(u)
.where(u.age > 18);
auto query2 = relx::select(u.id, u.name, u.email)
.from(u)
.where(relx::to_expr(u.age) > relx::val(18));
auto query3 = relx::select(u.id, u.name, u.email)
.from(u)
.where(u.age > 18_sql);
std::string sql = query1.to_sql();
(users.age > ?) auto params = query1.bind_params();
struct Posts {
static constexpr auto table_name = "posts";
};
Posts p;
auto join_query = relx::select(u.name, p.title)
.from(u)
.join(p, relx::on(u.id == p.user_id))
.where(u.age > 21)
.order_by(relx::desc(p.title));
auto agg_query = relx::select_expr(
relx::count_all().
as(
"user_count"),
relx::avg(u.age).as("average_age")
)
.from(u)
.where(u.age > 21);
auto update_query = relx::update(u)
.set(
relx::set(u.name, "John Smith"),
relx::set(u.email, "john.smith@example.com")
)
.where(u.id == 1);
auto delete_query = relx::delete_from(u)
.where(u.age < 18);
auto insert_query = relx::insert_into(u)
.values(
relx::set(u.name, "Alice"),
relx::set(u.email, "alice@example.com"),
relx::set(u.age, 25)
);
Represents a foreign key constraint on a table.
This is the main include file for relx result processing functionality. It provides a way to parse and process query results in a type-safe manner.
Example usage:
struct Users {
static constexpr auto table_name = "users";
};
auto connect_result = conn.connect();
if (!connect_result) {
std::println("Connection error: {}", connect_result.error().message);
return 1;
}
struct UserDTO {
int id;
std::string name;
std::string email;
};
Users u;
auto query = relx::select(u.id, u.name, u.email)
.from(u)
.where(u.age > 18);
auto dto_result = conn.execute<UserDTO>(query);
if (dto_result) {
for (const auto& user : *dto_result) {
std::println("{}: {} <{}>", user.id, user.name, user.email);
}
}
auto result = conn.execute(query);
if (!result) {
std::println("Query error: {}", result.error().message);
return 1;
}
for (const auto& row : *result) {
auto id = row.get<int>(0);
auto name = row.get<std::string>(1);
auto email = row.get<std::string>(2);
if (id && name && email) {
std::println("{}: {} <{}>", *id, *name, *email);
}
}
for (const auto& row : *result) {
auto id = row.get<int>("id");
auto name = row.get<std::string>("name");
auto email = row.get<std::string>("email");
if (id && name && email) {
std::println("{}: {} <{}>", *id, *name, *email);
}
}
for (const auto& row : *result) {
auto id = row.get<int>(u.id);
auto name = row.get<std::string>(u.name);
auto email = row.get<std::string>(u.email);
if (id && name && email) {
std::println("{}: {} <{}>", *id, *name, *email);
}
}
for (
const auto& [
id, name, email] : result->
as<int,
std::string,
std::string>()) {
std::println("{}: {} <{}>", id, name, email);
}
struct UserData {
int id;
std::string name;
std::string email;
};
auto users = result->transform<UserData>([](const auto& row) ->
relx::ResultProcessingResult<UserData> { auto id = row.get<int>("id"); auto name =
row.get<std::string>("name"); auto email = row.get<std::string>("email");
if (!id || !name || !email) {
}
return UserData{*id, *name, *email};
});
auto optional_query = relx::select(u.id, u.name, u.email)
.from(u);
auto optional_result = conn.execute(optional_query);
if (optional_result) {
for (const auto& row : *optional_result) {
auto email = row.get<std::optional<std::string>>("email");
if (email && *email) {
std::println("Email: {}", **email);
} else if (email) {
std::println("Email is NULL");
}
}
}
Error type for result processing operations.
This file includes all the necessary headers to define database schemas.
Example usage:
struct Users {
static constexpr auto table_name = "users";
true>> created_at;
};
struct Posts {
static constexpr auto table_name = "posts";
};
Users users;
Posts posts;
Generate CREATE TABLE SQL statement for a table struct.
Represents an index on a table.
Check constraint that accepts a condition string at compile time.
DEFAULT value for non-string values.
NULL default for optional types.