Codegen org-entity meta-model

Table of Contents

A codegen entity model is a single literate org-mode file that describes one entity (e.g. party) and drives both the C++ and SQL code generation pipelines for that entity.

This document is the single source of truth for the file shape: every section, every required and optional property, every well-known block kind that templates may reference.

About this document   ignore

The :ignore: tag asks the codegen loader to skip this heading when reading an entity file. Sections without that tag may be consumed.

File-level conventions

Naming

Entity files live under projects/ores.codegen/models/<component>/ with the filename <entity>_entity.org. Example: party_entity.org under models/refdata.

Frontmatter

The first few lines of the file are org-mode frontmatter. They describe the document (this codegen model), not the modelled entity. Required:

Keyword Purpose
:ID: (drawer) UUID of this org file. Used for cross-document links.
#+title: Fully-qualified entity name, e.g. ores.refdata.party.
#+description: What this file is, not what the modelled thing is.
#+type: ores.codegen.entity
#+component: Component the entity belongs to (refdata, trading, …).
#+filetags: :model:entity:<component>:
#+entity_singular: Identifier form, singular (party).
#+entity_plural: Identifier form, plural (parties).
#+entity_title: Title-cased human form (Party).
#+brief: One-line summary of the modelled entity.

Prose body (between frontmatter and first heading)

A paragraph or two describing the modelled entity itself. This text is exposed to templates as the entity description. Keep the document-level #+description for the file and put the entity description here.

Sections

The body is divided into top-level sections. The loader recognises the section names below. Unrecognised section names (e.g. * About this file, typically tagged :ignore:) are skipped.

Flags

Cross-cutting properties consumed by both C++ and SQL codegen.

Property Required Notes
:schema: yes Postgres schema (almost always public).
:product: yes Top-level namespace (ores).
:component: yes Component name (refdata, trading, …).
:has_tenant_id: no Adds tenant_id column / field.
:has_workspace_id: no Adds workspace_id column / field.

Example:

* Flags
,:PROPERTIES:
,:schema: public
,:product: ores
,:component: refdata
,:has_tenant_id: true
,:END:

Primary key

Drawer holds the primary-key column description. Body holds prose documentation.

Property Required Notes
:column: yes SQL column name.
:type: yes SQL type (uuid, text, etc.).
:cpp_type: yes C++ type (boost::uuids::uuid, std::string, …).

Example:

* Primary key
,:PROPERTIES:
,:column:   id
,:type:     uuid
,:cpp_type: boost::uuids::uuid
,:END:

UUID uniquely identifying this party.

Surrogate key for the party record.

Natural keys

Container for natural-key sub-headings. Each ** sub-heading is one natural key, with the column name as the heading title.

Each natural-key sub-heading uses the same property structure as a column (see * Columns below), and may carry a generator babel block.

Columns

Container for column sub-headings. Each ** sub-heading is one column, with the column name as the heading title.

Per-column properties:

Property Required Notes
:type: yes SQL type.
:cpp_type: yes C++ type.
:nullable: no true for nullable columns (default false).

Each column's body is split into a description (first paragraph) and an optional detail (second paragraph and beyond).

Each column may carry one or more named babel blocks. The well-known names:

  • generator — C++ expression producing a synthetic value for tests.

Example:

** codename
,:PROPERTIES:
,:type:     text
,:cpp_type: std::string
,:nullable: false
,:END:

Globally unique human-readable codename (adjective_noun).

Used as the per-party pgmq queue prefix, pg_cron job names, and
operator tooling.

#+begin_src cpp :name generator
,std::string(faker::word::adjective()) + "_" + std::string(faker::word::noun())
#+end_src

SQL

SQL-specific knobs. The first sub-section is always ** Flags with a property drawer.

* SQL
** Flags
,:PROPERTIES:
,:tablename: ores_refdata_parties_tbl
,:END:

Future SQL sub-sections (custom triggers, indices) will live alongside the Flags sub-heading.

C++

C++-specific knobs. Sub-sections:

  • ** Flags — Repository / service flags that affect template output.
  • ** Repository — Naming-convention strings (entity_singular_short, entity_plural_short, etc.).
  • ** Domain includes — Named includes babel block; one #include directive per line.
  • ** Entity includes — Same, for the SQL-mapping entity header.
  • ** Conventions — Misc C++ properties (iterator_var, etc.).
  • ** Table display — Org table mapping column names to display headers.
  • ** Qt — Property drawer plus sub-tables (*** Detail fields, *** Columns (Qt model)).
  • ** Custom repository methods — Custom methods that the standard repository template cannot express. See "Block kinds" below.

Block kinds

A block kind is a slot in a template that an entity model can fill. Each kind has a stable UUID; templates reference the slot by that UUID; entity models attach blocks to the slot by listing the kind UUID in the block's :implements: property.

Two levels of identity are at play:

  • The kind UUID is well-known, lives here in the meta-model, and is referenced by templates.
  • An instance UUID (each individual block's :ID:) is per-entity, per block. Many entities may implement the same kind.

SQL: extra drop statements

Additional DROP statements to be inserted in the generated <component>_<entity_plural>_drop.sql just before drop table if exists.

Use this when the entity's companion _table.org model generates database objects (e.g. a validate function) that the entity template cannot know about. If no entity implements this kind, the marker collapses to nothing.

Template usage (mustache / sql_schema_domain_entity_drop.mustache):

drop function if exists {{product}}_{{component}}_{{entity_plural}}_insert_fn;
<<paste:5E47F108-1350-4540-B3C2-E83DD5379B2D>>
drop table if exists "{{product}}_{{component}}_{{entity_plural}}_tbl";

Entity attachment (** SQL > ** Extra drops sub-section):

** Extra drops

#+begin_src sql :name body :implements 5E47F108-1350-4540-B3C2-E83DD5379B2D
drop function if exists ores_refdata_validate_rounding_type_fn;
#+end_src

Multiple drop statements can appear in one block or in multiple blocks; codegen concatenates them in order with a blank line between blocks.

Repository: additional class-member declarations

Class-member declarations to be inserted into the generated <entity>_repository.hpp just before the closing brace of the <entity>_repository class.

Template usage (mustache):

,    void remove(context ctx, const std::string& id);
,    // <<paste:DCA78C69-E508-48D9-9972-A9B8094D91FB>>
,};

Entity attachment (per custom method):

*** 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

The :implements header argument on the babel block is the contract: codegen substitutes the <<paste:DCA78C69-...>> marker with the concatenation of every babel block in the current entity whose :implements header argument matches.

The babel block's :name is for Emacs / human authoring purposes (so authors can navigate by name); :implements is what codegen looks at.

Repository: additional out-of-class implementations

Out-of-class method definitions to be inserted into the generated <entity>_repository.cpp at the marker, typically just before the closing namespace brace.

Templates reference: <<paste:F2EB1914-5E94-42CA-9C77-46BDB364BF9E>>.

Entity attachment: any babel block with :implements F2EB1914-5E94-42CA-9C77-46BDB364BF9E contributes its body to the substitution.

Repository: additional .cpp includes

Additional #include directives that the implementations above need. Inserted into the generated <entity>_repository.cpp near the top of the include block.

Templates reference: <<paste:6141050C-0ED4-4680-B387-7DDDA3A69806>>.

Entity attachment: any babel block with :implements 6141050C-0ED4-4680-B387-7DDDA3A69806 contributes its body (verbatim, no syntax stripping). Multiple methods may each declare their own includes block; the codegen concatenates them. The C++ compiler de-duplicates #include directives at preprocessing time anyway.

Service header: extra includes

Extra #include directives injected into the generated <entity>_service.hpp immediately before the namespace opening brace.

Use this when the service needs headers for entity-specific types (e.g. <boost/uuid/uuid.hpp>, a junction repository header) that the generic service template does not emit.

Templates reference: <<paste:C5F35C7E-6EED-48D8-99F8-793145586882>>.

Entity attachment example:

** Custom service includes

#+begin_src cpp :name includes :implements C5F35C7E-6EED-48D8-99F8-793145586882
,#include <boost/uuid/uuid.hpp>
,#include "ores.refdata.core/repository/party_country_repository.hpp"
#+end_src

Service header: custom public methods

Additional public method declarations injected into the generated <entity>_service.hpp just before the private: label.

Use this for entity-specific query methods that are beyond the standard CRUD surface (e.g. party-scoped list/count operations on junction tables).

Templates reference: <<paste:2D4EA0F9-6AF6-45A5-A959-F672BF866C6A>>.

Entity attachment example:

** Custom service methods

#+begin_src cpp :name declaration :implements 2D4EA0F9-6AF6-45A5-A959-F672BF866C6A
,    std::vector<domain::country> list_countries_for_party(
,        const boost::uuids::uuid& party_id,
,        std::uint32_t offset, std::uint32_t limit);
,    std::uint32_t count_countries_for_party(const boost::uuids::uuid& party_id);
#+end_src

Service header: extra private members

Additional private member declarations injected into the generated <entity>_service.hpp after the standard repo_ member.

Use this when the service needs entity-specific members such as a junction repository that the generic template does not know about.

Templates reference: <<paste:047A96EF-1CC4-4661-B80B-C6C4532076AB>>.

Entity attachment example:

** Custom service members

#+begin_src cpp :name declaration :implements 047A96EF-1CC4-4661-B80B-C6C4532076AB
,    repository::party_country_repository junction_repo_;
#+end_src

Service .cpp: extra includes

Additional #include directives injected into the generated <entity>_service.cpp after the standard includes (<cstdint>, <stdexcept>).

Use this for headers that are needed only by the custom method implementations injected via D3E7A5F2 (e.g. <algorithm>, <unordered_set>, <boost/uuid/uuid_io.hpp>) — headers that are implementation details and should not appear in the service header.

Templates reference: <<paste:A7D3F1E2-8C5B-4A2F-B9E0-3D6C1A8F4B2E>>.

Service .cpp: constructor extra init

Additional member-initialiser-list entries injected into the generated <entity>_service.cpp constructor, appended after ctx_(std::move(ctx)).

Use this when extra private members (declared via 047A96EF) need initialisation with ctx_. Each block should start with , to extend the initialiser list correctly.

Templates reference: <<paste:4F8A2B1E-7C3D-4E9F-B6A0-1D5C8F2E7A4B>>.

Entity attachment example:

** Custom service constructor init

#+begin_src cpp :name ctor_init :implements 4F8A2B1E-7C3D-4E9F-B6A0-1D5C8F2E7A4B
,    , junction_repo_(ctx_)
#+end_src

Service .cpp: custom implementations

Out-of-class method definitions injected into the generated <entity>_service.cpp just before the closing namespace brace.

Use this for the implementations of methods declared via 2D4EA0F9 — the standard template only generates bodies for the built-in CRUD methods; entity-specific methods must supply their own bodies here.

Templates reference: <<paste:D3E7A5F2-8B4C-4D1E-9A6F-2E1C7B4D8A3F>>.

Entity attachment: any babel block with :implements D3E7A5F2-8B4C-4D1E-9A6F-2E1C7B4D8A3F contributes its body. Multiple blocks are concatenated with a blank line between them.

Generator header: custom declarations

Additional free-function declarations injected into the generated <entity>_generator.hpp after the standard synthetic generator declarations, before the closing namespace brace.

Use this for entity-specific generator functions not expressible through the standard generate_synthetic_<entity> template (e.g. fictional/ hardcoded generators for test entities with authoritative data sets).

Templates reference: <<paste:E32E358B-56EC-4642-911E-A2EA1D95E3B4>>.

Entity attachment example:

** Custom generator declarations

#+begin_src cpp :name declaration :implements E32E358B-56EC-4642-911E-A2EA1D95E3B4
,ORES_REFDATA_API_EXPORT std::vector<domain::country>
,generate_fictional_countries(std::size_t n,
,    utility::generation::generation_context& ctx);
#+end_src

Generator .cpp: custom implementations

Out-of-class function definitions injected into the generated <entity>_generator.cpp after the standard generate_synthetic_<plural> function, before the closing namespace brace.

Use this for the implementations of functions declared via E32E358B.

Templates reference: <<paste:C2F8A4E3-7D5B-4C2F-8A1E-3D9C6B2E5F1A>>.

Entity attachment: any babel block with :implements C2F8A4E3-7D5B-4C2F-8A1E-3D9C6B2E5F1A contributes its body.

Notes

Why two levels of UUID

The kind UUID is a concept (akin to an interface), declared once in the meta-model. The instance UUID identifies an implementation (akin to a class) — there will be many. Templates speak the language of concepts; entity models speak the language of implementations.

This also means we never have to coordinate UUID changes across templates and per-entity files: a template references a fixed kind UUID; entity files discover what they implement.

Template marker syntax

Templates use <<paste:UUID>> as the marker. Codegen is a post-rendering substitution pass: mustache renders first, then a regex pass replaces every <<paste:UUID>> with the concatenated body blocks of every implementing sub-heading in the current entity.

If no entity sub-heading implements a given kind, the marker is replaced with an empty string (and a single newline is collapsed).

Block-name conventions

Inside an implementing sub-heading the well-known babel block name is body. Future kinds may use additional names (declaration, implementation, etc.) — they will be documented alongside each kind.

Future work

  • Stage 1.5: tag-based heading dispatch. Replace title matching ("Flags", "Primary key", …) with tag matching (:section:flags:, etc.). Lets us rename headings without breaking codegen.
  • Migration of remaining ~100 entity models from JSON to org.
  • Compass scaffold for compass add entity-org.

Emacs 29.1 (Org mode 9.6.6)