Manager: remove GORM annotations and last dependencies

Remove GORM struct annotations/tags and references to GORM types.

Ref: #104305
This commit is contained in:
Sybren A. Stüvel 2024-09-26 23:03:43 +02:00
parent 816046663e
commit 21cf3c47f9
11 changed files with 71 additions and 114 deletions

View File

@ -29,7 +29,7 @@ type DB struct {
// Soft deletion is not used by Flamenco. If it ever becomes necessary to
// support soft-deletion, see https://gorm.io/docs/delete.html#Soft-Delete
type Model struct {
ID uint `gorm:"primarykey"`
ID uint
CreatedAt time.Time
UpdatedAt time.Time
}
@ -167,8 +167,6 @@ type queriesTX struct {
}
// queries returns the SQLC Queries struct, connected to this database.
// It is intended that all GORM queries will be migrated to use this interface
// instead.
//
// After calling this function, all queries should use this transaction until it
// is closed (either committed or rolled back). Otherwise SQLite will deadlock,

View File

@ -5,16 +5,13 @@ import (
"database/sql"
"errors"
"fmt"
"gorm.io/gorm"
)
var (
// TODO: let these errors wrap database/sql.ErrNoRows.
ErrJobNotFound = PersistenceError{Message: "job not found", Err: gorm.ErrRecordNotFound}
ErrTaskNotFound = PersistenceError{Message: "task not found", Err: gorm.ErrRecordNotFound}
ErrWorkerNotFound = PersistenceError{Message: "worker not found", Err: gorm.ErrRecordNotFound}
ErrWorkerTagNotFound = PersistenceError{Message: "worker tag not found", Err: gorm.ErrRecordNotFound}
ErrJobNotFound = PersistenceError{Message: "job not found", Err: sql.ErrNoRows}
ErrTaskNotFound = PersistenceError{Message: "task not found", Err: sql.ErrNoRows}
ErrWorkerNotFound = PersistenceError{Message: "worker not found", Err: sql.ErrNoRows}
ErrWorkerTagNotFound = PersistenceError{Message: "worker tag not found", Err: sql.ErrNoRows}
ErrDeletingWithoutFK = errors.New("refusing to delete a job when foreign keys are not enabled on the database")
@ -80,9 +77,6 @@ func wrapError(errorToWrap error, message string, format ...interface{}) error {
// This helps to keep Gorm as "implementation detail" of the persistence layer.
func translateGormJobError(err error) error {
if errors.Is(err, sql.ErrNoRows) {
return ErrTaskNotFound
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrJobNotFound
}
return err
@ -94,9 +88,6 @@ func translateGormTaskError(err error) error {
if errors.Is(err, sql.ErrNoRows) {
return ErrTaskNotFound
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrTaskNotFound
}
return err
}
@ -106,9 +97,6 @@ func translateGormWorkerError(err error) error {
if errors.Is(err, sql.ErrNoRows) {
return ErrWorkerNotFound
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrWorkerNotFound
}
return err
}
@ -118,8 +106,5 @@ func translateGormWorkerTagError(err error) error {
if errors.Is(err, sql.ErrNoRows) {
return ErrWorkerTagNotFound
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrWorkerTagNotFound
}
return err
}

View File

@ -2,16 +2,16 @@
package persistence
import (
"database/sql"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)
func TestNotFoundErrors(t *testing.T) {
assert.ErrorIs(t, ErrJobNotFound, gorm.ErrRecordNotFound)
assert.ErrorIs(t, ErrTaskNotFound, gorm.ErrRecordNotFound)
assert.ErrorIs(t, ErrJobNotFound, sql.ErrNoRows)
assert.ErrorIs(t, ErrTaskNotFound, sql.ErrNoRows)
assert.Contains(t, ErrJobNotFound.Error(), "job")
assert.Contains(t, ErrTaskNotFound.Error(), "task")
@ -19,7 +19,7 @@ func TestNotFoundErrors(t *testing.T) {
func TestTranslateGormJobError(t *testing.T) {
assert.Nil(t, translateGormJobError(nil))
assert.Equal(t, ErrJobNotFound, translateGormJobError(gorm.ErrRecordNotFound))
assert.Equal(t, ErrJobNotFound, translateGormJobError(sql.ErrNoRows))
otherError := errors.New("this error is not special for this function")
assert.Equal(t, otherError, translateGormJobError(otherError))
@ -27,7 +27,7 @@ func TestTranslateGormJobError(t *testing.T) {
func TestTranslateGormTaskError(t *testing.T) {
assert.Nil(t, translateGormTaskError(nil))
assert.Equal(t, ErrTaskNotFound, translateGormTaskError(gorm.ErrRecordNotFound))
assert.Equal(t, ErrTaskNotFound, translateGormTaskError(sql.ErrNoRows))
otherError := errors.New("this error is not special for this function")
assert.Equal(t, otherError, translateGormTaskError(otherError))

View File

@ -148,11 +148,6 @@ func (db *DB) pragmaForeignKeyCheck(ctx context.Context) (ok bool) {
// ensureForeignKeysEnabled checks whether foreign keys are enabled, and if not,
// tries to enable them.
//
// This is likely caused by either GORM or its embedded SQLite creating a new
// connection to the low-level SQLite driver. Unfortunately the GORM-embedded
// SQLite doesn't have an 'on-connect' callback function to always enable
// foreign keys.
func (db *DB) ensureForeignKeysEnabled(ctx context.Context) {
fkEnabled, err := db.areForeignKeysEnabled(ctx)

View File

@ -13,7 +13,6 @@ import (
"time"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"projects.blender.org/studio/flamenco/internal/manager/job_compilers"
"projects.blender.org/studio/flamenco/internal/manager/persistence/sqlc"
@ -22,23 +21,23 @@ import (
type Job struct {
Model
UUID string `gorm:"type:char(36);default:'';unique;index"`
UUID string
Name string `gorm:"type:varchar(64);default:''"`
JobType string `gorm:"type:varchar(32);default:''"`
Priority int `gorm:"type:smallint;default:0"`
Status api.JobStatus `gorm:"type:varchar(32);default:''"`
Activity string `gorm:"type:varchar(255);default:''"`
Name string
JobType string
Priority int
Status api.JobStatus
Activity string
Settings StringInterfaceMap `gorm:"type:jsonb"`
Metadata StringStringMap `gorm:"type:jsonb"`
Settings StringInterfaceMap
Metadata StringStringMap
DeleteRequestedAt sql.NullTime
Storage JobStorageInfo `gorm:"embedded;embeddedPrefix:storage_"`
Storage JobStorageInfo
WorkerTagID *uint
WorkerTag *WorkerTag `gorm:"foreignkey:WorkerTagID;references:ID;constraint:OnDelete:SET NULL"`
WorkerTag *WorkerTag
}
type StringInterfaceMap map[string]interface{}
@ -54,43 +53,32 @@ func (j *Job) DeleteRequested() bool {
// files.
type JobStorageInfo struct {
// ShamanCheckoutID is only set when the job was actually using Shaman storage.
ShamanCheckoutID string `gorm:"type:varchar(255);default:''"`
ShamanCheckoutID string
}
type Task struct {
Model
UUID string `gorm:"type:char(36);default:'';unique;index"`
UUID string
Name string `gorm:"type:varchar(64);default:''"`
Type string `gorm:"type:varchar(32);default:''"`
JobID uint `gorm:"default:0"`
Job *Job `gorm:"foreignkey:JobID;references:ID;constraint:OnDelete:CASCADE"`
JobUUID string `gorm:"-"` // Fetched by SQLC, handled by GORM in Task.AfterFind()
Priority int `gorm:"type:smallint;default:50"`
Status api.TaskStatus `gorm:"type:varchar(16);default:''"`
Name string
Type string
JobID uint
Job *Job
JobUUID string // Fetched by SQLC, handled by GORM in Task.AfterFind()
Priority int
Status api.TaskStatus
// Which worker is/was working on this.
WorkerID *uint
Worker *Worker `gorm:"foreignkey:WorkerID;references:ID;constraint:OnDelete:SET NULL"`
WorkerUUID string `gorm:"-"` // Fetched by SQLC, handled by GORM in Task.AfterFind()
LastTouchedAt time.Time `gorm:"index"` // Should contain UTC timestamps.
Worker *Worker
WorkerUUID string // Fetched by SQLC, handled by GORM in Task.AfterFind()
LastTouchedAt time.Time // Should contain UTC timestamps.
// Dependencies are tasks that need to be completed before this one can run.
Dependencies []*Task `gorm:"many2many:task_dependencies;constraint:OnDelete:CASCADE"`
Dependencies []*Task
Commands Commands `gorm:"type:jsonb"`
Activity string `gorm:"type:varchar(255);default:''"`
}
// AfterFind updates the task JobUUID and WorkerUUID fields from its job/worker, if known.
func (t *Task) AfterFind(tx *gorm.DB) error {
if t.JobUUID == "" && t.Job != nil {
t.JobUUID = t.Job.UUID
}
if t.WorkerUUID == "" && t.Worker != nil {
t.WorkerUUID = t.Worker.UUID
}
return nil
Commands Commands
Activity string
}
type Commands []Command
@ -138,10 +126,10 @@ type TaskFailure struct {
// Don't include the standard Gorm ID, UpdatedAt, or DeletedAt fields, as they're useless here.
// Entries will never be updated, and should never be soft-deleted but just purged from existence.
CreatedAt time.Time
TaskID uint `gorm:"primaryKey;autoIncrement:false"`
Task *Task `gorm:"foreignkey:TaskID;references:ID;constraint:OnDelete:CASCADE"`
WorkerID uint `gorm:"primaryKey;autoIncrement:false"`
Worker *Worker `gorm:"foreignkey:WorkerID;references:ID;constraint:OnDelete:CASCADE"`
TaskID uint
Task *Task
WorkerID uint
Worker *Worker
}
// StoreJob stores an AuthoredJob and its tasks, and saves it to the database.

View File

@ -17,13 +17,13 @@ type JobBlock struct {
ID uint
CreatedAt time.Time
JobID uint `gorm:"default:0;uniqueIndex:job_worker_tasktype"`
Job *Job `gorm:"foreignkey:JobID;references:ID;constraint:OnDelete:CASCADE"`
JobID uint
Job *Job
WorkerID uint `gorm:"default:0;uniqueIndex:job_worker_tasktype"`
Worker *Worker `gorm:"foreignkey:WorkerID;references:ID;constraint:OnDelete:CASCADE"`
WorkerID uint
Worker *Worker
TaskType string `gorm:"uniqueIndex:job_worker_tasktype"`
TaskType string
}
// AddWorkerToJobBlocklist prevents this Worker of getting any task, of this type, on this job, from the task scheduler.

View File

@ -15,8 +15,8 @@ import (
// This is used to show the global last-rendered image in the web interface.
type LastRendered struct {
Model
JobID uint `gorm:"default:0"`
Job *Job `gorm:"foreignkey:JobID;references:ID;constraint:OnDelete:CASCADE"`
JobID uint
Job *Job
}
// SetLastRendered sets this job as the one with the most recent rendered image.

View File

@ -26,14 +26,6 @@ func isDatabaseBusyError(err error) bool {
if err == nil {
return false
}
// The exact error type is dependent on deep dependencies of GORM. The code
// below used to work, until an upgrade of one of those dependencies. This is
// why I feel it's more future-proof to just check for SQLITE_BUSY in the
// error text.
//
// sqlErr, ok := err.(*sqlite.Error)
// return ok && sqlErr.Code() == sqlite_lib.SQLITE_BUSY
return strings.Contains(err.Error(), "SQLITE_BUSY")
}

View File

@ -18,16 +18,16 @@ import (
type SleepSchedule struct {
Model
WorkerID uint `gorm:"default:0;unique;index"`
Worker *Worker `gorm:"foreignkey:WorkerID;references:ID;constraint:OnDelete:CASCADE"`
WorkerID uint
Worker *Worker
IsActive bool `gorm:"default:false;index"`
IsActive bool
// Space-separated two-letter strings indicating days of week the schedule is
// active ("mo", "tu", etc.). Empty means "every day".
DaysOfWeek string `gorm:"default:''"`
StartTime TimeOfDay `gorm:"default:''"`
EndTime TimeOfDay `gorm:"default:''"`
DaysOfWeek string
StartTime TimeOfDay
EndTime TimeOfDay
NextCheck time.Time
}

View File

@ -12,11 +12,11 @@ import (
type WorkerTag struct {
Model
UUID string `gorm:"type:char(36);default:'';unique;index"`
Name string `gorm:"type:varchar(64);default:'';unique"`
Description string `gorm:"type:varchar(255);default:''"`
UUID string
Name string
Description string
Workers []*Worker `gorm:"many2many:worker_tag_membership;constraint:OnDelete:CASCADE"`
Workers []*Worker
}
func (db *DB) CreateWorkerTag(ctx context.Context, wc *WorkerTag) error {

View File

@ -11,32 +11,31 @@ import (
"time"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"projects.blender.org/studio/flamenco/internal/manager/persistence/sqlc"
"projects.blender.org/studio/flamenco/pkg/api"
)
type Worker struct {
Model
DeletedAt gorm.DeletedAt `gorm:"index"`
DeletedAt sql.NullTime
UUID string `gorm:"type:char(36);default:'';unique;index"`
Secret string `gorm:"type:varchar(255);default:''"`
Name string `gorm:"type:varchar(64);default:''"`
UUID string
Secret string
Name string
Address string `gorm:"type:varchar(39);default:'';index"` // 39 = max length of IPv6 address.
Platform string `gorm:"type:varchar(16);default:''"`
Software string `gorm:"type:varchar(32);default:''"`
Status api.WorkerStatus `gorm:"type:varchar(16);default:''"`
LastSeenAt time.Time `gorm:"index"` // Should contain UTC timestamps.
CanRestart bool `gorm:"type:smallint;default:false"`
Address string // 39 = max length of IPv6 address.
Platform string
Software string
Status api.WorkerStatus
LastSeenAt time.Time // Should contain UTC timestamps.
CanRestart bool
StatusRequested api.WorkerStatus `gorm:"type:varchar(16);default:''"`
LazyStatusRequest bool `gorm:"type:smallint;default:false"`
StatusRequested api.WorkerStatus
LazyStatusRequest bool
SupportedTaskTypes string `gorm:"type:varchar(255);default:''"` // comma-separated list of task types.
SupportedTaskTypes string // comma-separated list of task types.
Tags []*WorkerTag `gorm:"many2many:worker_tag_membership;constraint:OnDelete:CASCADE"`
Tags []*WorkerTag
}
func (w *Worker) Identifier() string {
@ -315,7 +314,7 @@ func convertSqlcWorker(worker sqlc.Worker) *Worker {
CreatedAt: worker.CreatedAt,
UpdatedAt: worker.UpdatedAt.Time,
},
DeletedAt: gorm.DeletedAt(worker.DeletedAt),
DeletedAt: worker.DeletedAt,
UUID: worker.UUID,
Secret: worker.Secret,