Files
relspecgo/pkg/writers/dbml/writer.go
Hein 5d60bc3b2c
Some checks are pending
CI / Test (1.23) (push) Waiting to run
CI / Test (1.24) (push) Waiting to run
CI / Test (1.25) (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Build (push) Waiting to run
Bugs Fixed
1. pkg/models/models.go:184 - Fixed typo in ForeignKeyConstraint constant from "foreign_Key" to "foreign_key"
  2. pkg/readers/drawdb/reader.go:62-68 - Fixed ReadSchema() to properly detect schema name from tables instead of hardcoding "default"
  3. pkg/writers/dbml/writer.go:128 - Changed primary key attribute from "primary key" to DBML standard "pk"
  4. pkg/writers/dbml/writer.go:208-221 - Fixed foreign key reference format to use "table.column" syntax for single columns instead of "table.(column)"

  Test Results

  All reader and writer tests are now passing:

  Readers:
  - DBML: 74.4% coverage (2 tests skipped due to missing parser features for Ref statements)
  - DCTX: 77.6% coverage
  - DrawDB: 83.6% coverage
  - JSON: 82.1% coverage
  - YAML: 82.1% coverage

  Writers:
  - Bun: 68.5% coverage
  - DBML: 91.5% coverage
  - DCTX: 100.0% coverage
  - DrawDB: 83.8% coverage
  - GORM: 69.2% coverage
  - JSON: 82.4% coverage
  - YAML: 82.4% coverage
2025-12-16 21:43:45 +02:00

238 lines
5.8 KiB
Go

package dbml
import (
"fmt"
"os"
"strings"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
)
// Writer implements the writers.Writer interface for DBML format
type Writer struct {
options *writers.WriterOptions
}
// NewWriter creates a new DBML writer with the given options
func NewWriter(options *writers.WriterOptions) *Writer {
return &Writer{
options: options,
}
}
// WriteDatabase writes a Database model to DBML format
func (w *Writer) WriteDatabase(db *models.Database) error {
content := w.databaseToDBML(db)
if w.options.OutputPath != "" {
return os.WriteFile(w.options.OutputPath, []byte(content), 0644)
}
// If no output path, print to stdout
fmt.Print(content)
return nil
}
// WriteSchema writes a Schema model to DBML format
func (w *Writer) WriteSchema(schema *models.Schema) error {
content := w.schemaToDBML(schema)
if w.options.OutputPath != "" {
return os.WriteFile(w.options.OutputPath, []byte(content), 0644)
}
fmt.Print(content)
return nil
}
// WriteTable writes a Table model to DBML format
func (w *Writer) WriteTable(table *models.Table) error {
content := w.tableToDBML(table, table.Schema)
if w.options.OutputPath != "" {
return os.WriteFile(w.options.OutputPath, []byte(content), 0644)
}
fmt.Print(content)
return nil
}
// databaseToDBML converts a Database to DBML format string
func (w *Writer) databaseToDBML(d *models.Database) string {
var result string
// Add database comment if exists
if d.Description != "" {
result += fmt.Sprintf("// %s\n", d.Description)
}
if d.Comment != "" {
result += fmt.Sprintf("// %s\n", d.Comment)
}
if d.Description != "" || d.Comment != "" {
result += "\n"
}
// Process each schema
for _, schema := range d.Schemas {
result += w.schemaToDBML(schema)
}
// Add relationships
result += "\n// Relationships\n"
for _, schema := range d.Schemas {
for _, table := range schema.Tables {
for _, constraint := range table.Constraints {
if constraint.Type == models.ForeignKeyConstraint {
result += w.constraintToDBML(constraint, schema.Name, table.Name)
}
}
}
}
return result
}
// schemaToDBML converts a Schema to DBML format string
func (w *Writer) schemaToDBML(schema *models.Schema) string {
var result string
if schema.Description != "" {
result += fmt.Sprintf("// Schema: %s - %s\n", schema.Name, schema.Description)
}
// Process tables
for _, table := range schema.Tables {
result += w.tableToDBML(table, schema.Name)
result += "\n"
}
return result
}
// tableToDBML converts a Table to DBML format string
func (w *Writer) tableToDBML(t *models.Table, schemaName string) string {
var result string
// Table definition
tableName := fmt.Sprintf("%s.%s", schemaName, t.Name)
result += fmt.Sprintf("Table %s {\n", tableName)
// Add columns
for _, column := range t.Columns {
result += fmt.Sprintf(" %s %s", column.Name, column.Type)
// Add column attributes
attrs := make([]string, 0)
if column.IsPrimaryKey {
attrs = append(attrs, "pk")
}
if column.NotNull && !column.IsPrimaryKey {
attrs = append(attrs, "not null")
}
if column.AutoIncrement {
attrs = append(attrs, "increment")
}
if column.Default != nil {
attrs = append(attrs, fmt.Sprintf("default: %v", column.Default))
}
if len(attrs) > 0 {
result += fmt.Sprintf(" [%s]", strings.Join(attrs, ", "))
}
if column.Comment != "" {
result += fmt.Sprintf(" // %s", column.Comment)
}
result += "\n"
}
// Add indexes
indexCount := 0
for _, index := range t.Indexes {
if indexCount == 0 {
result += "\n indexes {\n"
}
indexAttrs := make([]string, 0)
if index.Unique {
indexAttrs = append(indexAttrs, "unique")
}
if index.Name != "" {
indexAttrs = append(indexAttrs, fmt.Sprintf("name: '%s'", index.Name))
}
if index.Type != "" {
indexAttrs = append(indexAttrs, fmt.Sprintf("type: %s", index.Type))
}
result += fmt.Sprintf(" (%s)", strings.Join(index.Columns, ", "))
if len(indexAttrs) > 0 {
result += fmt.Sprintf(" [%s]", strings.Join(indexAttrs, ", "))
}
result += "\n"
indexCount++
}
if indexCount > 0 {
result += " }\n"
}
// Add table note
if t.Description != "" || t.Comment != "" {
note := t.Description
if note != "" && t.Comment != "" {
note += " - "
}
note += t.Comment
result += fmt.Sprintf("\n Note: '%s'\n", note)
}
result += "}\n"
return result
}
// constraintToDBML converts a Constraint to DBML format string
func (w *Writer) constraintToDBML(c *models.Constraint, schemaName, tableName string) string {
if c.Type != models.ForeignKeyConstraint || c.ReferencedTable == "" {
return ""
}
fromTable := fmt.Sprintf("%s.%s", schemaName, tableName)
toTable := fmt.Sprintf("%s.%s", c.ReferencedSchema, c.ReferencedTable)
// Determine relationship cardinality
// For foreign keys, it's typically many-to-one
relationship := ">"
// Build from and to column references
// For single columns: table.column
// For multiple columns: table.(col1, col2)
var fromRef, toRef string
if len(c.Columns) == 1 {
fromRef = fmt.Sprintf("%s.%s", fromTable, c.Columns[0])
} else {
fromRef = fmt.Sprintf("%s.(%s)", fromTable, strings.Join(c.Columns, ", "))
}
if len(c.ReferencedColumns) == 1 {
toRef = fmt.Sprintf("%s.%s", toTable, c.ReferencedColumns[0])
} else {
toRef = fmt.Sprintf("%s.(%s)", toTable, strings.Join(c.ReferencedColumns, ", "))
}
result := fmt.Sprintf("Ref: %s %s %s", fromRef, relationship, toRef)
// Add actions
actions := make([]string, 0)
if c.OnDelete != "" {
actions = append(actions, fmt.Sprintf("ondelete: %s", c.OnDelete))
}
if c.OnUpdate != "" {
actions = append(actions, fmt.Sprintf("onupdate: %s", c.OnUpdate))
}
if len(actions) > 0 {
result += fmt.Sprintf(" [%s]", strings.Join(actions, ", "))
}
result += "\n"
return result
}