fix: writer/reader correctness and determinism issues

- merge: cloneTable keeps relationships; skip-tables applies to new schemas
- diff: detect schema description/owner changes
- prisma: reader no longer turns relation fields into columns, enum
  detection via declared names; writer type mapping is ordered
- typeorm writer: keep explicit SQL types that cannot be inferred
- drizzle: enum columns call the enum constant; reader resolves them
- mysql/mssql/sqlite writers: honour OutputPath, deterministic column
  and constraint order; mssql live execute covers full schema
- template: ToYAML recovers from panics; Merge nil-pointer loop
- regenerate drizzle fixtures
This commit is contained in:
2026-10-03 21:33:59 +02:00
parent 08e1417393
commit a32647ee16
15 changed files with 301 additions and 180 deletions
+49 -16
View File
@@ -29,18 +29,11 @@ func (w *Writer) WriteDatabase(db *models.Database) error {
if conn, ok := w.options.Metadata["connection_string"].(string); ok && conn != "" {
return w.execute(db, conn)
}
if w.writer == nil {
if w.options.OutputPath != "" {
f, err := os.Create(w.options.OutputPath)
if err != nil {
return err
}
defer f.Close()
w.writer = f
} else {
w.writer = os.Stdout
}
release, err := w.openOutput()
if err != nil {
return err
}
defer release()
return w.writeDatabaseDDL(db)
}
@@ -56,7 +49,33 @@ func (w *Writer) writeDatabaseDDL(db *models.Database) error {
return nil
}
// openOutput points w.writer at the configured destination (output file or
// stdout) when none is set, and returns a func that releases it again.
func (w *Writer) openOutput() (func(), error) {
if w.writer != nil {
return func() {}, nil
}
if w.options != nil && w.options.OutputPath != "" {
f, err := os.Create(w.options.OutputPath)
if err != nil {
return nil, err
}
w.writer = f
return func() {
f.Close()
w.writer = nil
}, nil
}
w.writer = os.Stdout
return func() { w.writer = nil }, nil
}
func (w *Writer) WriteSchema(s *models.Schema) error {
release, err := w.openOutput()
if err != nil {
return err
}
defer release()
for _, t := range s.Tables {
if err := w.writeTable(s, t); err != nil {
return err
@@ -66,9 +85,11 @@ func (w *Writer) WriteSchema(s *models.Schema) error {
}
func (w *Writer) WriteTable(t *models.Table) error {
if w.writer == nil {
w.writer = os.Stdout
release, err := w.openOutput()
if err != nil {
return err
}
defer release()
return w.writeTable(nil, t)
}
@@ -83,7 +104,12 @@ func (w *Writer) writeTable(s *models.Schema, t *models.Table) error {
for _, c := range t.Columns {
cols = append(cols, c)
}
sort.Slice(cols, func(i, j int) bool { return cols[i].Sequence < cols[j].Sequence })
sort.Slice(cols, func(i, j int) bool {
if cols[i].Sequence != cols[j].Sequence {
return cols[i].Sequence < cols[j].Sequence
}
return cols[i].Name < cols[j].Name
})
defs := []string{}
pk := []string{}
for _, c := range cols {
@@ -105,7 +131,13 @@ func (w *Writer) writeTable(s *models.Schema, t *models.Table) error {
pk = append(pk, quote(c.Name))
}
}
for _, c := range t.Constraints {
constraintNames := make([]string, 0, len(t.Constraints))
for name := range t.Constraints {
constraintNames = append(constraintNames, name)
}
sort.Strings(constraintNames)
for _, name := range constraintNames {
c := t.Constraints[name]
if c.Type == models.PrimaryKeyConstraint {
pk = nil
for _, n := range c.Columns {
@@ -117,7 +149,8 @@ func (w *Writer) writeTable(s *models.Schema, t *models.Table) error {
if len(pk) > 0 {
defs = append(defs, " PRIMARY KEY ("+strings.Join(pk, ", ")+")")
}
for _, c := range t.Constraints {
for _, name := range constraintNames {
c := t.Constraints[name]
if c.Type == models.UniqueConstraint {
defs = append(defs, fmt.Sprintf(" CONSTRAINT %s UNIQUE (%s)", quote(c.Name), quoted(c.Columns)))
}