fix(migrations): support MySQL 8.4 checks (#140)

This commit is contained in:
QiuSW
2026-08-29 08:47:06 +08:00
parent 9d23b44811
commit a6cc1c6d5d
2 changed files with 68 additions and 8 deletions
+25 -8
View File
@@ -1,11 +1,26 @@
package migrations
import (
"strings"
"go-admin/app/goauto/models"
"gorm.io/gorm"
)
const mysqlCheckConstraintQuery = `
SELECT cc.check_clause
FROM information_schema.table_constraints AS tc
JOIN information_schema.check_constraints AS cc
ON cc.constraint_catalog = tc.constraint_catalog
AND cc.constraint_schema = tc.constraint_schema
AND cc.constraint_name = tc.constraint_name
WHERE tc.constraint_schema = DATABASE()
AND tc.table_name = ?
AND tc.constraint_type = 'CHECK'
AND tc.constraint_name = ?
LIMIT 1`
// Migrate creates the complete GoAuto MVP schema. AutoMigrate is intentionally
// kept behind this package so the CLI migration and tests use the same model set.
// MigratedModels is the single source of truth for which models make up the
@@ -68,21 +83,23 @@ func ensureMySQLDirectSelectConstraint(db *gorm.DB) error {
return nil
}
const name = "ck_purchase_task_spec_source"
var total, current int64
base := `SELECT COUNT(*) FROM information_schema.check_constraints WHERE constraint_schema = DATABASE() AND table_name = 'purchase_task' AND constraint_name = ?`
if err := db.Raw(base, name).Scan(&total).Error; err != nil {
var constraints []struct {
CheckClause string `gorm:"column:check_clause"`
}
if err := db.Raw(mysqlCheckConstraintQuery, "purchase_task", name).Scan(&constraints).Error; err != nil {
return err
}
if err := db.Raw(base+` AND check_clause LIKE '%direct_select%'`, name).Scan(&current).Error; err != nil {
return err
}
if current > 0 {
if len(constraints) > 0 && checkConstraintAllowsDirectSelect(constraints[0].CheckClause) {
return nil
}
if total > 0 {
if len(constraints) > 0 {
if err := db.Exec("ALTER TABLE purchase_task DROP CHECK " + name).Error; err != nil {
return err
}
}
return db.Exec("ALTER TABLE purchase_task ADD CONSTRAINT " + name + " CHECK (spec_source IN ('unresolved','manual_mapping','exact_match','ai_match','direct_select'))").Error
}
func checkConstraintAllowsDirectSelect(clause string) bool {
return strings.Contains(strings.ToLower(clause), "direct_select")
}
@@ -0,0 +1,43 @@
package migrations
import (
"strings"
"testing"
)
func TestMySQLCheckConstraintQueryUsesTableConstraintsForTableName(t *testing.T) {
query := strings.ToLower(mysqlCheckConstraintQuery)
for _, required := range []string{
"from information_schema.table_constraints as tc",
"join information_schema.check_constraints as cc",
"tc.table_name = ?",
"tc.constraint_type = 'check'",
"cc.constraint_schema = tc.constraint_schema",
"cc.constraint_name = tc.constraint_name",
} {
if !strings.Contains(query, required) {
t.Fatalf("metadata query missing %q: %s", required, mysqlCheckConstraintQuery)
}
}
if strings.Contains(query, "cc.table_name") {
t.Fatalf("MySQL 8.4 CHECK_CONSTRAINTS has no TABLE_NAME column: %s", mysqlCheckConstraintQuery)
}
}
func TestCheckConstraintAllowsDirectSelect(t *testing.T) {
for _, test := range []struct {
name string
clause string
want bool
}{
{name: "legacy constraint", clause: "spec_source in ('unresolved','manual_mapping','exact_match','ai_match')", want: false},
{name: "upgraded constraint", clause: "spec_source in ('unresolved','manual_mapping','exact_match','ai_match','direct_select')", want: true},
{name: "mysql charset prefix", clause: "(`spec_source` in (_utf8mb4'DIRECT_SELECT',_utf8mb4'unresolved'))", want: true},
} {
t.Run(test.name, func(t *testing.T) {
if got := checkConstraintAllowsDirectSelect(test.clause); got != test.want {
t.Fatalf("checkConstraintAllowsDirectSelect(%q) = %v, want %v", test.clause, got, test.want)
}
})
}
}