Commented refs are collected per file and resolved against the combined model after all inputs are loaded (directory, --from-list, merge, jobs). Matched refs become FKs and relationships; duplicates of existing FKs are skipped; missing targets are skipped with a warning; column type mismatches warn. Also keep reused index names within a DBML table instead of overwriting, give a second FK to the same table a distinct relationship name, and make the pgsql writer match relationships to FKs by name first.
DBML Reader
Reads Database Markup Language (DBML) files and extracts database schema information.
Overview
The DBML Reader parses .dbml files that define database schemas using the DBML syntax (used by dbdiagram.io) and converts them into RelSpec's internal database model representation.
Features
- Parses DBML syntax
- Extracts tables, columns, and relationships
- Supports DBML-specific features:
- Table groups and notes
- Enum definitions
- Indexes
- Foreign key relationships
Usage
Basic Example
package main
import (
"fmt"
"git.warky.dev/wdevs/relspecgo/pkg/readers"
"git.warky.dev/wdevs/relspecgo/pkg/readers/dbml"
)
func main() {
options := &readers.ReaderOptions{
FilePath: "/path/to/schema.dbml",
}
reader := dbml.NewReader(options)
db, err := reader.ReadDatabase()
if err != nil {
panic(err)
}
fmt.Printf("Found %d schemas\n", len(db.Schemas))
}
CLI Example
# Read DBML file and convert to JSON
relspec --input dbml --in-file schema.dbml --output json --out-file schema.json
# Convert DBML to GORM models
relspec --input dbml --in-file database.dbml --output gorm --out-file models.go
Example DBML File
Table users {
id bigserial [pk, increment]
username varchar(50) [not null, unique]
email varchar(100) [not null]
created_at timestamp [not null, default: `now()`]
Note: 'Users table'
}
Table posts {
id bigserial [pk]
user_id bigint [not null, ref: > users.id]
title varchar(200) [not null]
content text
indexes {
user_id
(user_id, created_at) [name: 'idx_user_posts']
}
}
Ref: posts.user_id > users.id [delete: cascade]
DBML Features Supported
- Table definitions with columns
- Primary keys (
pk) - Not null constraints (
not null) - Unique constraints (
unique) - Default values (
default) - Inline references (
ref) - Standalone
Refblocks - Commented cross-file refs (
// Ref:— see below) - Indexes and composite indexes
- Table notes and column notes
- Enums
- Dialect directives (
@postgres:/@sqlite:— see below)
Commented cross-file refs
// Ref: / // ref: lines (ignored by dbdiagram) become FKs + relationships once both ends are loaded.
| Rule | Behaviour |
|---|---|
| When | Single file / directory: end of read. --from-list, merge, jobs: after all inputs are combined |
| Match | schema.table.column on both sides, case-insensitive |
| Operators | >, <, - (parsed like Ref:) |
| Duplicate of an FK on the same columns | Skipped silently |
| Target not loaded | Kept pending; skipped with a warning on the final pass |
| Column type mismatch | Warning, FK still created (serial≈integer, bigserial≈bigint) |
| Pending state | Database.Metadata["dbml.commented_refs"] ([]string) |
API: dbml.ResolveCommentedRefs(db, final bool) []string returns warnings.
Dialect directives
Lines of the form @<namespace>[(<column>)]: <args> embed database-specific
features that plain DBML cannot express (partitioning, WITHOUT ROWID,
tablespaces, index storage parameters, …). They are stored losslessly on the
relevant object's Metadata and round-trip unchanged through the DBML writer;
the PostgreSQL and SQLite writers translate the ones they understand to SQL.
@postgres: search_path myapp
Table myapp.events {
id bigint [pk]
created_at timestamp [not null]
@postgres(id): identity always
@postgres: partition by RANGE (created_at)
@sqlite: without rowid
indexes {
(created_at) [name: 'idx_events_created']
@postgres: with (fillfactor=90)
}
}
| Position | Attaches to |
|---|---|
Before the first Table { |
database |
Table body, no (target) |
that table |
Table body, (col) target |
column col (error if unknown) |
Inside indexes { } |
the most recently listed index entry |
args is preserved verbatim; the key (lowercased first token) drives
duplicate detection. Repeated directives are kept in order; catalog "singleton"
keys error on a second occurrence at the same location. All errors are
line-numbered.
ReaderOptions.StrictDirectives (CLI --strict-directives) turns an unknown
namespace or key into an error instead of preserving it silently.
See docs/DBML_DIRECTIVES.md for the full
grammar and the supported-directive matrix.
Notes
- DBML is designed for database documentation and diagramming
- Schema name defaults to
public - Relationship cardinality is preserved