Skip to content

Entity–relationship diagram

Example definition erd.dis: Tables with typed columns and relationships in crow's-foot notation.

It is explained in 17.3 Entity–relationship diagram — compartments from children, crow's-foot markers.

What it demonstrates

From 17.3 Entity–relationship diagram — compartments from children, crow's-foot markers:

This example shows notation driven by the model. Columns are child elements listed in a compartment, with conditional item styles and icons. Relationship markers are computed from the cardinality attributes (erZeroOrMany, erOne, …). The line is dashed for non-identifying relationships. Cardinality labels sit at both ends and only appear when zoomed in. Tables align to a 20 px grid while bendpoints use a 10 px grid. Ids are natural (derived from table and column names), persistence is YAML, and a plugin provides SQL export.

Views generated from the file

These views are drawn by this site from erd.dis itself: its metamodel, and the layers it fills. They are not the notation the definition draws; that appears in an IDE that runs it.

Metamodel of the Entity–relationship diagram example. Class diagram of erd.dis: 2 node types (Table, Column) and 1 relation types (Relationship), with their typed attributes, inheritance, containment and relation ends.
Generated from erd.dis at 4e69c28
Diagram as text (Mermaid)
classDiagram
  accTitle: Metamodel of the Entity–relationship diagram example
  accDescr: Class diagram of erd.dis: 2 node types (Table, Column) and 1 relation types (Relationship), with their typed attributes, inheritance, containment and relation ends.
  class Table {
    +string name
    +string schema
    +text comment
  }
  class Column {
    +string name
    +SqlType dataType
    +bool nullable
    +bool primaryKey
    +Column references
  }
  class Relationship {
    <<relation>>
    +string name
    +Cardinality sourceCardinality
    +Cardinality targetCardinality
    +bool identifying
  }
  Table "1" *-- "1..*" Column : contains
  Relationship --> Table : source
  Relationship --> Table : target
Layers of erd.dis. The eight DISL layers in order. erd.dis fills 8 of them: Metamodel (5 items), Coordinates (2 items), Notation (4 items), Toolbox (6 items), Constraints (3 items), Behavior (1 items), Layout (2 items), Persistence (6 items).
Generated from erd.dis at 4e69c28
Diagram as text (Mermaid)
flowchart LR
  accTitle: Layers of erd.dis
  accDescr: The eight DISL layers in order. erd.dis fills 8 of them: Metamodel (5 items), Coordinates (2 items), Notation (4 items), Toolbox (6 items), Constraints (3 items), Behavior (1 items), Layout (2 items), Persistence (6 items).
  L1["1 · Metamodel<br/>5 items"]
  L2["2 · Coordinate systems, placement and snapping<br/>2 items"]
  L3["3 · Notation (visual definition)<br/>4 items"]
  L4["4 · Toolbox, context tools and forms<br/>6 items"]
  L5["5 · Constraints<br/>3 items"]
  L6["6 · Behavior<br/>1 items"]
  L7["7 · Layout<br/>2 items"]
  L8["8 · Persistence<br/>6 items"]
  L1 --> L2 --> L3 --> L4 --> L5 --> L6 --> L7 --> L8
  classDef filled stroke-width:3px
  classDef empty stroke-dasharray:4 4
  class L1 filled
  class L2 filled
  class L3 filled
  class L4 filled
  class L5 filled
  class L6 filled
  class L7 filled
  class L8 filled

The file

Download erd.dis (9,674 bytes, unchanged from the source).

{
  "$schema": "https://etalii.net/adp/disl/schema/0.1/disl.schema.json#/$defs/Specification",
  "disl": "0.1",
  "language": {
    "id": "org.example.erd",
    "version": "2.0.0",
    "label": "Entity–relationship diagram",
    "doc": "Tables with typed columns and relationships in crow's-foot notation."
  },

  "metamodel": {
    "enums": {
      "SqlType": {
        "extensible": true,
        "values": { "uuid": {}, "text": {}, "varchar": {}, "int": {}, "bigint": {}, "numeric": {},
                    "bool": {}, "date": {}, "timestamptz": {}, "jsonb": {} }
      },
      "Cardinality": {
        "values": {
          "one": { "label": "Exactly one" },
          "zeroOrOne": { "label": "Zero or one" },
          "oneOrMany": { "label": "One or many" },
          "zeroOrMany": { "label": "Zero or many" }
        }
      }
    },
    "types": {
      "Table": {
        "attributes": {
          "name": { "type": "string", "required": true, "key": true, "pattern": "^[a-z_][a-z0-9_]*$", "unique": "diagram",
                    "doc": "snake_case table name." },
          "schema": { "type": "string", "default": "public" },
          "comment": { "type": "text" }
        },
        "children": { "allowed": ["Column"], "ordered": true, "min": 1,
                      "slots": { "columns": { "allowed": ["Column"], "min": 1 } } }
      },
      "Column": {
        "attributes": {
          "name": { "type": "string", "required": true, "key": true, "unique": "parent" },
          "dataType": { "type": "SqlType", "default": "text", "label": "Type" },
          "nullable": { "type": "bool", "default": true },
          "primaryKey": { "type": "bool", "default": false, "label": "Primary key" },
          "references": { "type": "Column", "doc": "Foreign-key target column." }
        }
      }
    },
    "relations": {
      "Relationship": {
        "source": "Table",
        "target": "Table",
        "directed": false,
        "allowSelfLoops": true,
        "attributes": {
          "name": { "type": "string" },
          "sourceCardinality": { "type": "Cardinality", "default": "one" },
          "targetCardinality": { "type": "Cardinality", "default": "zeroOrMany" },
          "identifying": { "type": "bool", "default": false,
                           "doc": "Identifying relationships make the parent key part of the child key." }
        }
      }
    }
  },

  "coordinates": {
    "snapProfiles": {
      "coarse": { "both": { "grid": { "spacing": 20 } }, "targets": ["objects", "rule"],
                  "doc": "Tables align to a 20 px grid; edges snap their bendpoints to 10 px.",
                  "bendpoints": { "both": { "grid": { "spacing": 10 } } } }
    },
    "systems": {
      "canvas": { "kind": "cartesian", "x": { "kind": "linear" }, "y": { "kind": "linear" }, "snapping": "coarse",
                  "grid": { "visible": true, "style": "lines", "spacing": 20, "majorEvery": 5 } }
    }
  },

  "notation": {
    "styles": {
      "table": { "fill": "#FFFFFF", "stroke": { "color": "#444441", "width": 1 }, "cornerRadius": 6,
                 "font": { "family": "Inter, sans-serif", "size": 12 }, "shadow": { "dx": 0, "dy": 1, "blur": 3, "color": "#00000022" } }
    },
    "nodes": {
      "Table": {
        "shape": { "type": "roundedRect", "params": { "radius": 6 } },
        "style": "table",
        "size": { "default": [220, 120], "autoSize": "fitHeight", "resizable": "horizontal" },
        "labels": [
          { "id": "name", "text": { "attribute": "name" }, "position": "top", "editable": "inline",
            "background": { "fill": "#EEEDFE", "padding": [6, 8] },
            "style": { "font": { "weight": 600, "color": "#3C3489" } } }
        ],
        "compartments": [
          { "id": "columns",
            "items": { "children": ["Column"], "slot": "columns" },
            "itemText": { "cel": "item.name + '  ' + item.dataType + (item.nullable ? '' : ' not null')" },
            "itemIcon": { "cel": "item.primaryKey ? 'key' : (has(item.references) ? 'link' : '')" },
            "itemConditions": [ { "when": "item.primaryKey", "style": { "font": { "weight": 600, "decoration": "underline" } } } ],
            "separator": { "color": "#D3D1C7", "width": 0.5 },
            "overflow": "ellipsis", "maxItems": 20,
            "editable": { "add": true, "remove": true, "reorder": true, "inline": true },
            "emptyText": "No columns" }
        ],
        "anchors": { "mode": "sides", "spread": true },
        "form": { "form": "columnsTable", "interaction": "onDoubleClick", "minZoom": 0.8, "fallback": "labels" },
        "lod": [ { "maxZoom": 0.4, "hide": ["compartments"] } ]
      },
      "Column": { "shape": "none", "labels": [] }
    },
    "edges": {
      "Relationship": {
        "line": {
          "stroke": { "color": "#444441", "width": 1.25,
                      "dash": { "cel": "self.identifying ? 'solid' : 'dashed'" } },
          "routing": "orthogonal",
          "cornerRadius": 0,
          "avoidNodes": true,
          "minSegmentLength": 18,
          "startDirection": "normal",
          "endDirection": "normal",
          "bendpoints": { "editable": true }
        },
        "jumps": { "style": "gap", "size": 5 },
        "sourceMarker": { "cel": "'er' + self.sourceCardinality.substring(0, 1).upperAscii() + self.sourceCardinality.substring(1)" },
        "targetMarker": { "cel": "'er' + self.targetCardinality.substring(0, 1).upperAscii() + self.targetCardinality.substring(1)" },
        "labels": [
          { "id": "name", "text": { "attribute": "name" }, "at": "middle", "side": "above", "orientation": "upright",
            "style": { "font": { "style": "italic", "size": 11 } }, "editable": "inline" },
          { "id": "srcCard", "text": { "cel": "enumLabel('Cardinality', self.sourceCardinality)" }, "at": "start", "offset": 20,
            "side": "below", "style": { "font": { "size": 10, "color": "#888780" } }, "visible": { "cel": "env.zoom > 1.2" } },
          { "id": "tgtCard", "text": { "cel": "enumLabel('Cardinality', self.targetCardinality)" }, "at": "end", "offset": 20,
            "side": "below", "style": { "font": { "size": 10, "color": "#888780" } }, "visible": { "cel": "env.zoom > 1.2" } }
        ]
      }
    }
  },

  "forms": {
    "columnsTable": {
      "for": "Table",
      "usage": ["embedded", "inspector"],
      "layout": { "density": "compact" },
      "items": [
        { "attribute": "name" },
        { "kind": "field", "attribute": "comment", "widget": "textarea", "rows": 2 }
      ]
    }
  },

  "toolbox": {
    "groups": [
      { "id": "model", "label": "Model", "tools": [
        { "id": "table", "creates": "Table", "shortcut": "T" },
        { "id": "rel", "creates": "Relationship", "shortcut": "R" },
        { "id": "rel1n", "creates": "Relationship", "variantOf": "rel", "label": "One-to-many",
          "initial": { "sourceCardinality": "one", "targetCardinality": "zeroOrMany" } },
        { "id": "rel11", "creates": "Relationship", "variantOf": "rel", "label": "One-to-one",
          "initial": { "sourceCardinality": "one", "targetCardinality": "zeroOrOne" } }
      ] }
    ],
    "contextTools": [
      { "for": ["Table"], "tools": [
        { "kind": "create-child", "creates": "Column", "slot": "columns", "label": "Add column", "icon": "plus", "position": "bottom" }
      ] }
    ]
  },

  "constraints": {
    "rules": [
      { "id": "hasPrimaryKey", "scope": "Table",
        "rule": "self.children.exists(c, c.primaryKey)",
        "severity": "warning", "message": { "cel": "'Table ' + self.name + ' has no primary key.'" },
        "fixes": [ { "label": "Add an id column",
                     "actions": [ { "create": { "type": "'Column'", "parent": "self", "slot": "'columns'",
                                                "attributes": { "name": "'id'", "dataType": "'uuid'", "primaryKey": "true", "nullable": "false" } } } ] } ] },
      { "id": "fkTypeMatches", "scope": "Column", "when": "has(self.references)",
        "rule": "self.references.dataType == self.dataType",
        "attribute": "dataType",
        "message": { "cel": "'Foreign key type ' + self.dataType + ' does not match ' + self.references.parent.name + '.' + self.references.name + ' (' + self.references.dataType + ').'" },
        "fixes": [ { "label": "Use the referenced column's type", "actions": [ { "set": { "dataType": "self.references.dataType" } } ] } ] },
      { "id": "pkNotNull", "scope": "Column", "when": "self.primaryKey", "rule": "!self.nullable",
        "enforcement": "prevent-and-report", "attribute": "nullable",
        "message": "Primary key columns cannot be nullable." }
    ]
  },

  "behavior": {
    "hooks": [
      { "id": "pkImpliesNotNull", "on": "change", "for": "Column", "attribute": "primaryKey", "phase": "before",
        "when": "self.primaryKey", "actions": [ { "set": { "nullable": "false" } } ] }
    ]
  },

  "layout": {
    "algorithms": { "orthogonal": { "algorithm": "elk:org.eclipse.elk.layered", "direction": "right",
                                    "edgeRouting": "orthogonal", "spacing": { "node": 60, "layer": 120 } } },
    "default": "orthogonal"
  },

  "persistence": {
    "format": "yaml",
    "ids": { "strategy": "natural", "stable": true },
    "ordering": { "elements": "tree" },
    "view": { "store": ["bounds", "waypoints", "labelOffsets", "collapsed"] },
    "export": [ { "format": "plugin:sql-ddl", "label": "SQL DDL" } ]
  },

  "plugins": {
    "sql-ddl": { "version": "^1.0.0", "provides": ["export"], "required": false,
                 "doc": "Generates CREATE TABLE statements." }
  }
}

In an IDE

No IDE host runs this example yet.