refac: migration crash, real-database tests, site deleted during create, ruels out of the repo, lists
This commit is contained in:
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
|
||||
}
|
||||
Reference in new issue
Block a user