Codegen entity meta-model — SQL

Table of Contents

This page is a segment of the Codegen org-entity meta-model hub, covering the * SQL section and the ores.sql.schema facet it feeds: bi-temporal table create/drop, insert triggers, and notify triggers. The columns it operates on are declared once, shared with C++, on the Keys and columns page — this page is only the SQL-specific fields layered on top.

This page splits its fields across two diagrams, per Applied MASD § "The entity model file as a unified literate document":

The :has_parent_id: hierarchy block is the exception. Its three properties are authored at the entity root, not in this section, so Hierarchy below covers them here while Keys and columns models them with the other structural features.

1. Physical model mapping

Facet Description
ores.sql.schema Bi-temporal schema: table create/drop, insert triggers, notify triggers.

2. Flags

The section always opens with a ** Flags drawer:

* SQL
** Flags
,:PROPERTIES:
,:tablename: ores_refdata_books_tbl
,:END:
Property Required Meaning
:tablename: yes The generated table's name. Convention: <product>_<component>_<entity_plural>_tbl.

Structural view — the SQL segment's fields, per the split above, plus its one paste block kind (extra drop statements, below), drawn as its own class: a block kind's ID is the well-known kind UUID, distinct from any entity's per-instance implementing block:

entity_meta_model_sql_structural.png

(Source: entity_meta_model_sql_structural.puml, rendered with plantuml.)

The :tablename: flag above (ores_refdata_books_tbl) drives the generated create table statement's identifier directly:

create table if not exists "ores_refdata_books_tbl" (
    "id" uuid not null,
    "tenant_id" uuid not null,
    "version" integer not null,
    -- ... columns from Keys and columns ...
);

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

3. Hierarchy: has_parent_id

Some entities form a tree via a self-referencing soft foreign key. A book belongs to a portfolio, and a counterparty can have a parent counterparty. Setting :has_parent_id: true makes the ores.sql.schema facet generate a recursive-CTE function that walks that tree, so consumers don't each hand-write their own recursive query. The C++ facets gate their matching members on the same feature: get_hierarchy on the repository and the service, and the protocol and NATS-handler entries that reach them.

All three properties sit in the entity's root * Flags drawer, beside :has_tenant_id: and :has_workspace_id:. :has_parent_id: is a structural feature, not one of this section's ores.sql.schema knobs; its bundle is Keys and columns.

Property Required Meaning
:has_parent_id: no Turns the hierarchy block on.
:parent_id_column: if set The self-referencing FK column name, e.g. parent_party_id.
:hierarchy_name_field: if set An existing column used as the display label in the tree.

The first property turns the block on and the other two name the columns the generated function reads. Set the first without the others and the function names empty columns, so author all three together.

counterparty is one such self-referencing entity. Its model shows where each property lives. The feature and the two column names are in the root drawer; the table name is in this section:

* Flags
,:PROPERTIES:
,:schema:               public
,:product:              ores
,:component:            refdata
,:profile:              self-referencing-hierarchy
,:parent_id_column:     parent_counterparty_id
,:hierarchy_name_field: full_name
,:END:

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

The bound Self-referencing hierarchy profile assigns :has_parent_id: true, so the model does not state it. A model with no such profile writes the line itself, in that same root drawer.

Generates a <product>_<component>_<entity_plural>_hierarchy_fn(p_tenant_id, p_root_id, p_from_root) recursive-CTE function returning flat (id, parent_id, name) rows — the subtree rooted at p_root_id when p_from_root is false, or (resolving up to the real root first) the whole tenant tree when true.

The generated function only produces flat rows; it does not build a tree. Turning those rows into an actual tree is generic work that does not vary from one hierarchical entity to the next, so it is not generated at all — every consumer instead calls the same hand-written ores::utility::domain::hierarchy.hpp, whose build_tree() turns a vector of flat hierarchy_flat_row into a tree of hierarchy_node.

:has_parent_id: does not generate everything a hierarchical entity needs, though. The self-referencing FK column itself (parent_<entity>_id) and its insert-trigger validation are still hand-written today. The hierarchy function does not depend on that column being generated; it composes independently either way.

4. Projection configuration (variability)

Every property below is, in MASD Variability terms, a feature (ores.sql.schema bundle) — authored on the model's sql section and read through in codegen/core.py=/=org_loader.py, never computed. None of them changes the entity's logical shape, only the generated DDL, triggers, and rules. Full classification and cross-facet index: ORE Studio Variability Model § "Bundle: ores.sql.schema" — this section is the narrative home for these knobs; that page indexes them.

The bundle as a feature model:

entity_meta_model_sql.svg

Feature Type Default Effect
:current_state: bool false Table holds one row per key and no history at all: no valid_from=/=valid_to, no GIST exclusion, no temporal checks, no version or audit tail, and the model's own primary key. Because it has no temporal axis it has no history surface either: no history endpoint, no history request/response messages and no history subscription. A current-state entity still validates its tenant_id on insert, and keeps any ** Indexes it declares (without a valid_to predicate). Structural consequence: removes the temporal axis.
:system_scope: bool false Table is system-tenant-scoped: tenant_id defaults to the system tenant and the insert trigger forces it. Structural consequence: changes the tenant_id column default.
:nullable_tenant_id: bool false tenant_id is nullable (system records carry NULL); selects the nullable-tenant validation and index variants. Structural consequence: changes tenant_id nullability.
:extra_checks: collection<string> ∅ Additional CHECK constraints to emit on the table.
:extra_delete_sets: collection<string> ∅ Extra SET clauses in the soft-delete rule.
:fk_copy_validations: collection<struct> ∅ FK validations that also copy/denormalise fields from the referenced row.
:text_code_validations: collection<struct> ∅ FK validations against a text lookup column.
:party_id_from_book_id: struct absent Derive and validate party_id=/=portfolio_id from book_id in the trigger.
:party_id_from_session: bool false Set party_id from the session GUC app.current_party_id.
:rls_tenant_isolation: bool false Emit enable row level security + a permissive tenant-isolation policy (T1) as a trailer of the create file, and its drop in the drop file.
:rls_party_isolation: bool false Also emit a party_id-scoped as restrictive policy (T3) over ores_iam_visible_party_ids_fn membership; requires :rls_tenant_isolation:.
:rls_system_tenant_visible: bool false Widen the tenant-isolation policy body to own-tenant or system-tenant (T2); requires :rls_tenant_isolation:.

Current-state is a distinct axis from :no_audit_columns:. A :no_audit_columns: entity (a TimescaleDB hypertable) drops only the version column and the audit tail; it keeps valid_from=/=valid_to as its transaction-time window, and its reads still filter on valid_to. A :current_state: entity drops the whole temporal axis: the SQL create has no valid_from=/=valid_to, no GIST exclusion and no temporal check, and its primary key is the model's own key (for login_info, account_id), not (tenant_id, key, valid_from, valid_to). It has no history surface: the service declares no get_<entity>_history, the C++ and TypeScript protocol headers state no history request/response pair and no history subject, the NATS registrar subscribes to no history subject, and the shell-command and presentation history facets omit the history verb and field mapper. The C++ domain type then has no recorded_at and the repository upserts (insert_or_replace) and hard-deletes instead of closing a validity window, so a caller must not expect version rows from read_all or a soft delete from remove. The insert trigger still validates tenant_id, and the hand-written table's indexes and constraints carry over through the ** Indexes and ** Checks sections (plus the per-column :skip_uuid_check: / :skip_empty_check: and per-FK :skip_check: suppressions), which is how the iam_login_info table keeps login_info_locked_idx and avoids gaining a nil-UUID check it never had. The feature is authored in the * SQL ** Flags drawer, beside the other SQL projection knobs above.

Tenant-scoping is a policy, not three booleans. :system_scope: and :nullable_tenant_id: are mutually exclusive with the default (standard-tenant) mode; together they form a single enumerated feature — tenant-scoping policy ∈ {standard, system, nullable}. If they migrate anywhere, it is into the logical model as a tenant-scoping attribute (they lean structural despite being authored as knobs).

Row-level security emission is an explicit intent flag, not a consequence of having a tenant column. The :rls_*: features above emit enable row level security and the matching policy blocks inline at the end of the generated per-entity create file, so a regenerated table owns its whole SQL lifecycle. They default off and no profile fixes them: many tenant-column tables deliberately skip RLS (system-owned registries, currently silenced by RLS_001 ignore entries in validation_ignore.txt), and a party_id column is not party isolation scope on its own (e.g. market_series). The emitted blocks are verbatim copies of the dominant hand-written shapes from the components' *_rls_policies_create.sql files (T1 tenant-only, T2 own-or-system-tenant, T3 restrictive party membership). Because create policy resolves ores_iam_current_tenant_id_fn at creation time, inline emission is only legal for components created after the iam section of create/create.sql; the pre-iam components keep their RLS-layer files until that ordering question is resolved.

SECURITY DEFINER on insert/validate triggers is not a feature — checked against library/templates/sql_schema_domain_entity_create.mustache, it is emitted unconditionally today, matching the entity commissioning process reference's mandatory rule. It is mentioned here only so nobody goes looking for a :security_definer: knob that no longer exists.

5. Paste blocks

The SQL segment defines the following paste block kinds, one per sub-heading below — see Paste blocks for the general mechanism they assume.

5.1. Extra drop statements

This paste block defines additional DROP statements inserted into 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 itself has no way to know about. If no entity implements this kind, the marker collapses to nothing.

Example of an inclusion of this paste block in a template (sql_schema_domain_entity_drop):

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";

To use this paste block on an entity, declare it under ** Paste blocks > *** Extra drop statements, with the body in a block named body. See the following example:

** Paste blocks
*** Extra drop statements

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

An entity is not limited to a single drop statement, nor to a single block: it may write several statements inside one body block, or spread them across several *** Extra drop statements sub-headings. Either way, codegen collects them all, in the order they appear in the model, and concatenates them with a blank line between each.

6. See also

  • Codegen org-entity meta-model — the hub.
  • Keys and columns — the column declarations this section's DDL is built from.
  • Paste blocks — the shared paste-block mechanism.
  • Variability — MASD's formal vocabulary for the ores.sql.schema features above. :tablename: (§ "Flags") is structural, not variability. :has_parent_id: (§ "Hierarchy") is variability, but its bundle is structural.
  • ORE Studio Variability Model — the ores.sql.schema bundle's classification and cross-facet index; this page is the narrative home for those knobs.

Emacs 29.3 (Org mode 9.6.15)