package mysql_test import ( "database/sql" "fmt" "git.warky.dev/wdevs/relspecgo/pkg/models" driver "github.com/go-sql-driver/mysql" "os" "path/filepath" "strings" "testing" "time" "git.warky.dev/wdevs/relspecgo/pkg/readers" rmysql "git.warky.dev/wdevs/relspecgo/pkg/readers/mysql" "git.warky.dev/wdevs/relspecgo/pkg/writers" wmysql "git.warky.dev/wdevs/relspecgo/pkg/writers/mysql" ) func TestLiveUnsupportedObjectsDiagnosed(t *testing.T) { conn := os.Getenv("RELSPEC_TEST_MYSQL_CONN") if conn == "" { t.Skip("RELSPEC_TEST_MYSQL_CONN is not set") } db, err := sql.Open("mysql", conn) if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) prefix := fmt.Sprintf("unsupported_%d", time.Now().UnixNano()) table, view, trigger := prefix+"_table", prefix+"_view", prefix+"_trigger" t.Cleanup(func() { for _, stmt := range []string{"DROP VIEW IF EXISTS " + view, "DROP TRIGGER IF EXISTS " + trigger, "DROP TABLE IF EXISTS " + table} { if _, err := db.Exec(stmt); err != nil { t.Error(err) } } }) for _, stmt := range []string{ "CREATE TABLE " + table + " (id INT PRIMARY KEY) PARTITION BY HASH(id) PARTITIONS 2", "CREATE VIEW " + view + " AS SELECT id FROM " + table, "CREATE TRIGGER " + trigger + " BEFORE INSERT ON " + table + " FOR EACH ROW SET NEW.id = NEW.id + 1", } { if _, err := db.Exec(stmt); err != nil { t.Fatalf("%s: %v", stmt, err) } } model, err := rmysql.NewReader(&readers.ReaderOptions{ConnectionString: conn}).ReadDatabase() if err != nil { t.Fatal(err) } path := filepath.Join(t.TempDir(), "schema.sql") if err := os.WriteFile(path, []byte("original"), 0600); err != nil { t.Fatal(err) } err = wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true, OutputPath: path}).WriteDatabase(model) for _, want := range []string{"partitioned table", view, trigger} { if err == nil || !strings.Contains(err.Error(), want) { t.Errorf("missing diagnostic %s: %v", want, err) } } bytes, readErr := os.ReadFile(path) if readErr != nil || string(bytes) != "original" { t.Fatalf("strict failure touched file: %s %v", bytes, readErr) } // Schema-filtered/library writes retain discovery diagnostics as well. err = wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true}).WriteSchema(model.Schemas[0]) if err == nil || !strings.Contains(err.Error(), trigger) { t.Fatalf("filtered schema lost diagnostics: %v", err) } var warnings []string _, err = wmysql.NewWriter(&writers.WriterOptions{Warning: func(s string) { warnings = append(warnings, s) }}).Statements(model) if err != nil { t.Fatal(err) } for _, want := range []string{view, trigger, table} { found := false for _, warning := range warnings { if strings.Contains(warning, want) { found = true } } if !found { t.Errorf("no warning for %s: %v", want, warnings) } } } func TestLiveUnsupportedSQLModeFailsBeforeDDL(t *testing.T) { conn := os.Getenv("RELSPEC_TEST_MYSQL_CONN") if conn == "" { t.Skip("RELSPEC_TEST_MYSQL_CONN is not set") } for _, mode := range []string{"NO_BACKSLASH_ESCAPES", "ANSI_QUOTES"} { t.Run(mode, func(t *testing.T) { cfg, err := driver.ParseDSN(conn) if err != nil { t.Fatal(err) } if cfg.Params == nil { cfg.Params = map[string]string{} } cfg.Params["sql_mode"] = "'" + mode + "'" modeConn := cfg.FormatDSN() model, err := rmysql.NewReader(&readers.ReaderOptions{ConnectionString: modeConn}).ReadDatabase() if err != nil { t.Fatal(err) } if _, err := wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true}).Statements(model); err == nil || !strings.Contains(err.Error(), mode) { t.Fatalf("source mode not diagnosed: %v", err) } db, err := sql.Open("mysql", conn) if err != nil { t.Fatal(err) } defer db.Close() name := fmt.Sprintf("mode_%d", time.Now().UnixNano()) defer db.Exec("DROP TABLE IF EXISTS " + name) target := models.InitDatabase(cfg.DBName) s := models.InitSchema(cfg.DBName) target.Schemas = append(target.Schemas, s) table := models.InitTable(name, s.Name) s.Tables = append(s.Tables, table) c := models.InitColumn("id", name, s.Name) c.Type = "int" table.Columns[c.Name] = c err = wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true, Metadata: map[string]any{"connection_string": modeConn}}).WriteDatabase(target) if err == nil || !strings.Contains(err.Error(), mode) { t.Fatalf("target mode not rejected: %v", err) } var count int if err := db.QueryRow("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?", cfg.DBName, name).Scan(&count); err != nil { t.Fatal(err) } if count != 0 { t.Fatal("mode rejection occurred after DDL") } }) } }