Files
fm_be/internal/repository/mysql/delete_guard.go
2026-07-16 22:16:45 +07:00

107 lines
2.8 KiB
Go

package mysql
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
mysqldriver "github.com/go-sql-driver/mysql"
"gorm.io/gorm"
"wucher/internal/shared/pkg/apperrors"
)
type fkReferenceRow struct {
TableName string `gorm:"column:table_name"`
ColumnName string `gorm:"column:column_name"`
}
var mysqlDeleteRefTableRegexp = regexp.MustCompile("fails \\(`[^`]+`\\.`([^`]+)`")
func ensureNoReferenceBeforeDelete(ctx context.Context, db *gorm.DB, tableName string, id []byte) error {
if db == nil || db.Dialector == nil || db.Dialector.Name() != "mysql" {
return nil
}
if len(id) != 16 || strings.TrimSpace(tableName) == "" {
return nil
}
refs := make([]fkReferenceRow, 0)
err := db.WithContext(ctx).Raw(`
SELECT kcu.TABLE_NAME AS table_name, kcu.COLUMN_NAME AS column_name
FROM information_schema.KEY_COLUMN_USAGE kcu
WHERE kcu.TABLE_SCHEMA = DATABASE()
AND kcu.REFERENCED_TABLE_SCHEMA = DATABASE()
AND kcu.REFERENCED_TABLE_NAME = ?
AND kcu.REFERENCED_COLUMN_NAME = 'id'
`, tableName).Scan(&refs).Error
if err != nil {
return err
}
related := make([]string, 0)
for i := range refs {
query := fmt.Sprintf("SELECT 1 FROM `%s` WHERE `%s` = ? LIMIT 1", refs[i].TableName, refs[i].ColumnName)
var one int
rowErr := db.WithContext(ctx).Raw(query, id).Scan(&one).Error
if rowErr != nil {
return rowErr
}
if one == 1 {
related = append(related, moduleNameFromTable(refs[i].TableName))
}
}
if len(related) > 0 {
return apperrors.NewDeleteConflictError(related...)
}
return nil
}
func mapDeleteConstraintError(err error) error {
if err == nil {
return nil
}
if apperrors.IsDeleteConflict(err) {
return err
}
var mysqlErr *mysqldriver.MySQLError
if !strings.Contains(strings.ToLower(err.Error()), "foreign key") && !strings.Contains(strings.ToLower(err.Error()), "referenced") && !strings.Contains(strings.ToLower(err.Error()), "constraint") && !strings.Contains(strings.ToLower(err.Error()), "delete") {
return err
}
if !asMySQLError(err, &mysqlErr) || mysqlErr.Number != 1451 {
return err
}
if matches := mysqlDeleteRefTableRegexp.FindStringSubmatch(mysqlErr.Message); len(matches) > 1 {
return apperrors.NewDeleteConflictError(moduleNameFromTable(matches[1]))
}
return apperrors.NewDeleteConflictError()
}
func asMySQLError(err error, target **mysqldriver.MySQLError) bool {
if err == nil {
return false
}
return errors.As(err, target)
}
func moduleNameFromTable(tableName string) string {
tableName = strings.TrimSpace(tableName)
if tableName == "" {
return "another module"
}
switch tableName {
case "takeover_acs":
return "takeover"
case "base_operational_shift_times":
return "base operational shift times"
case "base_contact_roles":
return "base contact roles"
case "duls":
return "dul"
}
return strings.ReplaceAll(tableName, "_", " ")
}