diff --git a/pkg/models/models.go b/pkg/models/models.go index 56f4f40..2645f4b 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -7,6 +7,8 @@ package models import ( "strings" "time" + + "github.com/google/uuid" ) // DatabaseType represents the type of database system. @@ -30,6 +32,7 @@ type Database struct { DatabaseVersion string `json:"database_version,omitempty" yaml:"database_version,omitempty" xml:"database_version,omitempty"` SourceFormat string `json:"source_format,omitempty" yaml:"source_format,omitempty" xml:"source_format,omitempty"` // Source Format of the database. UpdatedAt string `json:"updatedat,omitempty" yaml:"updatedat,omitempty" xml:"updatedat,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the database name in lowercase for SQL compatibility. @@ -51,6 +54,7 @@ type Domain struct { Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the domain name in lowercase for SQL compatibility. @@ -66,6 +70,7 @@ type DomainTable struct { SchemaName string `json:"schema_name" yaml:"schema_name" xml:"schema_name"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` RefTable *Table `json:"-" yaml:"-" xml:"-"` // Excluded to prevent circular references + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // Schema represents a database schema, which is a logical grouping of database objects @@ -86,6 +91,7 @@ type Schema struct { Relations []*Relationship `json:"relations,omitempty" yaml:"relations,omitempty" xml:"-"` Enums []*Enum `json:"enums,omitempty" yaml:"enums,omitempty" xml:"enums"` UpdatedAt string `json:"updatedat,omitempty" yaml:"updatedat,omitempty" xml:"updatedat,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // UpdaUpdateDateted sets the UpdatedAt field to the current time in RFC3339 format. @@ -117,6 +123,7 @@ type Table struct { Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` RefSchema *Schema `json:"-" yaml:"-" xml:"-"` // Excluded to prevent circular references UpdatedAt string `json:"updatedat,omitempty" yaml:"updatedat,omitempty" xml:"updatedat,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // UpdateDate sets the UpdatedAt field to the current time in RFC3339 format. @@ -165,6 +172,7 @@ type View struct { Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty" xml:"-"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` RefSchema *Schema `json:"-" yaml:"-" xml:"-"` // Excluded to prevent circular references + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the view name in lowercase for SQL compatibility. @@ -188,6 +196,7 @@ type Sequence struct { Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` RefSchema *Schema `json:"-" yaml:"-" xml:"-"` // Excluded to prevent circular references + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the sequence name in lowercase for SQL compatibility. @@ -212,6 +221,7 @@ type Column struct { Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` Collation string `json:"collation,omitempty" yaml:"collation,omitempty" xml:"collation,omitempty"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the column name in lowercase for SQL compatibility. @@ -234,6 +244,7 @@ type Index struct { Include []string `json:"include,omitempty" yaml:"include,omitempty" xml:"include,omitempty"` // INCLUDE columns Comment string `json:"comment,omitempty" yaml:"comment,omitempty" xml:"comment,omitempty"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the index name in lowercase for SQL compatibility. @@ -268,6 +279,7 @@ type Relationship struct { ThroughSchema string `json:"through_schema,omitempty" yaml:"through_schema,omitempty" xml:"through_schema,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty" xml:"description,omitempty"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the relationship name in lowercase for SQL compatibility. @@ -292,6 +304,7 @@ type Constraint struct { Deferrable bool `json:"deferrable,omitempty" yaml:"deferrable,omitempty" xml:"deferrable,omitempty"` InitiallyDeferred bool `json:"initially_deferred,omitempty" yaml:"initially_deferred,omitempty" xml:"initially_deferred,omitempty"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the constraint name in lowercase for SQL compatibility. @@ -307,6 +320,7 @@ type Enum struct { Name string `json:"name" yaml:"name" xml:"name"` Values []string `json:"values" yaml:"values" xml:"values"` Schema string `json:"schema,omitempty" yaml:"schema,omitempty" xml:"schema,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the enum name in lowercase for SQL compatibility. @@ -314,6 +328,16 @@ func (d *Enum) SQLName() string { return strings.ToLower(d.Name) } +// InitEnum initializes a new Enum with empty values slice +func InitEnum(name, schema string) *Enum { + return &Enum{ + Name: name, + Schema: schema, + Values: make([]string, 0), + GUID: uuid.New().String(), + } +} + // Supported constraint types. const ( PrimaryKeyConstraint ConstraintType = "primary_key" // Primary key uniquely identifies each record @@ -335,6 +359,7 @@ type Script struct { Version string `json:"version,omitempty" yaml:"version,omitempty" xml:"version,omitempty"` Priority int `json:"priority,omitempty" yaml:"priority,omitempty" xml:"priority,omitempty"` Sequence uint `json:"sequence,omitempty" yaml:"sequence,omitempty" xml:"sequence,omitempty"` + GUID string `json:"guid" yaml:"guid" xml:"guid"` } // SQLName returns the script name in lowercase for SQL compatibility. @@ -350,6 +375,7 @@ func InitDatabase(name string) *Database { Name: name, Schemas: make([]*Schema, 0), Domains: make([]*Domain, 0), + GUID: uuid.New().String(), } } @@ -363,6 +389,7 @@ func InitSchema(name string) *Schema { Permissions: make(map[string]string), Metadata: make(map[string]any), Scripts: make([]*Script, 0), + GUID: uuid.New().String(), } } @@ -376,6 +403,7 @@ func InitTable(name, schema string) *Table { Indexes: make(map[string]*Index), Relationships: make(map[string]*Relationship), Metadata: make(map[string]any), + GUID: uuid.New().String(), } } @@ -385,6 +413,7 @@ func InitColumn(name, table, schema string) *Column { Name: name, Table: table, Schema: schema, + GUID: uuid.New().String(), } } @@ -396,6 +425,7 @@ func InitIndex(name, table, schema string) *Index { Schema: schema, Columns: make([]string, 0), Include: make([]string, 0), + GUID: uuid.New().String(), } } @@ -408,6 +438,7 @@ func InitRelation(name, schema string) *Relationship { Properties: make(map[string]string), FromColumns: make([]string, 0), ToColumns: make([]string, 0), + GUID: uuid.New().String(), } } @@ -417,6 +448,7 @@ func InitRelationship(name string, relType RelationType) *Relationship { Name: name, Type: relType, Properties: make(map[string]string), + GUID: uuid.New().String(), } } @@ -427,6 +459,7 @@ func InitConstraint(name string, constraintType ConstraintType) *Constraint { Type: constraintType, Columns: make([]string, 0), ReferencedColumns: make([]string, 0), + GUID: uuid.New().String(), } } @@ -435,6 +468,7 @@ func InitScript(name string) *Script { return &Script{ Name: name, RunAfter: make([]string, 0), + GUID: uuid.New().String(), } } @@ -445,6 +479,7 @@ func InitView(name, schema string) *View { Schema: schema, Columns: make(map[string]*Column), Metadata: make(map[string]any), + GUID: uuid.New().String(), } } @@ -455,6 +490,7 @@ func InitSequence(name, schema string) *Sequence { Schema: schema, IncrementBy: 1, StartValue: 1, + GUID: uuid.New().String(), } } @@ -464,6 +500,7 @@ func InitDomain(name string) *Domain { Name: name, Tables: make([]*DomainTable, 0), Metadata: make(map[string]any), + GUID: uuid.New().String(), } } @@ -472,5 +509,6 @@ func InitDomainTable(tableName, schemaName string) *DomainTable { return &DomainTable{ TableName: tableName, SchemaName: schemaName, + GUID: uuid.New().String(), } } diff --git a/pkg/readers/dctx/reader.go b/pkg/readers/dctx/reader.go index a9b51ec..c0fce64 100644 --- a/pkg/readers/dctx/reader.go +++ b/pkg/readers/dctx/reader.go @@ -79,6 +79,8 @@ func (r *Reader) convertToDatabase(dctx *models.DCTXDictionary) (*models.Databas db := models.InitDatabase(dbName) schema := models.InitSchema("public") + // Note: DCTX doesn't have database GUID, but schema can use dictionary name if available + // Create GUID mappings for tables and keys tableGuidMap := make(map[string]string) // GUID -> table name keyGuidMap := make(map[string]*models.DCTXKey) // GUID -> key definition @@ -162,6 +164,10 @@ func (r *Reader) convertTable(dctxTable *models.DCTXTable) (*models.Table, map[s tableName := r.sanitizeName(dctxTable.Name) table := models.InitTable(tableName, "public") table.Description = dctxTable.Description + // Assign GUID from DCTX table + if dctxTable.Guid != "" { + table.GUID = dctxTable.Guid + } fieldGuidMap := make(map[string]string) @@ -202,6 +208,10 @@ func (r *Reader) convertField(dctxField *models.DCTXField, tableName string) ([] // Convert single field column := models.InitColumn(r.sanitizeName(dctxField.Name), tableName, "public") + // Assign GUID from DCTX field + if dctxField.Guid != "" { + column.GUID = dctxField.Guid + } // Map Clarion data types dataType, length := r.mapDataType(dctxField.DataType, dctxField.Size) @@ -346,6 +356,10 @@ func (r *Reader) convertKey(dctxKey *models.DCTXKey, table *models.Table, fieldG constraint.Table = table.Name constraint.Schema = table.Schema constraint.Columns = columns + // Assign GUID from DCTX key + if dctxKey.Guid != "" { + constraint.GUID = dctxKey.Guid + } table.Constraints[constraint.Name] = constraint @@ -366,6 +380,10 @@ func (r *Reader) convertKey(dctxKey *models.DCTXKey, table *models.Table, fieldG index.Columns = columns index.Unique = dctxKey.Unique index.Type = "btree" + // Assign GUID from DCTX key + if dctxKey.Guid != "" { + index.GUID = dctxKey.Guid + } table.Indexes[index.Name] = index return nil @@ -460,6 +478,10 @@ func (r *Reader) processRelations(dctx *models.DCTXDictionary, schema *models.Sc constraint.ReferencedColumns = pkColumns constraint.OnDelete = r.mapReferentialAction(relation.Delete) constraint.OnUpdate = r.mapReferentialAction(relation.Update) + // Assign GUID from DCTX relation + if relation.Guid != "" { + constraint.GUID = relation.Guid + } foreignTable.Constraints[fkName] = constraint @@ -473,6 +495,10 @@ func (r *Reader) processRelations(dctx *models.DCTXDictionary, schema *models.Sc relationship.ForeignKey = fkName relationship.Properties["on_delete"] = constraint.OnDelete relationship.Properties["on_update"] = constraint.OnUpdate + // Assign GUID from DCTX relation + if relation.Guid != "" { + relationship.GUID = relation.Guid + } foreignTable.Relationships[relationshipName] = relationship } diff --git a/pkg/readers/drizzle/reader.go b/pkg/readers/drizzle/reader.go index 36d1202..949bd39 100644 --- a/pkg/readers/drizzle/reader.go +++ b/pkg/readers/drizzle/reader.go @@ -241,11 +241,9 @@ func (r *Reader) parsePgEnum(line string, matches []string) *models.Enum { } } - return &models.Enum{ - Name: enumName, - Values: values, - Schema: "public", - } + enum := models.InitEnum(enumName, "public") + enum.Values = values + return enum } // parseTableBlock parses a complete pgTable definition block diff --git a/pkg/readers/graphql/reader.go b/pkg/readers/graphql/reader.go index e9dc68a..750ea43 100644 --- a/pkg/readers/graphql/reader.go +++ b/pkg/readers/graphql/reader.go @@ -260,11 +260,7 @@ func (r *Reader) parseType(typeName string, lines []string, schema *models.Schem } func (r *Reader) parseEnum(enumName string, lines []string, schema *models.Schema) { - enum := &models.Enum{ - Name: enumName, - Schema: schema.Name, - Values: make([]string, 0), - } + enum := models.InitEnum(enumName, schema.Name) for _, line := range lines { trimmed := strings.TrimSpace(line) diff --git a/pkg/readers/prisma/reader.go b/pkg/readers/prisma/reader.go index c961669..788f8e2 100644 --- a/pkg/readers/prisma/reader.go +++ b/pkg/readers/prisma/reader.go @@ -128,11 +128,7 @@ func (r *Reader) parsePrisma(content string) (*models.Database, error) { if matches := enumRegex.FindStringSubmatch(trimmed); matches != nil { currentBlock = "enum" enumName := matches[1] - currentEnum = &models.Enum{ - Name: enumName, - Schema: "public", - Values: make([]string, 0), - } + currentEnum = models.InitEnum(enumName, "public") blockContent = []string{} continue } diff --git a/pkg/readers/sqldir/reader.go b/pkg/readers/sqldir/reader.go index 447fdd4..928ad76 100644 --- a/pkg/readers/sqldir/reader.go +++ b/pkg/readers/sqldir/reader.go @@ -150,13 +150,11 @@ func (r *Reader) readScripts() ([]*models.Script, error) { } // Create Script model - script := &models.Script{ - Name: name, - Description: fmt.Sprintf("SQL script from %s", relPath), - SQL: string(content), - Priority: priority, - Sequence: uint(sequence), - } + script := models.InitScript(name) + script.Description = fmt.Sprintf("SQL script from %s", relPath) + script.SQL = string(content) + script.Priority = priority + script.Sequence = uint(sequence) scripts = append(scripts, script) diff --git a/pkg/writers/bun/writer.go b/pkg/writers/bun/writer.go index 2aa0baa..7cbae80 100644 --- a/pkg/writers/bun/writer.go +++ b/pkg/writers/bun/writer.go @@ -386,6 +386,7 @@ func (w *Writer) createDatabaseRef(db *models.Database) *models.Database { DatabaseVersion: db.DatabaseVersion, SourceFormat: db.SourceFormat, Schemas: nil, // Don't include schemas to avoid circular reference + GUID: db.GUID, } } @@ -402,5 +403,6 @@ func (w *Writer) createSchemaRef(schema *models.Schema, db *models.Database) *mo Sequence: schema.Sequence, RefDatabase: w.createDatabaseRef(db), // Include database ref Tables: nil, // Don't include tables to avoid circular reference + GUID: schema.GUID, } } diff --git a/pkg/writers/dctx/writer.go b/pkg/writers/dctx/writer.go index c575406..7ae0368 100644 --- a/pkg/writers/dctx/writer.go +++ b/pkg/writers/dctx/writer.go @@ -133,7 +133,11 @@ func (w *Writer) mapTableFields(table *models.Table) models.DCTXTable { prefix = table.Name[:3] } - tableGuid := w.newGUID() + // Use GUID from model if available, otherwise generate a new one + tableGuid := table.GUID + if tableGuid == "" { + tableGuid = w.newGUID() + } w.tableGuidMap[table.Name] = tableGuid dctxTable := models.DCTXTable{ @@ -171,7 +175,11 @@ func (w *Writer) mapTableKeys(table *models.Table) []models.DCTXKey { } func (w *Writer) mapField(column *models.Column) models.DCTXField { - guid := w.newGUID() + // Use GUID from model if available, otherwise generate a new one + guid := column.GUID + if guid == "" { + guid = w.newGUID() + } fieldKey := fmt.Sprintf("%s.%s", column.Table, column.Name) w.fieldGuidMap[fieldKey] = guid @@ -209,7 +217,11 @@ func (w *Writer) mapDataType(dataType string) string { } func (w *Writer) mapKey(index *models.Index, table *models.Table) models.DCTXKey { - guid := w.newGUID() + // Use GUID from model if available, otherwise generate a new one + guid := index.GUID + if guid == "" { + guid = w.newGUID() + } keyKey := fmt.Sprintf("%s.%s", table.Name, index.Name) w.keyGuidMap[keyKey] = guid @@ -344,7 +356,7 @@ func (w *Writer) mapRelation(rel *models.Relationship, schema *models.Schema) mo } return models.DCTXRelation{ - Guid: w.newGUID(), + Guid: rel.GUID, // Use GUID from relationship model PrimaryTable: w.tableGuidMap[rel.ToTable], // GUID of the 'to' table (e.g., users) ForeignTable: w.tableGuidMap[rel.FromTable], // GUID of the 'from' table (e.g., posts) PrimaryKey: primaryKeyGUID, diff --git a/pkg/writers/gorm/writer.go b/pkg/writers/gorm/writer.go index 7d0a86a..3ac36ef 100644 --- a/pkg/writers/gorm/writer.go +++ b/pkg/writers/gorm/writer.go @@ -380,6 +380,7 @@ func (w *Writer) createDatabaseRef(db *models.Database) *models.Database { DatabaseVersion: db.DatabaseVersion, SourceFormat: db.SourceFormat, Schemas: nil, // Don't include schemas to avoid circular reference + GUID: db.GUID, } } @@ -396,5 +397,6 @@ func (w *Writer) createSchemaRef(schema *models.Schema, db *models.Database) *mo Sequence: schema.Sequence, RefDatabase: w.createDatabaseRef(db), // Include database ref Tables: nil, // Don't include tables to avoid circular reference + GUID: schema.GUID, } }