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, "_", " ") }