Codegen entity meta-model — C++ repository
Table of Contents
This page is a segment of the Codegen org-entity meta-model hub, covering the ores.cpp.repository facet: the persistence layer between the plain C++ Domain struct and the database — the entity, mapper, and repository classes, plus list/count/save/delete/get CRUD.
1. Physical model mapping
| Facet | Description |
|---|---|
| ores.cpp.repository | Repository layer: entity, mapper, CRUD. |
2. Flags
** Flags
,:PROPERTIES:
,:subcomponent: api
,:END:
| Property | Required | Meaning |
|---|---|---|
:subcomponent: |
no | Sub-directory under the component the domain struct lives in (api is the default assumption; set when it diverges, e.g. a core-only helper type). Affects the #include paths the repository/service facets generate for this entity's domain header. |
3. Repository naming
Naming-convention strings the templates need but cannot always derive mechanically from the entity's singular/plural forms (compound words, irregular plurals):
| Property | Meaning |
|---|---|
:entity_singular_short: |
Short form used in generated variable names, e.g. currency. |
:entity_plural_short: |
Plural short form, e.g. currencies (not just entity_plural with an "s" — irregular plurals need this spelled out). |
:entity_singular_words: |
Human-readable singular, for doc-comments. |
:entity_plural_words: |
Human-readable plural, for doc-comments. |
** Repository
,:PROPERTIES:
,:entity_singular_short: currency
,:entity_plural_short: currencies
,:entity_singular_words: currency
,:entity_plural_words: currencies
,:END:
4. Tenant read scope
Every read the repository generates carries a tenant_id = ctx.tenant_id()
predicate, on top of the table's row-level security policy. That predicate is
what a read chooses to add, and :tenant_read_scope: states the choice:
| Value | Meaning |
|---|---|
tenant (default) |
The read carries the predicate. Use it for rows a caller may only see in its own tenant. |
shared |
The read carries no tenant predicate and the row-level security policy alone decides. Use it when the policy admits rows the predicate would hide: system-tenant-seeded reference data every tenant may read, or a service that holds the system tenant and works across tenants. |
Mutations (insert, update, delete) always stay tenant-scoped, whichever value is set.
** Repository
,:PROPERTIES:
,:tenant_read_scope: shared
,:entity_singular_short: catalog
,:END:
5. Entity includes
Same shape as C++ Domain's Domain includes, but for the
repository entity header (the struct a row reads into) instead of the
domain struct.
Generates, in book_entity.hpp:
#include "ores.database/query_builder/columns.hpp" #include "ores.database/repository/db_types.hpp" #include <boost/uuid/uuid.hpp> #include <chrono> #include <optional> #include <ostream> #include <string> namespace ores::refdata::repository { using db_timestamp = ores::database::repository::db_timestamp; struct book_entity { constexpr static const char* schema = "public"; constexpr static const char* tablename = "ores_refdata_books_tbl"; boost::uuids::uuid id; // ... fields from Keys and columns, each the type its column has ... db_timestamp valid_from = db_timestamp::max(); db_timestamp valid_to = db_timestamp::max(); static constexpr auto columns() { using e = book_entity; return query_builder::columns<e>() | query_builder::key("id", &e::id) | // ... a bind for each column ... } }; }
A column whose database type the layer has no codec for, such as jsonb or
inet, is stated with bind_text instead of bind. The statement asks the
server for it cast to text and the row reads the text, so the member keeps the
string the mapper converts.
6. Conventions
| Property | Meaning |
|---|---|
:iterator_var: |
Loop variable name used in generated range-based for loops over this entity's rows (defaults are usually fine; override for readability on an unusually-named entity). |
7. Table display
An org table mapping column names to their display headers, consumed by more than one downstream facet (UI list columns, CSV/XML export column headers) wherever a human-readable label is needed for a raw column name:
** Table display
| Column | Header |
|--------+--------|
| name | Name |
| party_id | Party |
8. Paste blocks
The ores.cpp.repository facet defines the following paste block
kinds, one per sub-heading below — see Paste blocks for the general
mechanism they assume.
8.1. Additional class-member declarations
Class-member declarations inserted just before the closing brace of
the generated <entity>_repository class, expecting a declaration
block.
Example of an inclusion of this paste block in a template:
void remove(context ctx, const std::string& id);
// <<paste:DCA78C69-E508-48D9-9972-A9B8094D91FB>>
};
To use this paste block on an entity, declare it under ** Paste
blocks > *** Additional class-member declarations, one ****
sub-heading per custom method. See the following example:
** Paste blocks *** Additional class-member declarations **** read_system_party ,:PROPERTIES: ,:ID: 9B7C5D2E-1A4F-4E7B-9D8A-6F3C2E1B5A4D ,:END: Prose describing what this method does and why it cannot be templated. #+begin_src cpp :name declaration :implements DCA78C69-E508-48D9-9972-A9B8094D91FB std::vector<domain::party> read_system_party(context ctx, const std::string& tenant_id); #+end_src
8.2. Additional out-of-class implementations
Out-of-class method definitions inserted into the generated
<entity>_repository.cpp, just before the closing namespace brace —
the .cpp counterpart of the declaration above, expecting a body
block. Multiple implementing blocks concatenate with a blank line
between them.
8.3. Additional .cpp includes
Additional #include directives the implementations above need,
inserted near the top of the generated <entity>_repository.cpp's
include block, expecting a body block. Multiple methods may each
declare their own includes block; the C++ compiler de-duplicates
#include directives at preprocessing time regardless, so no need
to worry about collisions between them.
8.4. After a single write
Code inserted at the end of the generated write(context, value, claim),
after the row is written. The value the write stored is in scope as t, and
the context is the caller's, so the block joins the caller's transaction and
commits with the generated write.
This is the point for a second table that is derived from the one just written and must not be able to disagree with it. Nothing else is in scope, so a block here needs an include of its own.
8.5. After a batch write
The same point at the end of the generated
write(context, values, claims), with the values written in scope as
batch. A block here usually extracts the rows it needs and calls the same
thing the single-write block calls.
8.6. Before a single insert
Code inserted before the row is written by the generated
insert(context, value), after the log line and before the write. The
value about to be written is in scope as v, and the context is the
caller's, so the block joins the caller's transaction.
This is the point for a check that must refuse the row rather than record it, so the row is never written when the check throws. Nothing else is in scope, so a block here needs an include of its own.
8.7. Before a batch insert
The same point before the rows are written by the generated
insert(context, values), with the values about to be written in scope as
v. A block here checks the whole batch in one call, so a batch that
breaches the check is refused before any of its rows are written.
9. See also
- Codegen org-entity meta-model — the hub.
- Keys and columns — the fields this repository entity mirrors.
- C++ Domain — the plain struct this layer persists.
- C++ Service — the layer that wraps this repository with business logic and NATS eventing.
- Variability — MASD's formal vocabulary for this page's Flags/Repository/Conventions configuration points.