Files
relspecgo/pkg/writers/dbml/README.md
HeinandClaude Sonnet 5 ce3b615b0a feat(dbml): @postgres/@sqlite dialect directives (#19)
Add parseable `@<namespace>[(<target>)]: <args>` directives embedded in DBML.
They are stored losslessly on each object's Metadata, round-trip unchanged
through the DBML writer, and are translated to SQL only by the writer for the
matching dialect.

- models: Directive type + catalog; Metadata map added to Column and Index
- dbml reader: parse and attach directives at database/table/column/index
  level; line-numbered errors; repeatable by default with singleton duplicate
  detection. Fixes a preexisting bug where an `indexes {}` closing brace ended
  the table early, dropping trailing Note: and directive lines.
- dbml writer: re-emit directives at their location; idempotent output
- pgsql writer: PARTITION BY / INHERITS / WITH / TABLESPACE (table),
  STORAGE / COMPRESSION / identity (column), WITH / TABLESPACE (index)
- sqlite writer: WITHOUT ROWID / STRICT (table), COLLATE (column)
- --strict-directives flag on ReaderOptions and WriterOptions
- docs/DBML_DIRECTIVES.md + reader/writer READMEs

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ss2MY5J11cRGwEz86ZXk7d
2026-09-08 16:17:37 +02:00

197 lines
4.3 KiB
Markdown

# DBML Writer
Generates Database Markup Language (DBML) files from database schema information.
## Overview
The DBML Writer converts RelSpec's internal database model representation into DBML syntax, suitable for use with dbdiagram.io and other DBML-compatible tools.
## Features
- Generates DBML syntax
- Creates table definitions with columns
- Defines relationships
- Includes indexes
- Adds notes and documentation
- Supports enums
## Usage
### Basic Example
```go
package main
import (
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
"git.warky.dev/wdevs/relspecgo/pkg/writers/dbml"
)
func main() {
options := &writers.WriterOptions{
OutputPath: "schema.dbml",
}
writer := dbml.NewWriter(options)
err := writer.WriteDatabase(db)
if err != nil {
panic(err)
}
}
```
### CLI Examples
```bash
# Generate DBML from PostgreSQL database
relspec --input pgsql \
--conn "postgres://localhost/mydb" \
--output dbml \
--out-file schema.dbml
# Convert GORM models to DBML
relspec --input gorm --in-file models.go --output dbml --out-file database.dbml
# Convert JSON to DBML for visualization
relspec --input json --in-file schema.json --output dbml --out-file diagram.dbml
```
## Generated DBML Example
```dbml
Project MyDatabase {
database_type: 'PostgreSQL'
}
Table users {
id bigserial [pk, increment]
username varchar(50) [not null, unique]
email varchar(100) [not null]
bio text [null]
created_at timestamp [not null, default: `now()`]
Note: 'Users table'
indexes {
email [name: 'idx_users_email']
}
}
Table posts {
id bigserial [pk, increment]
user_id bigint [not null]
title varchar(200) [not null]
content text [null]
created_at timestamp [default: `now()`]
indexes {
user_id [name: 'idx_posts_user_id']
(user_id, created_at) [name: 'idx_posts_user_created']
}
}
Ref: posts.user_id > users.id [delete: cascade, update: no action]
```
## DBML Features
### Table Definitions
```dbml
Table table_name {
column_name type [attributes]
}
```
### Column Attributes
- `pk` - Primary key
- `increment` - Auto-increment
- `not null` - NOT NULL constraint
- `null` - Nullable (explicit)
- `unique` - Unique constraint
- `default: value` - Default value
- `note: 'text'` - Column note
### Relationships
```dbml
Ref: table1.column > table2.column
Ref: table1.column < table2.column
Ref: table1.column - table2.column
```
Relationship types:
- `>` - Many-to-one
- `<` - One-to-many
- `-` - One-to-one
Relationship actions:
```dbml
Ref: posts.user_id > users.id [delete: cascade, update: restrict]
```
### Indexes
```dbml
indexes {
column_name
(column1, column2) [name: 'idx_name', unique]
}
```
### Dialect directives
Dialect directives stored on a model object's `Metadata` (namespace `postgres`,
`sqlite`, …) are re-emitted verbatim, one line per directive, at the location
they belong to:
```dbml
@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)
}
}
```
| Emitted at | From |
|------------|------|
| Before the first table | `Database.Metadata` |
| After a column line, as `@ns(col): …` | `Column.Metadata` |
| After an index line, inside `indexes { }` | `Index.Metadata` |
| After the `indexes` block, before `Note:` | `Table.Metadata` |
Output is deterministic (ordered by namespace, then source line, then args), so a
`DBML → model → DBML` round-trip is idempotent. See
[`docs/DBML_DIRECTIVES.md`](../../../docs/DBML_DIRECTIVES.md) for the grammar and
the list of directives the PostgreSQL and SQLite writers translate to SQL.
## Type Mapping
| SQL Type | DBML Type |
|----------|-----------|
| bigint | bigint |
| integer | int |
| varchar(n) | varchar(n) |
| text | text |
| boolean | boolean |
| timestamp | timestamp |
| date | date |
| json | json |
| uuid | uuid |
## Notes
- DBML is designed for database visualization
- Can be imported into dbdiagram.io
- Human-readable format
- Schema names can be included in table names
- Comments and notes are preserved
- Ideal for documentation and sharing designs