fix: preserve MySQL schema fidelity
This commit is contained in:
+20
-4
@@ -181,12 +181,12 @@ Examples:
|
||||
}
|
||||
|
||||
func init() {
|
||||
convertCmd.Flags().StringVar(&convertSourceType, "from", "", "Source format (dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql, sqlite)")
|
||||
convertCmd.Flags().StringVar(&convertSourceType, "from", "", "Source format (dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql, mssql, mysql, sqlite)")
|
||||
convertCmd.Flags().StringVar(&convertSourcePath, "from-path", "", "Source file path (for file-based formats)")
|
||||
convertCmd.Flags().StringVar(&convertSourceConn, "from-conn", "", "Source connection string (for pgsql) or file path (for sqlite)")
|
||||
convertCmd.Flags().StringVar(&convertSourceConn, "from-conn", "", "Source connection string (pgsql, mssql, mysql) or file path (sqlite)")
|
||||
convertCmd.Flags().StringSliceVar(&convertFromList, "from-list", nil, "Comma-separated list of source file paths to read and merge (mutually exclusive with --from-path)")
|
||||
|
||||
convertCmd.Flags().StringVar(&convertTargetType, "to", "", "Target format (dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql)")
|
||||
convertCmd.Flags().StringVar(&convertTargetType, "to", "", "Target format (dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql, mssql, mysql, sqlite)")
|
||||
convertCmd.Flags().StringVar(&convertTargetPath, "to-path", "", "Target output path (file or directory)")
|
||||
convertCmd.Flags().StringVar(&convertPackageName, "package", "", "Package name (for code generation formats like gorm/bun)")
|
||||
convertCmd.Flags().StringVar(&convertSchemaFilter, "schema", "", "Filter to a specific schema by name (required for formats like dctx that only support single schemas)")
|
||||
@@ -449,7 +449,7 @@ func readDatabaseForConvert(dbType, filePath, connString string) (*models.Databa
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaFilter string, flattenSchema bool, nullableTypes, nullableArrays string, continueOnError bool, extraFields string) error {
|
||||
func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaFilter string, flattenSchema bool, nullableTypes, nullableArrays string, continueOnError bool, extraFields string, strict ...bool) error {
|
||||
if lowercaseIdentifiers {
|
||||
var err error
|
||||
schemaFilter = strings.ToLower(schemaFilter)
|
||||
@@ -462,6 +462,9 @@ func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaF
|
||||
var writer writers.Writer
|
||||
|
||||
writerOpts := newWriterOptions(outputPath, packageName, flattenSchema, nullableTypes, nullableArrays, continueOnError)
|
||||
if len(strict) > 0 {
|
||||
writerOpts.StrictConversion = writerOpts.StrictConversion || strict[0]
|
||||
}
|
||||
if extraFields != "" {
|
||||
extraFieldsJSON, err := loadExtraFields(dbType, extraFields)
|
||||
if err != nil {
|
||||
@@ -611,6 +614,19 @@ func validateWriteTarget(db *models.Database, dbType, packageName, schemaFilter,
|
||||
"pgsql", "postgres", "postgresql", "sql",
|
||||
"mssql", "sqlserver", "mssql2016", "mssql2017", "mssql2019", "mssql2022",
|
||||
"sqlite", "sqlite3", "prisma", "typeorm", "graphql", "gql":
|
||||
case "mysql", "mariadb":
|
||||
selected := db
|
||||
if schemaFilter != "" {
|
||||
selected = &models.Database{}
|
||||
for _, s := range db.Schemas {
|
||||
if s.Name == schemaFilter {
|
||||
selected.Schemas = append(selected.Schemas, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := wmysql.NewWriter(newWriterOptions("", packageName, false, "", "", false)).Statements(selected); err != nil {
|
||||
return err
|
||||
}
|
||||
case "gorm", "bun":
|
||||
if packageName == "" {
|
||||
return fmt.Errorf("package name is required for %s format (use --package flag)", dbType)
|
||||
|
||||
+1
-1
@@ -874,7 +874,7 @@ func writeJobOutput(rj *resolvedJob, db *models.Database, lg *jobLogger) error {
|
||||
lg.logf("writing output: %s (%s)", rj.outputPath, format)
|
||||
|
||||
write := func(target string) error {
|
||||
return writeDatabase(db, format, target, o.Package, o.Schema, o.FlattenSchema, o.Types, o.ArrayNullable, o.ContinueOnError, "")
|
||||
return writeDatabase(db, format, target, o.Package, o.Schema, o.FlattenSchema, o.Types, o.ArrayNullable, o.ContinueOnError, "", o.StrictConversion)
|
||||
}
|
||||
// Single-file formats are written to a temp file and renamed into place so
|
||||
// a failure never leaves a partial or truncated output. Directory-emitting
|
||||
|
||||
@@ -32,5 +32,6 @@ func newWriterOptions(outputPath, packageName string, flattenSchema bool, nullab
|
||||
Prisma7: prisma7,
|
||||
ContinueOnError: continueOnError,
|
||||
StrictDirectives: strictDirectives,
|
||||
StrictConversion: strictConversion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ var (
|
||||
noVersion bool
|
||||
silent bool
|
||||
strictDirectives bool
|
||||
strictConversion bool
|
||||
lowercaseIdentifiers bool
|
||||
typeMapFlags []string
|
||||
typeMappings map[string]string
|
||||
@@ -62,6 +63,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringArrayVar(&typeMapFlags, "type-map", nil, "Override a SQL-to-Go type mapping for bun/gorm output as sqltype=gotype (repeatable), e.g. --type-map uuid=uuid.UUID --type-map numeric=decimal.Decimal")
|
||||
rootCmd.PersistentFlags().BoolVar(&noVersion, "no-version", false, "Suppress the RelSpec version header")
|
||||
rootCmd.PersistentFlags().BoolVar(&silent, "silent", false, "Suppress progress and status messages (errors are still shown)")
|
||||
rootCmd.PersistentFlags().BoolVar(&strictConversion, "strict-conversion", false, "Reject known lossy MySQL schema conversions before writing or executing output")
|
||||
rootCmd.PersistentFlags().BoolVar(&strictDirectives, "strict-directives", false, "Fail on unknown or untranslatable DBML dialect directives (@postgres:, @sqlite:, …)")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/jobs"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
func TestStrictConversionOptionAndDryRun(t *testing.T) {
|
||||
prior := strictConversion
|
||||
strictConversion = true
|
||||
t.Cleanup(func() { strictConversion = prior })
|
||||
if flag := rootCmd.PersistentFlags().Lookup("strict-conversion"); flag == nil {
|
||||
t.Fatal("missing --strict-conversion")
|
||||
}
|
||||
db := models.InitDatabase("shop")
|
||||
s := models.InitSchema("shop")
|
||||
table := models.InitTable("users", "shop")
|
||||
col := models.InitColumn("value", "users", "shop")
|
||||
col.Type = "unknown_type"
|
||||
table.Columns[col.Name] = col
|
||||
s.Tables = append(s.Tables, table)
|
||||
db.Schemas = append(db.Schemas, s)
|
||||
if err := validateWriteTarget(db, "mysql", "", "", ""); err == nil || !strings.Contains(err.Error(), "lossy MySQL") {
|
||||
t.Fatalf("dry run bypassed strict validation: %v", err)
|
||||
}
|
||||
strictConversion = false
|
||||
out := filepath.Join(t.TempDir(), "out.sql")
|
||||
rj := &resolvedJob{job: &jobs.Job{Output: &jobs.Output{Format: "mysql"}, Options: jobs.Options{StrictConversion: true}}, outputPath: out}
|
||||
if err := writeJobOutput(rj, db, &jobLogger{file: io.Discard}); err == nil || !strings.Contains(err.Error(), "lossy MySQL") {
|
||||
t.Fatalf("job option bypassed strict validation: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(out); !os.IsNotExist(err) {
|
||||
t.Fatalf("strict job created output: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user