146 lines
4.9 KiB
Go
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])
|
|
}
|
|
}
|