Getting Started

Reflect requires a compiler with C++26 static reflection support and the -freflection compiler flag.

Build

cmake -S . -B build -DREFLECT_BUILD_TESTS=ON -DREFLECT_BUILD_EXAMPLES=ON
cmake --build build
ctest --test-dir build --output-on-failure

Tests and examples are off by default so Reflect can be consumed by another CMake project without building development targets.

Consume With FetchContent

cmake_minimum_required(VERSION 3.30)

project(my_app LANGUAGES CXX)

include(FetchContent)

FetchContent_Declare(
    reflect
    GIT_REPOSITORY https://github.com/swordfatih/reflect.git
    GIT_TAG main
)

set(REFLECT_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(REFLECT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(reflect)

add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE reflect::reflect)

Reflect requires C++26 static reflection support. The reflect::reflect target adds the required -freflection compile option to consumers.

Connect

reflect::client sqlite{"sqlite://:memory:"};
reflect::client file_db{"sqlite://app.db"};
reflect::client postgres{"postgresql://user:pass@localhost/app"};

Supported URI prefixes are:

  • sqlite://
  • postgres://
  • postgresql://

SQLite accepts :memory: and file paths after the prefix. PostgreSQL forwards the connection string to the PostgreSQL backend.

Create Or Sync A Table

db.migrate<User>();

migrate<T>() creates the table if missing, adds missing columns, creates indexes, and then validates the schema.

Validate On Startup

db.require_schema<User>();
db.require_schema<Post>();

Use validation as a startup guard before the application starts accepting writes.

Query

auto users = db.find_many<User>(
    reflect::query<User>(reflect::where(&User::email).contains("@example.test"))
        .order_by(&User::name)
        .take(50)
);

Transactions

db.transaction([](reflect::client& tx) {
    tx.insert<User>({.email = "ada@example.test", .name = "Ada"});
    tx.insert<User>({.email = "grace@example.test", .name = "Grace"});
});

Nested transactions use savepoints.

Inspect A Table

auto table = db.inspect<User>();

if(table.exists())
{
    for(const auto& column: table.columns)
    {
        // column.name, column.sql_type, column.nullable, column.primary_key
    }
}

Use inspect_table("table_name") when you do not have a model type.

Direct SQL

db.execute(reflect::statement{
    .sql = "CREATE INDEX IF NOT EXISTS \"idx_users_email\" ON \"users\" (\"email\")",
});

auto rows = db.query(reflect::statement{
    .sql = "SELECT \"email\" FROM \"users\" WHERE \"id\" = ?",
    .binds = {std::int64_t{1}},
});

Use direct SQL for backend-specific features, views, custom indexes, and manual migrations.


This site uses Just the Docs, a documentation theme for Jekyll.