|
ORM C++
|
The model API now treats metadata as part of the C++ type system. This is a breaking change: runtime string, string_view, vector, and map configuration objects are replaced by structural strings, member pointers, and a closed schema.
| Earlier declaration | Static declaration |
|---|---|
| std::string_view table_name = "users" | orm::reflection::FixedString table_name{"users"} |
| std::map columns_names{{"displayName", "display_name"}} | orm::columnNames(orm::columnName<&User::displayName, "display_name">()) |
| std::vector id_columns{"tenant", "id"} | orm::primaryKey<&User::tenant, &User::id>() |
| empty id_columns vector | orm::primaryKey<>() |
| std::vector auto_increment_columns{"id"} | orm::autoIncrement<&User::id>() |
| oneToMany("books") | oneToMany<&Author::books>() |
| manyToMany("roles").through("user_roles") | manyToMany<&User::roles>().through<"user_roles">() |
| inverse mappedBy("roles") | mappedBy<&User::roles>() |
| orm::Database database | orm::Database<orm::Schema<Models...>> database |
The static member names (table_name, columns_names, id_columns, auto_increment_columns, and relations) are retained. Their value types are what changed.
A model must be an aggregate with public data fields. The local C++20 reflection layer supports empty aggregates and aggregates with up to 128 fields. Non-aggregates, unions, raw C-array fields, and bit-fields produce compile-time diagnostics. Inherited aggregates are outside the supported contract: common forms cannot be decomposed and fail at compile time, while a small subset can be accepted by the C++ structured-binding rules and must not be relied on. Use composition instead. Use std::array<T, N> instead of a raw array field.
No reflect-cpp headers or library target are required by consumers. The reflection mechanics used by orm-cxx are provided by its public headers.
orm::reflection::typeName<T>(), memberName<&T::field>(), and valueName<Value>() return exact-size FixedString values during constant evaluation. valueName supports enumerators and symbols or addresses that are valid non-type template parameters. C++20 does not expose the source spelling of an ordinary local variable; recovering that spelling would require a macro and is intentionally outside this API.
Before:
After:
Put a mapping after the member declarations it references. A mistyped field can no longer silently survive until model construction: an invalid member pointer, duplicate member, empty name, incompatible primary key, or invalid auto-increment definition produces a compile-time diagnostic.
An int id field remains the default primary key when id_columns is absent. Use orm::primaryKey<>() when a model is intentionally keyless.
The collection field is always identified by a member pointer. Builder strings are non-type template parameters:
For one-to-many mappings, prefer a target member pointer when the target type is complete:
The normal Author/Book declaration order forms a C++ completeness cycle. For that case only, use the structural-name fallback:
Runtime operations have not yet moved to member pointers. Calls such as query.include("roles"), exists("roles"), and database.link(user, "roles", role) still take reflected C++ field names.
List every model the database may operate on, including all to-one and collection targets:
The schema validates unique model types and table names, relation targets, inverse mappings, primary keys, logical and generated foreign-key column names, junction tables, and junction column counts. Calling a model operation with a type outside AppSchema fails to compile.
Typed code can inspect modelDescriptor<AppSchema, T>() during constant evaluation. Runtime backend extensions receive modelView<AppSchema, T>(), a trivially-copyable two-word handle containing the schema address and model index. The handle exposes immutable descriptor data through operator-> and resolves relation targets by schema index, so cyclic schemas do not allocate or recursively copy metadata:
A backend connection probe that performs no model operations can use an empty schema:
This migration does not rename the current query DSL. col("displayName") still uses the reflected C++ field name and is translated through columns_names; it does not use the physical display_name spelling. One-level paths such as col("profile.city") and collection names passed to include, any, exists, and none remain runtime strings.
Compile-time query-field and relation-path validation is the next focused API step. Until then, mapping and schema errors are compile-time failures while query-path and backend-capability errors are reported when a query is validated or rendered.
Static metadata does not migrate an existing database. Continue to apply SQL schema changes explicitly and test migrations against a copy of production data.