Documentation
¶
Overview ¶
Package erd draws a connection's schema as an entity-relationship diagram: its tables, their columns and keys, and the foreign keys between them.
It renders the same three ways the explain package renders a plan, for the same reasons: a PNG or JPEG for a chat, a ticket or a slide (drawn in Go, picture.go), and Mermaid erDiagram source (mermaid.go) for a pull request or a wiki page that draws ```mermaid blocks.
db.Manager.Schema ──► *erd.Schema ──┬─► Mermaid() text
(catalog queries, (this file) ├─► PNG(opt) bytes
one per engine) └─► JPEG(opt) bytes
Around(names, depth) narrows it to one
table's neighbourhood first
The package is a leaf — it knows nothing of drivers or connections. The db package reads each engine's catalog and builds a Schema; everything here works on that model alone, so it is testable without a database.
Index ¶
- func Dir() string
- func SafeFileName(s string) string
- type Column
- type Options
- type Rel
- type Schema
- func (s *Schema) Around(names []string, depth int) (out *Schema, missing []string)
- func (s *Schema) FileName(ext string) string
- func (s *Schema) Find(name string) (*Table, bool)
- func (s *Schema) JPEG(opt Options) ([]byte, error)
- func (s *Schema) MarkKeys()
- func (s *Schema) Mermaid() string
- func (s *Schema) PNG(opt Options) ([]byte, error)
- func (s *Schema) Picture(opt Options) (*image.RGBA, error)
- func (s *Schema) Select(sel Selection) (out *Schema, missing []string)
- func (s *Schema) Sort()
- func (s *Schema) Title() string
- func (s *Schema) WithoutViews() *Schema
- func (s *Schema) WriteFile(dir, ext string, data []byte) (string, error)
- type Selection
- type Table
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SafeFileName ¶
SafeFileName keeps a connection name usable in a file name (and in a Content-Disposition header): anything but letters, digits, - and _ becomes _.
Types ¶
type Column ¶
type Column struct {
Name string
Type string
Nullable bool
PK bool // part of the primary key
FK bool // part of some foreign key
Unique bool // part of some unique key (not the primary key)
}
Column is one column and the key roles it plays.
type Options ¶
type Options struct {
// Palette colors the picture; the zero value is theme.Default(). dbc
// web passes the palette the page is wearing; the shell and the TUI
// pass plan_theme (or `dbc erd --theme`), the one setting for every
// picture dbc draws.
Palette theme.Palette
// Scale is device pixels per CSS pixel; 0 means 2 (sharp when zoomed).
// A diagram too big to draw at it is drawn smaller (see maxPixels).
Scale float64
}
Options choose how the picture looks. The zero value is dbc's dark palette at 2×.
type Rel ¶
Rel is one foreign key: Child's ChildCols reference Parent's ParentCols, pairwise in order. A self-reference (an employee's manager) has Child == Parent.
func (*Rel) Identifying ¶
Identifying reports whether the foreign key is part of the child's identity: every one of its columns is in the child's primary key (an order line keyed by (order_id, line_no)). Mermaid draws these solid and the rest dashed.
func (*Rel) OneToOne ¶
OneToOne reports whether at most one child row can reference a parent row: the foreign key's columns are exactly the child's primary key or one of its unique keys (as a set — key order does not matter for uniqueness). It decides the child end's notation: zero-or-one rather than zero-or-many.
func (*Rel) Optional ¶
Optional reports whether a child row may have no parent: any of the foreign key's columns is nullable. A composite key with one NULL column is not checked at all under the default MATCH SIMPLE, so one is enough. It decides the parent end's notation: zero-or-one rather than exactly one.
type Schema ¶
type Schema struct {
Conn string // connection name, for the title and file names
Driver string // engine, for the title
Tables []*Table
Rels []*Rel
}
Schema is the diagram's subject: a set of tables and the foreign keys among them. Rels only ever point at tables in Tables — a foreign key to a table outside the set (another MySQL database, a schema the user cannot see) is dropped when the Schema is built, as the diagram has no box to draw it to.
func (*Schema) Around ¶
Around narrows the schema to the named tables and every table within depth foreign-key hops of them, in either direction — the tables a person asking about orders wants to see: what orders references, and what references orders. depth 0 is the named tables alone; a negative depth is unlimited (the named tables' whole connected component).
A name that does not resolve (see Find) is returned in missing rather than failing the whole call, so a caller can say which one was wrong.
func (*Schema) FileName ¶
FileName is the name a saved or downloaded rendering takes: erd-<conn>-<yyyymmdd-hhmmss>.<ext>, or erd-<stamp>.<ext> with no connection — the plan files' pattern (explain.Plan.FileName), so the two sort side by side in a downloads folder.
func (*Schema) Find ¶
Find resolves a name as a caller spells it — the table's Label, or schema.name, or the bare name when that is unique — to a table. An exact match wins over a case-insensitive one, and a name that fits several tables (a bare name in two schemas) is not guessed at.
func (*Schema) MarkKeys ¶
func (s *Schema) MarkKeys()
MarkKeys sets each column's PK, FK and Unique flags from the tables' keys and the relationships. The builder calls it once the Schema is complete; it is idempotent, so a caller that edits keys can call it again.
func (*Schema) Mermaid ¶
Mermaid renders the schema as a Mermaid erDiagram, for a pull request, an issue or a wiki page that draws ```mermaid blocks (GitHub, GitLab, Notion, Obsidian …). The page that draws it applies its own theme, so there is no palette here.
erDiagram
%% dbc · demo (sqlite) · 3 tables · 2 relationships
owners ||--o{ cats : "owner_id"
cats ||--|{ visits : "cat_id"
cats {
INTEGER id PK
INTEGER owner_id FK
}
THE NOTATION. A relationship line is written parent-first, so its left end is the "one" side and its right end the child's:
parent end || exactly one the foreign key's columns are NOT NULL
|o zero or one one of them is nullable (Rel.Optional)
child end o{ zero or many the usual foreign key
o| zero or one the key's columns are unique in the
child (Rel.OneToOne): a 1:1 extension
line -- identifying the key is inside the child's PK
.. non-identifying the rest (Rel.Identifying)
The label is the child's key columns, which is what a reader needs to write the join; the constraint's name is usually a generated "<table>_<col>_fkey" that says the same thing less usefully.
NAMES. Mermaid's grammar is narrow: an entity name is a word, and an attribute's type and name are words of letters, digits, _ - ( ) [ ]. A table whose label is not such a word ("public.cats" on a multi-schema connection, a name with a space) is given a generated id with the label as its alias — id["label"] — which Mermaid draws as the label (entity name aliases need Mermaid 10.5 or later; a plain word is used whenever possible so older renderers still read most diagrams). A column name or type that is not a word is made one (spaces and commas become _), and the original is kept as the attribute's comment, so "numeric(10,2)" is still readable in the drawing.
func (*Schema) PNG ¶
PNG renders the picture as a PNG: lossless, and for flat fills and text usually smaller than the JPEG as well.
func (*Schema) Picture ¶
Picture draws the diagram.
Two passes: the layout is measured at 1× (so it does not depend on the scale), then the image is allocated at its final size and painted.
func (*Schema) Select ¶
Select narrows the schema to sel. Names that do not resolve come back in missing, and the rest of the selection still applies.
func (*Schema) Sort ¶
func (s *Schema) Sort()
Sort puts the tables in Label order and the relationships in (child, name) order, so every rendering of the same schema is byte-for-byte the same whatever order the catalog returned rows in.
func (*Schema) Title ¶
Title is the one-line description every rendering carries: where the schema came from and how big the diagram is.
func (*Schema) WithoutViews ¶
WithoutViews drops the views. Most diagrams want only tables: a view has no keys, so it would be a box with no lines, and a schema with many views would be mostly those.
type Selection ¶
type Selection struct {
// Tables, when set, centres the diagram on these tables (as Find
// resolves names) and their neighbours within Depth hops.
Tables []string
// Depth is Around's depth; ignored without Tables.
Depth int
// Views keeps the views. A view named in Tables is kept regardless —
// asking for a diagram of a view is asking to see it.
Views bool
// Schema, when set and Tables is not, keeps only that schema's tables:
// "diagram everything" from a sidebar that shows one schema at a time
// means everything in that schema, not every table in the database.
// With Tables it is ignored, since a neighbourhood follows its keys
// into whatever schema they lead.
Schema string
}
Selection is what a caller asks a diagram to show. The zero value is every table (no views) on the connection.
type Table ¶
type Table struct {
Schema string
Name string
// Label is how the table is named on the diagram, and how a caller
// names it to Around: bare when the connection has one schema,
// schema.name when it has several — the sidebar's rule, so the name a
// user clicks is the name that resolves.
Label string
View bool
Cols []*Column
PK []string // primary key columns, in key order; nil when none
Uniques [][]string // each unique key's columns, in key order
}
Table is one entity: a table, or a view when asked for.