Files
relspecgo/pkg/writers/mysql/live_diagnostics_test.go
T
warkanum 1d93255575 fix(assets): improve error reporting for asset failures
fix(embed): update embedDirectives to return result directly

fix(executor): clean up import order and formatting

fix(syntax_test): change file permission syntax to octal

fix(mysql): update file permission syntax to octal

fix(pgsql): update file permission syntax to octal

fix(sqlex): simplify Walk function parameter handling

fix(transform): adjust identifier comparison logic

fix(template): clean up function formatting and error handling
2026-10-12 22:04:40 +02:00

143 lines
4.7 KiB
Go

package mysql_test
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
driver "github.com/go-sql-driver/mysql"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"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"), 0o600); 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")
}
})
}
}