init push
This commit is contained in:
106
internal/repository/mysql/delete_guard.go
Normal file
106
internal/repository/mysql/delete_guard.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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, "_", " ")
|
||||
}
|
||||
Reference in New Issue
Block a user