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 doc-add-er-diagram skill drives the authoring; this doc captures how the diagram looks. The schema lives in ores.sql. Return to Knowledge.
File location
| Item | Value |
|---|---|
| Diagram | projects/ores.sql/modeling/ores_schema.puml |
| Rendered | (committed) |
| CMakeLists | projects/ores.sql/modeling/CMakeLists.txt |
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 | projects/ores.codegen/src/plantuml_er_generate.py (pystache) |
projects/ores.sql/modeling/ores_schema.puml |
| Diagram | the .puml |
PlantUML (system plantuml, or plantuml.jar) |
![]() |
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 and .png are committed because reviewers and readers
benefit from seeing schema-shape diffs in PRs and viewing the diagram
without rebuilding.
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
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
}
Field notation
*prefix marksNOT NULLfields.<<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.
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 {
...
}
}
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"
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
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.
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.
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. |
See also
- doc-add-er-diagram skill — drives the authoring.
- PlantUML class diagram conventions — sibling, for components.
- How do I generate PlantUML diagrams? — runs the build.
- ores.sql — the schema this diagram documents.
(committed)