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
+18 -14
View File
@@ -13,7 +13,8 @@ import (
// Reader implements the readers.Reader interface for Prisma schema format
type Reader struct {
options *readers.ReaderOptions
options *readers.ReaderOptions
enumNames map[string]bool // enum names declared in the schema being parsed
}
// NewReader creates a new Prisma reader with the given options
@@ -82,6 +83,8 @@ func (r *Reader) parsePrisma(content string) (*models.Database, error) {
schema := models.InitSchema("public")
schema.Enums = make([]*models.Enum, 0)
r.enumNames = collectEnumNames(content)
scanner := bufio.NewScanner(strings.NewReader(content))
// State tracking
@@ -600,20 +603,21 @@ func (r *Reader) isPrimitiveType(typeName string) bool {
return false
}
// isEnumType checks if a type name might be an enum
// Note: We can't definitively check against schema.Enums at parse time
// because enums might be defined after the model, so we just check
// if it starts with uppercase (Prisma convention for enums)
func (r *Reader) isEnumType(typeName string, table *models.Table) bool {
// Simple heuristic: enum types start with uppercase letter
// and are not known model names (though we can't check that yet)
if len(typeName) > 0 && typeName[0] >= 'A' && typeName[0] <= 'Z' {
// Additional check: primitive types are already handled above
// So if it's uppercase and not primitive, it's likely an enum or model
// We'll assume it's an enum if it's a single word
return !strings.Contains(typeName, "_")
// isEnumType reports whether typeName is an enum declared in the schema.
// Enum names are collected up front because enums may be declared after the
// models that use them.
func (r *Reader) isEnumType(typeName string, _ *models.Table) bool {
return r.enumNames[typeName]
}
var enumDeclRegex = regexp.MustCompile(`(?m)^\s*enum\s+(\w+)\s*{`)
func collectEnumNames(content string) map[string]bool {
names := make(map[string]bool)
for _, m := range enumDeclRegex.FindAllStringSubmatch(content, -1) {
names[m[1]] = true
}
return false
return names
}
// createConstraintFromRelation creates a FK constraint from a @relation attribute