fix: preserve MySQL schema fidelity
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user