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
146 lines
3.6 KiB
Markdown
146 lines
3.6 KiB
Markdown
# 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
|
|
|
|
```go
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```dbml
|
|
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 `Ref` blocks
|
|
- Indexes and composite indexes
|
|
- Table notes and column notes
|
|
- Enums
|
|
- Dialect directives (`@postgres:` / `@sqlite:` — see below)
|
|
|
|
## 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.
|
|
|
|
```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)
|
|
}
|
|
}
|
|
```
|
|
|
|
| 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`](../../../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
|