PlantUML ER diagram conventions

Table of Contents

ER diagrams document the live SQL schema — every table, its columns, and the foreign-key relationships between them. The compass-doc-add-er-diagram skill drives the authoring; this doc captures how the diagram looks. The schema lives in ores.sql. Return to Knowledge.

1. File location

The schema outgrew one picture, so the diagram is one file per group and an index over them. A group is a package: its tables and the keys it holds inside itself. A key that leaves the group is named in the group's note, with the group it belongs to, because its other end is drawn in another file.

Item Value
Index projects/ores.sql/modeling/ores_schema.puml — every group, its file, and its tables
Group projects/ores.sql/modeling/ores_schema.<group>.puml — one per package, committed
Drawn <code>projects/ores.sql/modeling/ores_schema.svg</code> and one per group, ores_schema.<group>.svg — committed
CMakeLists projects/ores.sql/modeling/CMakeLists.txt

The .puml files hold what the diagram says and the SVG images are what a reader opens, so both are committed. The drawing is SVG because the schema has no raster budget left: the single 463-table diagram overflowed a 32-bit pixel count before it was split.

Below is the index; every group's image is on the ores.sql component page.

ores_schema.svg

2. Groups

The groups are the model's packages. A package holds its tables and the keys that stay inside it; a key that leaves is named in the note beside the group it reaches. The model's packages list is what names them, so a group cannot drift from the schema.

3. Generation pipeline

The ER diagram is generated, not hand-written. It's the canonical example in the project of the automated codegen route (see MASD for the routes architecture): codegen parses already-generated artefacts, derives a model from them, and renders new artefacts from that model.

The pipeline has three stages, all driven by projects/ores.codegen/plantuml_er_generate.sh:

Stage Reads Tool Writes
Parse projects/ores.sql/create/**/*.sql projects/ores.codegen/src/plantuml_er_parse_sql.py build/output/codegen/plantuml_er_model.json
Render the intermediate JSON + a mustache template per group, and one for the index projects/ores.codegen/src/plantuml_er_generate.py (pystache) projects/ores.sql/modeling/ores_schema.<group>.puml, and the index at ores_schema.puml
Draw the .puml files PlantUML (system plantuml, or plantuml.jar) projects/ores.sql/modeling/ores_schema*.svg

The intermediate model file is not committed and not literate. It lives under build/output/ (gitignored) because it is regenerated on every run and carries no information a human is expected to read or edit. The format is JSON for the same reason: a generated artefact doesn't benefit from org-mode literate prose, drawers, or block-paste mechanisms — those are reserved for the authored models humans maintain (under each component's modeling/ directory).

The .puml files are committed because reviewers and readers benefit from seeing schema-shape diffs in PRs. The images are committed because the documentation site renders them: an image link under proj: exports as an image tag pointing at the file in the repository, so an image that is not in the tree is a broken page.

4. Skeleton

' GPL header
@startuml

title ORES Database Schema
hide circle
skinparam linetype ortho

' <<< BODY GOES HERE >>>

' Local Variables:
' compile-command: "java -Djava.awt.headless=true -DPLANTUML_SECURITY_PROFILE=UNSECURE -DPLANTUML_LIMIT_SIZE=65535 -jar /usr/share/plantuml/plantuml.jar ores_schema.puml"
' End:
@enduml

5. Entity styling

Use the entity keyword with the appropriate stereotype:

Stereotype Colour When
<<temporal>> #C6F0D8 Tables with valid_from / valid_to columns.
<<junction>> #ECECEC Many-to-many association tables.
(none) #F7E5FF Current-state tables without temporal support.

Example:

entity accounts <<temporal>> #C6F0D8 {
    * id : uuid <<PK>>
    --
    * username : text <<unique>>
    * email : text <<unique>>
    * valid_from : timestamptz
    * valid_to : timestamptz
}

6. Field notation

  • * prefix marks NOT NULL fields.
  • <<PK>> for primary keys, <<FK>> for foreign keys, <<unique>> for unique constraints.
  • A -- separator divides the primary key block from the rest of the columns.

7. Domain packages

Group related entities into packages by domain:

Domain Colour
IAM (Identity & Access) #E8F4FD
RBAC (Role-Based Access Control) #FFF3E0
Assets (Images & Currencies) #F3E5F5
Variability (Feature Flags) #E8F5E9

A package looks like:

package "IAM (Identity & Access Management)" #E8F4FD {
    entity accounts <<temporal>> #C6F0D8 {
        ...
    }
}

8. Relationships

Standard ER cardinality:

Notation Meaning Example
\vert\vert--o{ One-to-many accounts \vert\vert--o{ sessions
\vert\vert--o\vert One-to-one accounts \vert\vert--o\vert login_info
Junction table Many-to-many Use a <<junction>> entity

Always label the arrow with descriptive text:

accounts ||--o{ sessions : "creates"
roles ||--o{ role_permissions : "has"

9. Notes

Add a note next to any entity that benefits from explanation:

note right of accounts
User accounts with authentication credentials.
Supports optimistic locking via the version field.
end note

10. Temporal conventions

ORE Studio uses bitemporal data management. Most tables have:

  • valid_from / valid_to — validity period (temporal support).
  • version — optimistic locking.
  • modified_by — audit trail.

Junction tables typically carry:

  • assigned_by — who created the association.
  • assigned_at — when the association was created.

11. Schema source

SQL schema lives at projects/ores.sql/create/. Two file kinds:

  • *_create.sql — table definitions with triggers.
  • *_notify_trigger.sql — PostgreSQL NOTIFY triggers for change events.

Read these to confirm:

  • Column types, constraints, primary key shape (incl. composite).
  • Indexes (incl. partial indexes for "current" records).
  • Temporal trigger presence.

12. Evaluation checklist

Check How
All tables represented Compare with projects/ores.sql/create/*_create.sql.
FK arrows correct Verify against the SQL REFERENCES clauses.
Proper stereotypes Temporal tables <<temporal>>, junctions <<junction>>.
Domain packages Related entities grouped logically.
Syntax valid cmake --build --target generate_ores_schema_diagram.

13. See also

Emacs 29.3 (Org mode 9.6.15)