So far so good
This commit is contained in:
304
pkg/readers/drawdb/reader.go
Normal file
304
pkg/readers/drawdb/reader.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package drawdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/readers"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/writers/drawdb"
|
||||
)
|
||||
|
||||
// Reader implements the readers.Reader interface for DrawDB JSON format
|
||||
type Reader struct {
|
||||
options *readers.ReaderOptions
|
||||
}
|
||||
|
||||
// NewReader creates a new DrawDB reader with the given options
|
||||
func NewReader(options *readers.ReaderOptions) *Reader {
|
||||
return &Reader{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDatabase reads and parses DrawDB JSON input, returning a Database model
|
||||
func (r *Reader) ReadDatabase() (*models.Database, error) {
|
||||
if r.options.FilePath == "" {
|
||||
return nil, fmt.Errorf("file path is required for DrawDB reader")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(r.options.FilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
var drawSchema drawdb.DrawDBSchema
|
||||
if err := json.Unmarshal(data, &drawSchema); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DrawDB JSON: %w", err)
|
||||
}
|
||||
|
||||
return r.convertToDatabase(&drawSchema)
|
||||
}
|
||||
|
||||
// ReadSchema reads and parses DrawDB JSON input, returning a Schema model
|
||||
func (r *Reader) ReadSchema() (*models.Schema, error) {
|
||||
if r.options.FilePath == "" {
|
||||
return nil, fmt.Errorf("file path is required for DrawDB reader")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(r.options.FilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
var drawSchema drawdb.DrawDBSchema
|
||||
if err := json.Unmarshal(data, &drawSchema); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DrawDB JSON: %w", err)
|
||||
}
|
||||
|
||||
return r.convertToSchema(&drawSchema, "default")
|
||||
}
|
||||
|
||||
// ReadTable reads and parses DrawDB JSON input, returning a Table model
|
||||
func (r *Reader) ReadTable() (*models.Table, error) {
|
||||
if r.options.FilePath == "" {
|
||||
return nil, fmt.Errorf("file path is required for DrawDB reader")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(r.options.FilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
var drawSchema drawdb.DrawDBSchema
|
||||
if err := json.Unmarshal(data, &drawSchema); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DrawDB JSON: %w", err)
|
||||
}
|
||||
|
||||
if len(drawSchema.Tables) == 0 {
|
||||
return nil, fmt.Errorf("no tables found in DrawDB JSON")
|
||||
}
|
||||
|
||||
// Return the first table
|
||||
return r.convertToTable(drawSchema.Tables[0], &drawSchema)
|
||||
}
|
||||
|
||||
// convertToDatabase converts a DrawDB schema to a Database model
|
||||
func (r *Reader) convertToDatabase(drawSchema *drawdb.DrawDBSchema) (*models.Database, error) {
|
||||
db := models.InitDatabase("database")
|
||||
|
||||
if r.options.Metadata != nil {
|
||||
if name, ok := r.options.Metadata["name"].(string); ok {
|
||||
db.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// Extract database info from notes
|
||||
for _, note := range drawSchema.Notes {
|
||||
if strings.HasPrefix(note.Content, "Database:") {
|
||||
parts := strings.SplitN(note.Content, "\n\n", 2)
|
||||
if len(parts) == 2 {
|
||||
db.Description = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group tables by schema
|
||||
schemaMap := make(map[string]*models.Schema)
|
||||
|
||||
for _, drawTable := range drawSchema.Tables {
|
||||
schemaName := drawTable.Schema
|
||||
if schemaName == "" {
|
||||
schemaName = "public"
|
||||
}
|
||||
|
||||
schema, exists := schemaMap[schemaName]
|
||||
if !exists {
|
||||
schema = models.InitSchema(schemaName)
|
||||
schemaMap[schemaName] = schema
|
||||
}
|
||||
|
||||
table, err := r.convertToTable(drawTable, drawSchema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert table %s: %w", drawTable.Name, err)
|
||||
}
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
}
|
||||
|
||||
// Add schemas to database
|
||||
for _, schema := range schemaMap {
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// convertToSchema converts DrawDB tables to a Schema model
|
||||
func (r *Reader) convertToSchema(drawSchema *drawdb.DrawDBSchema, schemaName string) (*models.Schema, error) {
|
||||
schema := models.InitSchema(schemaName)
|
||||
|
||||
for _, drawTable := range drawSchema.Tables {
|
||||
// Filter by schema if specified in the table
|
||||
if drawTable.Schema != "" && drawTable.Schema != schemaName {
|
||||
continue
|
||||
}
|
||||
|
||||
table, err := r.convertToTable(drawTable, drawSchema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert table %s: %w", drawTable.Name, err)
|
||||
}
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
// convertToTable converts a DrawDB table to a Table model
|
||||
func (r *Reader) convertToTable(drawTable *drawdb.DrawDBTable, drawSchema *drawdb.DrawDBSchema) (*models.Table, error) {
|
||||
schemaName := drawTable.Schema
|
||||
if schemaName == "" {
|
||||
schemaName = "public"
|
||||
}
|
||||
|
||||
table := models.InitTable(drawTable.Name, schemaName)
|
||||
table.Description = drawTable.Comment
|
||||
|
||||
// Convert fields to columns
|
||||
for _, field := range drawTable.Fields {
|
||||
column := r.convertToColumn(field, drawTable.Name, schemaName)
|
||||
table.Columns[column.Name] = column
|
||||
}
|
||||
|
||||
// Convert indexes
|
||||
for _, index := range drawTable.Indexes {
|
||||
idx := r.convertToIndex(index, drawTable, schemaName)
|
||||
table.Indexes[idx.Name] = idx
|
||||
}
|
||||
|
||||
// Find and convert relationships/constraints for this table
|
||||
for _, rel := range drawSchema.Relationships {
|
||||
if rel.StartTableID == drawTable.ID {
|
||||
constraint := r.convertToConstraint(rel, drawSchema)
|
||||
if constraint != nil {
|
||||
table.Constraints[constraint.Name] = constraint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// convertToColumn converts a DrawDB field to a Column model
|
||||
func (r *Reader) convertToColumn(field *drawdb.DrawDBField, tableName, schemaName string) *models.Column {
|
||||
column := models.InitColumn(field.Name, tableName, schemaName)
|
||||
|
||||
// Parse type and dimensions
|
||||
typeStr := field.Type
|
||||
column.Type = typeStr
|
||||
|
||||
// Try to extract length/precision from type string like "varchar(255)" or "decimal(10,2)"
|
||||
if strings.Contains(typeStr, "(") {
|
||||
parts := strings.Split(typeStr, "(")
|
||||
column.Type = parts[0]
|
||||
|
||||
if len(parts) > 1 {
|
||||
dimensions := strings.TrimSuffix(parts[1], ")")
|
||||
if strings.Contains(dimensions, ",") {
|
||||
// Precision and scale (e.g., decimal(10,2))
|
||||
dims := strings.Split(dimensions, ",")
|
||||
if precision, err := strconv.Atoi(strings.TrimSpace(dims[0])); err == nil {
|
||||
column.Precision = precision
|
||||
}
|
||||
if len(dims) > 1 {
|
||||
if scale, err := strconv.Atoi(strings.TrimSpace(dims[1])); err == nil {
|
||||
column.Scale = scale
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Just length (e.g., varchar(255))
|
||||
if length, err := strconv.Atoi(dimensions); err == nil {
|
||||
column.Length = length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column.IsPrimaryKey = field.Primary
|
||||
column.NotNull = field.NotNull || field.Primary
|
||||
column.AutoIncrement = field.Increment
|
||||
column.Comment = field.Comment
|
||||
|
||||
if field.Default != "" {
|
||||
column.Default = field.Default
|
||||
}
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
// convertToIndex converts a DrawDB index to an Index model
|
||||
func (r *Reader) convertToIndex(drawIndex *drawdb.DrawDBIndex, drawTable *drawdb.DrawDBTable, schemaName string) *models.Index {
|
||||
index := models.InitIndex(drawIndex.Name)
|
||||
index.Table = drawTable.Name
|
||||
index.Schema = schemaName
|
||||
index.Unique = drawIndex.Unique
|
||||
|
||||
// Convert field IDs to column names
|
||||
for _, fieldID := range drawIndex.Fields {
|
||||
if fieldID >= 0 && fieldID < len(drawTable.Fields) {
|
||||
index.Columns = append(index.Columns, drawTable.Fields[fieldID].Name)
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
// convertToConstraint converts a DrawDB relationship to a Constraint model
|
||||
func (r *Reader) convertToConstraint(rel *drawdb.DrawDBRelationship, drawSchema *drawdb.DrawDBSchema) *models.Constraint {
|
||||
// Find the start and end tables
|
||||
var startTable, endTable *drawdb.DrawDBTable
|
||||
for _, table := range drawSchema.Tables {
|
||||
if table.ID == rel.StartTableID {
|
||||
startTable = table
|
||||
}
|
||||
if table.ID == rel.EndTableID {
|
||||
endTable = table
|
||||
}
|
||||
}
|
||||
|
||||
if startTable == nil || endTable == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
constraint := models.InitConstraint(rel.Name, models.ForeignKeyConstraint)
|
||||
|
||||
// Get the column names from field IDs
|
||||
if rel.StartFieldID >= 0 && rel.StartFieldID < len(startTable.Fields) {
|
||||
constraint.Columns = append(constraint.Columns, startTable.Fields[rel.StartFieldID].Name)
|
||||
}
|
||||
|
||||
if rel.EndFieldID >= 0 && rel.EndFieldID < len(endTable.Fields) {
|
||||
constraint.ReferencedColumns = append(constraint.ReferencedColumns, endTable.Fields[rel.EndFieldID].Name)
|
||||
}
|
||||
|
||||
constraint.Table = startTable.Name
|
||||
constraint.Schema = startTable.Schema
|
||||
if constraint.Schema == "" {
|
||||
constraint.Schema = "public"
|
||||
}
|
||||
|
||||
constraint.ReferencedTable = endTable.Name
|
||||
constraint.ReferencedSchema = endTable.Schema
|
||||
if constraint.ReferencedSchema == "" {
|
||||
constraint.ReferencedSchema = "public"
|
||||
}
|
||||
|
||||
constraint.OnUpdate = rel.UpdateConstraint
|
||||
constraint.OnDelete = rel.DeleteConstraint
|
||||
|
||||
return constraint
|
||||
}
|
||||
Reference in New Issue
Block a user