refac: migration crash, real-database tests, site deleted during create, ruels out of the repo, lists

This commit is contained in:
Florian Sylvain committed 2026-11-03 05:06:04 +01:00
1 parent 680b9a1308
commit 9d284e65a9
62 files changed
+1858 -987

No files matched your search

@@ -24,6 +24,7 @@ type Driver interface {
Open(source string) (*gorm.DB, error)
NewBackup(db *gorm.DB, directory string, now func() time.Time) domainUpdate.DatabaseBackup
IsLockContention(err error) bool
IsUniqueViolation(err error) bool
}
var drivers = map[string]Driver{
@@ -41,6 +42,15 @@ func IsLockContention(db *gorm.DB, err error) bool {
return IsLockContentionFor(db.Name(), err)
}
func IsUniqueViolationFor(driverName string, err error) bool {
driver, ok := drivers[driverName]
return ok && err != nil && driver.IsUniqueViolation(err)
}
func IsUniqueViolation(db *gorm.DB, err error) bool {
return IsUniqueViolationFor(db.Name(), err)
}
type Connection struct {
DB *gorm.DB
driver Driver
@@ -7,6 +7,9 @@ import (
)
func Migrate(db *gorm.DB, publicURL string) error {
if err := BackfillNullSourceRevisions(db); err != nil {
return err
}
err := tableOptionsFor(db).AutoMigrate(&models.Article{}, &models.Image{}, &models.User{}, &models.Invite{}, &models.APIKey{}, &models.PurgeSettings{}, &models.AuditEvent{}, &models.AuditSettings{}, &models.ImageVariant{}, &models.ImageVariantFile{}, &models.ImageVariantRejectionDay{}, &models.Site{}, &models.Author{}, &models.ArticleAuthor{}, &models.ContentType{}, &models.Entry{}, &models.RevokedSession{}, &models.Role{}, &models.RolePermission{}, &models.MigrationMarker{}, &models.Story{}, &models.SiteLanguage{})
if err != nil {
return err
@@ -28,6 +28,8 @@ const (
mysqlLockWaitTimeout = 1205
mysqlDeadlock = 1213
mysqlSnapshotChanged = 1020
mysqlDuplicateEntry = 1062
mysqlDuplicateKeyed = 1586
)
type mysqlDriver struct{}
@@ -94,6 +96,16 @@ func tableOptionsFor(db *gorm.DB) *gorm.DB {
var mysqlContentionCodes = []uint16{mysqlLockNowait, mysqlLockWaitTimeout, mysqlDeadlock, mysqlSnapshotChanged}
func (mysqlDriver) IsLockContention(err error) bool {
return hasMySQLCode(err, mysqlContentionCodes)
}
var mysqlUniqueViolationCodes = []uint16{mysqlDuplicateEntry, mysqlDuplicateKeyed}
func (mysqlDriver) IsUniqueViolation(err error) bool {
return hasMySQLCode(err, mysqlUniqueViolationCodes)
}
func hasMySQLCode(err error, codes []uint16) bool {
var failure *mysqldriver.MySQLError
return errors.As(err, &failure) && slices.Contains(mysqlContentionCodes, failure.Number)
return errors.As(err, &failure) && slices.Contains(codes, failure.Number)
}
@@ -26,6 +26,7 @@ const (
postgresLockNotAvailable = "55P03"
postgresDeadlockDetected = "40P01"
postgresSerializationFailure = "40001"
postgresUniqueViolation = "23505"
)
var timeType = reflect.TypeOf(time.Time{})
@@ -115,6 +116,14 @@ func (postgresDriver) NewBackup(db *gorm.DB, directory string, now func() time.T
var postgresContentionCodes = []string{postgresLockNotAvailable, postgresDeadlockDetected, postgresSerializationFailure}
func (postgresDriver) IsLockContention(err error) bool {
return hasPostgresCode(err, postgresContentionCodes)
}
func (postgresDriver) IsUniqueViolation(err error) bool {
return hasPostgresCode(err, []string{postgresUniqueViolation})
}
func hasPostgresCode(err error, codes []string) bool {
var failure *pgconn.PgError
return errors.As(err, &failure) && slices.Contains(postgresContentionCodes, failure.Code)
return errors.As(err, &failure) && slices.Contains(codes, failure.Code)
}
@@ -0,0 +1,20 @@
package database
import (
entity "RenewCMS/internal/infrastructure/persistence/models"
"gorm.io/gorm"
)
const (
sourceRevisionColumn = "source_revision"
untranslatedSourceValue = 0
)
func BackfillNullSourceRevisions(db *gorm.DB) error {
migrator := db.Migrator()
if !migrator.HasTable(&entity.Article{}) || !migrator.HasColumn(&entity.Article{}, sourceRevisionColumn) {
return nil
}
return db.Unscoped().Model(&entity.Article{}).Where(sourceRevisionColumn+" IS NULL").UpdateColumn(sourceRevisionColumn, untranslatedSourceValue).Error
}
@@ -19,6 +19,8 @@ const (
sqliteResultBusy = 5
sqliteResultLocked = 6
sqlitePrimaryMask = 0xff
sqliteUniqueKey = 2067
sqlitePrimaryKey = 1555
)
type sqliteDriver struct{}
@@ -39,10 +41,20 @@ func (sqliteDriver) NewBackup(db *gorm.DB, directory string, now func() time.Tim
}
func (sqliteDriver) IsLockContention(err error) bool {
code, ok := sqliteCode(err)
primary := code & sqlitePrimaryMask
return ok && (primary == sqliteResultBusy || primary == sqliteResultLocked)
}
func (sqliteDriver) IsUniqueViolation(err error) bool {
code, ok := sqliteCode(err)
return ok && (code == sqliteUniqueKey || code == sqlitePrimaryKey)
}
func sqliteCode(err error) (int, bool) {
var coded interface{ Code() int }
if !errors.As(err, &coded) {
return false
return 0, false
}
primary := coded.Code() & sqlitePrimaryMask
return primary == sqliteResultBusy || primary == sqliteResultLocked
return coded.Code(), true
}