Files
relspecgo/pkg/writers/mysql/conversion_test.go
T

146 lines
4.9 KiB
Go

package mysql
import (
"os"
"path/filepath"
"strings"
"testing"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/transform"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
)
func TestStrictConversionPreflightsAllObjects(t *testing.T) {
db := shopDB()
unsupported := models.InitTable("unsupported", "shop")
col := models.InitColumn("opaque", "unsupported", "shop")
col.Type = "unknown_type"
unsupported.Columns[col.Name] = col
index := models.InitIndex("partial", "unsupported", "shop")
index.Columns = []string{col.Name}
index.Where = "opaque IS NOT NULL"
index.Include = []string{"other"}
unsupported.Indexes[index.Name] = index
db.Schemas[0].Tables = append(db.Schemas[0].Tables, unsupported)
out := filepath.Join(t.TempDir(), "existing.sql")
if err := os.WriteFile(out, []byte("keep existing contents"), 0600); err != nil {
t.Fatal(err)
}
opts := &writers.WriterOptions{StrictConversion: true, OutputPath: out}
err := NewWriter(opts).WriteDatabase(db)
for _, want := range []string{"lossy MySQL conversion rejected", "unknown type", "partial predicate", "INCLUDE"} {
if err == nil || !strings.Contains(err.Error(), want) {
t.Fatalf("expected %s: %v", want, err)
}
}
bytes, err := os.ReadFile(out)
if err != nil || string(bytes) != "keep existing contents" {
t.Fatalf("destination changed: %q %v", bytes, err)
}
opts.Metadata = map[string]any{"connection_string": "invalid DSN"}
if err := NewWriter(opts).WriteDatabase(db); err == nil || !strings.Contains(err.Error(), "lossy MySQL") {
t.Fatalf("connected before strict validation: %v", err)
}
opts.StrictConversion = false
opts.Metadata = nil
var warnings []string
opts.Warning = func(s string) { warnings = append(warnings, s) }
if err := NewWriter(opts).WriteDatabase(db); err != nil {
t.Fatal(err)
}
if len(warnings) != 3 {
t.Fatalf("warnings: %v", warnings)
}
}
func TestUnsupportedModelFeaturesDiagnosed(t *testing.T) {
db := shopDB()
s := db.Schemas[0]
table := s.Tables[0]
s.Metadata["mysql_unsupported"] = []any{"trigger shop.audit", "partitioned table shop.archive"}
c := table.Columns["id"]
c.Type = "timestamptz"
c.Identity = true
c.Metadata["mysql_srs_id"] = 4326
check := models.InitConstraint("deferred", models.CheckConstraint)
check.Expression = "id > 0"
yes := true
check.Deferrable = &yes
table.Constraints[check.Name] = check
i := models.InitIndex("gin", "users", "shop")
i.Type = "gin"
i.Columns = []string{"id"}
table.Indexes[i.Name] = i
_, err := NewWriter(&writers.WriterOptions{StrictConversion: true}).Statements(db)
for _, want := range []string{"trigger shop.audit", "partitioned table", "time zone", "identity", "mysql_srs_id", "deferrable", "unsupported index type gin"} {
if err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("missing diagnostic %q: %v", want, err)
}
}
}
func TestCanonicalDimensionsAndNativeEdit(t *testing.T) {
db := shopDB()
c := db.Schemas[0].Tables[0].Columns["age"]
c.Type = "decimal"
c.Precision = 12
c.Scale = 4
if got := columnType(c); got != "DECIMAL(12,4)" {
t.Fatal(got)
}
c.Type = "uint64"
c.Precision = 0
c.Scale = 0
if got := columnType(c); got != "BIGINT UNSIGNED" {
t.Fatal(got)
}
c.Metadata["mysql_type"] = "bigint unsigned"
c.Metadata["mysql_type_signature"] = signature(c)
c.Type = "int64"
_, err := NewWriter(&writers.WriterOptions{StrictConversion: true}).Statements(db)
if err == nil || !strings.Contains(err.Error(), "override native declaration") {
t.Fatalf("native metadata silently overrode edit: %v", err)
}
if got := columnType(c); got != "BIGINT" {
t.Fatal(got)
}
}
func TestLowercaseIndexPartsPreserveSQL(t *testing.T) {
db := shopDB()
table := db.Schemas[0].Tables[0]
c := models.InitColumn("Mixed", "users", "shop")
c.Type = "string"
table.Columns[c.Name] = c
i := models.InitIndex("MixedIndex", "users", "shop")
i.KeyParts = []models.IndexPart{{Name: "Mixed", Length: 8, Descending: true}, {Expression: "lower(`Mixed`)"}}
table.Indexes[i.Name] = i
model, err := transform.LowercaseIdentifiers(db)
if err != nil {
t.Fatal(err)
}
parts := model.Schemas[0].Tables[0].Indexes["mixedindex"].KeyParts
if parts[0].Name != "mixed" || parts[1].Expression != "lower(`Mixed`)" || i.KeyParts[0].Name != "Mixed" {
t.Fatalf("lowercase parts: %+v", parts)
}
}
func TestLowercasePrimaryIndexIsNotDuplicated(t *testing.T) {
db := shopDB()
table := db.Schemas[0].Tables[0]
i := models.InitIndex("PRIMARY", table.Name, table.Schema)
i.Unique = true
i.Type = "btree"
i.Columns = []string{"id"}
i.KeyParts = []models.IndexPart{{Name: "id", Descending: true}}
table.Indexes[i.Name] = i
statements, err := NewWriter(&writers.WriterOptions{StrictConversion: true, LowercaseIdentifiers: true}).Statements(db)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(statements[0], "PRIMARY KEY (`id` DESC)") || strings.Contains(statements[0], "KEY `primary`") {
t.Fatalf("primary key duplicated or attributes lost: %s", statements[0])
}
}