So far so good
This commit is contained in:
349
pkg/writers/drawdb/writer.go
Normal file
349
pkg/writers/drawdb/writer.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package drawdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||
)
|
||||
|
||||
// Writer implements the writers.Writer interface for DrawDB JSON format
|
||||
type Writer struct {
|
||||
options *writers.WriterOptions
|
||||
}
|
||||
|
||||
// NewWriter creates a new DrawDB writer with the given options
|
||||
func NewWriter(options *writers.WriterOptions) *Writer {
|
||||
return &Writer{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteDatabase writes a Database model to DrawDB JSON format
|
||||
func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||
schema := w.databaseToDrawDB(db)
|
||||
return w.writeJSON(schema)
|
||||
}
|
||||
|
||||
// WriteSchema writes a Schema model to DrawDB JSON format
|
||||
func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
drawSchema := w.schemaToDrawDB(schema)
|
||||
return w.writeJSON(drawSchema)
|
||||
}
|
||||
|
||||
// WriteTable writes a Table model to DrawDB JSON format
|
||||
func (w *Writer) WriteTable(table *models.Table) error {
|
||||
drawSchema := w.tableToDrawDB(table)
|
||||
return w.writeJSON(drawSchema)
|
||||
}
|
||||
|
||||
// writeJSON marshals the data to JSON and writes to output
|
||||
func (w *Writer) writeJSON(data interface{}) error {
|
||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal to JSON: %w", err)
|
||||
}
|
||||
|
||||
if w.options.OutputPath != "" {
|
||||
return os.WriteFile(w.options.OutputPath, jsonData, 0644)
|
||||
}
|
||||
|
||||
// If no output path, print to stdout
|
||||
fmt.Println(string(jsonData))
|
||||
return nil
|
||||
}
|
||||
|
||||
// databaseToDrawDB converts a Database to DrawDB JSON format
|
||||
func (w *Writer) databaseToDrawDB(d *models.Database) *DrawDBSchema {
|
||||
schema := &DrawDBSchema{
|
||||
Tables: make([]*DrawDBTable, 0),
|
||||
Relationships: make([]*DrawDBRelationship, 0),
|
||||
Notes: make([]*DrawDBNote, 0),
|
||||
SubjectAreas: make([]*DrawDBArea, 0),
|
||||
}
|
||||
|
||||
// Track IDs and mappings
|
||||
tableID := 0
|
||||
fieldID := 0
|
||||
relationshipID := 0
|
||||
noteID := 0
|
||||
areaID := 0
|
||||
|
||||
// Map to track table name to ID
|
||||
tableMap := make(map[string]int)
|
||||
// Map to track field full path to ID
|
||||
fieldMap := make(map[string]int)
|
||||
|
||||
// Position tables in a grid layout
|
||||
gridX, gridY := 50, 50
|
||||
colWidth, rowHeight := 300, 200
|
||||
tablesPerRow := 4
|
||||
|
||||
tableIndex := 0
|
||||
|
||||
// Create subject areas for schemas
|
||||
for schemaIdx, schemaModel := range d.Schemas {
|
||||
if schemaModel.Description != "" || schemaModel.Comment != "" {
|
||||
note := schemaModel.Description
|
||||
if note != "" && schemaModel.Comment != "" {
|
||||
note += "\n"
|
||||
}
|
||||
note += schemaModel.Comment
|
||||
|
||||
area := &DrawDBArea{
|
||||
ID: areaID,
|
||||
Name: schemaModel.Name,
|
||||
Color: getColorForIndex(schemaIdx),
|
||||
X: gridX - 20,
|
||||
Y: gridY - 20,
|
||||
Width: colWidth*tablesPerRow + 100,
|
||||
Height: rowHeight*((len(schemaModel.Tables)/tablesPerRow)+1) + 100,
|
||||
}
|
||||
schema.SubjectAreas = append(schema.SubjectAreas, area)
|
||||
areaID++
|
||||
}
|
||||
|
||||
// Process tables in schema
|
||||
for _, table := range schemaModel.Tables {
|
||||
drawTable, newFieldID := w.convertTableToDrawDB(table, schemaModel.Name, tableID, fieldID, tableIndex, tablesPerRow, gridX, gridY, colWidth, rowHeight, schemaIdx)
|
||||
|
||||
// Store table mapping
|
||||
tableKey := fmt.Sprintf("%s.%s", schemaModel.Name, table.Name)
|
||||
tableMap[tableKey] = tableID
|
||||
|
||||
// Store field mappings
|
||||
for _, field := range drawTable.Fields {
|
||||
fieldKey := fmt.Sprintf("%s.%s.%s", schemaModel.Name, table.Name, field.Name)
|
||||
fieldMap[fieldKey] = field.ID
|
||||
}
|
||||
|
||||
schema.Tables = append(schema.Tables, drawTable)
|
||||
fieldID = newFieldID
|
||||
tableID++
|
||||
tableIndex++
|
||||
}
|
||||
}
|
||||
|
||||
// Add relationships
|
||||
for _, schemaModel := range d.Schemas {
|
||||
for _, table := range schemaModel.Tables {
|
||||
for _, constraint := range table.Constraints {
|
||||
if constraint.Type == models.ForeignKeyConstraint && constraint.ReferencedTable != "" {
|
||||
startTableKey := fmt.Sprintf("%s.%s", schemaModel.Name, table.Name)
|
||||
endTableKey := fmt.Sprintf("%s.%s", constraint.ReferencedSchema, constraint.ReferencedTable)
|
||||
|
||||
startTableID, startExists := tableMap[startTableKey]
|
||||
endTableID, endExists := tableMap[endTableKey]
|
||||
|
||||
if startExists && endExists && len(constraint.Columns) > 0 && len(constraint.ReferencedColumns) > 0 {
|
||||
// Find relative field IDs within their tables
|
||||
startFieldID := 0
|
||||
endFieldID := 0
|
||||
|
||||
for _, t := range schema.Tables {
|
||||
if t.ID == startTableID {
|
||||
for idx, f := range t.Fields {
|
||||
if f.Name == constraint.Columns[0] {
|
||||
startFieldID = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.ID == endTableID {
|
||||
for idx, f := range t.Fields {
|
||||
if f.Name == constraint.ReferencedColumns[0] {
|
||||
endFieldID = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relationship := &DrawDBRelationship{
|
||||
ID: relationshipID,
|
||||
Name: constraint.Name,
|
||||
StartTableID: startTableID,
|
||||
EndTableID: endTableID,
|
||||
StartFieldID: startFieldID,
|
||||
EndFieldID: endFieldID,
|
||||
Cardinality: "Many to one",
|
||||
UpdateConstraint: constraint.OnUpdate,
|
||||
DeleteConstraint: constraint.OnDelete,
|
||||
}
|
||||
|
||||
schema.Relationships = append(schema.Relationships, relationship)
|
||||
relationshipID++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add database description as a note
|
||||
if d.Description != "" || d.Comment != "" {
|
||||
note := d.Description
|
||||
if note != "" && d.Comment != "" {
|
||||
note += "\n"
|
||||
}
|
||||
note += d.Comment
|
||||
|
||||
schema.Notes = append(schema.Notes, &DrawDBNote{
|
||||
ID: noteID,
|
||||
Content: fmt.Sprintf("Database: %s\n\n%s", d.Name, note),
|
||||
Color: "#ffd93d",
|
||||
X: 10,
|
||||
Y: 10,
|
||||
})
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
// schemaToDrawDB converts a Schema to DrawDB format
|
||||
func (w *Writer) schemaToDrawDB(schema *models.Schema) *DrawDBSchema {
|
||||
drawSchema := &DrawDBSchema{
|
||||
Tables: make([]*DrawDBTable, 0),
|
||||
Relationships: make([]*DrawDBRelationship, 0),
|
||||
Notes: make([]*DrawDBNote, 0),
|
||||
SubjectAreas: make([]*DrawDBArea, 0),
|
||||
}
|
||||
|
||||
tableID := 0
|
||||
fieldID := 0
|
||||
gridX, gridY := 50, 50
|
||||
colWidth, rowHeight := 300, 200
|
||||
tablesPerRow := 4
|
||||
|
||||
for idx, table := range schema.Tables {
|
||||
drawTable, newFieldID := w.convertTableToDrawDB(table, schema.Name, tableID, fieldID, idx, tablesPerRow, gridX, gridY, colWidth, rowHeight, 0)
|
||||
drawSchema.Tables = append(drawSchema.Tables, drawTable)
|
||||
fieldID = newFieldID
|
||||
tableID++
|
||||
}
|
||||
|
||||
return drawSchema
|
||||
}
|
||||
|
||||
// tableToDrawDB converts a single Table to DrawDB format
|
||||
func (w *Writer) tableToDrawDB(table *models.Table) *DrawDBSchema {
|
||||
drawSchema := &DrawDBSchema{
|
||||
Tables: make([]*DrawDBTable, 0),
|
||||
Relationships: make([]*DrawDBRelationship, 0),
|
||||
Notes: make([]*DrawDBNote, 0),
|
||||
SubjectAreas: make([]*DrawDBArea, 0),
|
||||
}
|
||||
|
||||
drawTable, _ := w.convertTableToDrawDB(table, table.Schema, 0, 0, 0, 4, 50, 50, 300, 200, 0)
|
||||
drawSchema.Tables = append(drawSchema.Tables, drawTable)
|
||||
|
||||
return drawSchema
|
||||
}
|
||||
|
||||
// convertTableToDrawDB converts a table to DrawDB format and returns the table and next field ID
|
||||
func (w *Writer) convertTableToDrawDB(table *models.Table, schemaName string, tableID, fieldID, tableIndex, tablesPerRow, gridX, gridY, colWidth, rowHeight, colorIndex int) (*DrawDBTable, int) {
|
||||
// Calculate position
|
||||
x := gridX + (tableIndex%tablesPerRow)*colWidth
|
||||
y := gridY + (tableIndex/tablesPerRow)*rowHeight
|
||||
|
||||
drawTable := &DrawDBTable{
|
||||
ID: tableID,
|
||||
Name: table.Name,
|
||||
Schema: schemaName,
|
||||
Comment: table.Description,
|
||||
Color: getColorForIndex(colorIndex),
|
||||
X: x,
|
||||
Y: y,
|
||||
Fields: make([]*DrawDBField, 0),
|
||||
Indexes: make([]*DrawDBIndex, 0),
|
||||
}
|
||||
|
||||
// Add fields
|
||||
for _, column := range table.Columns {
|
||||
field := &DrawDBField{
|
||||
ID: fieldID,
|
||||
Name: column.Name,
|
||||
Type: formatTypeForDrawDB(column),
|
||||
Primary: column.IsPrimaryKey,
|
||||
NotNull: column.NotNull,
|
||||
Increment: column.AutoIncrement,
|
||||
Comment: column.Comment,
|
||||
}
|
||||
|
||||
if column.Default != nil {
|
||||
field.Default = fmt.Sprintf("%v", column.Default)
|
||||
}
|
||||
|
||||
// Check for unique constraint
|
||||
for _, constraint := range table.Constraints {
|
||||
if constraint.Type == models.UniqueConstraint {
|
||||
for _, col := range constraint.Columns {
|
||||
if col == column.Name {
|
||||
field.Unique = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawTable.Fields = append(drawTable.Fields, field)
|
||||
fieldID++
|
||||
}
|
||||
|
||||
// Add indexes
|
||||
indexID := 0
|
||||
for _, index := range table.Indexes {
|
||||
drawIndex := &DrawDBIndex{
|
||||
ID: indexID,
|
||||
Name: index.Name,
|
||||
Unique: index.Unique,
|
||||
Fields: make([]int, 0),
|
||||
}
|
||||
|
||||
// Map column names to field IDs
|
||||
for _, colName := range index.Columns {
|
||||
for idx, field := range drawTable.Fields {
|
||||
if field.Name == colName {
|
||||
drawIndex.Fields = append(drawIndex.Fields, idx)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawTable.Indexes = append(drawTable.Indexes, drawIndex)
|
||||
indexID++
|
||||
}
|
||||
|
||||
return drawTable, fieldID
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func formatTypeForDrawDB(column *models.Column) string {
|
||||
typeStr := column.Type
|
||||
if column.Length > 0 {
|
||||
typeStr = fmt.Sprintf("%s(%d)", typeStr, column.Length)
|
||||
} else if column.Precision > 0 {
|
||||
if column.Scale > 0 {
|
||||
typeStr = fmt.Sprintf("%s(%d,%d)", typeStr, column.Precision, column.Scale)
|
||||
} else {
|
||||
typeStr = fmt.Sprintf("%s(%d)", typeStr, column.Precision)
|
||||
}
|
||||
}
|
||||
return typeStr
|
||||
}
|
||||
|
||||
func getColorForIndex(index int) string {
|
||||
colors := []string{
|
||||
"#6366f1", // indigo
|
||||
"#8b5cf6", // violet
|
||||
"#ec4899", // pink
|
||||
"#f43f5e", // rose
|
||||
"#14b8a6", // teal
|
||||
"#06b6d4", // cyan
|
||||
"#0ea5e9", // sky
|
||||
"#3b82f6", // blue
|
||||
}
|
||||
return colors[index%len(colors)]
|
||||
}
|
||||
Reference in New Issue
Block a user