Manager: convert pragma foreign key command to sqlc

No functional changes.

Ref: #104305
This commit is contained in:
Sybren A. Stüvel 2024-09-18 10:10:15 +02:00
parent 96cc8255e7
commit 476d4059bf
10 changed files with 64 additions and 43 deletions

View File

@ -134,7 +134,9 @@ func openDBWithConfig(dsn string, config *gorm.Config) (*DB, error) {
sqlDB.SetMaxOpenConns(1) // Max num of open connections to the database. sqlDB.SetMaxOpenConns(1) // Max num of open connections to the database.
// Always enable foreign key checks, to make SQLite behave like a real database. // Always enable foreign key checks, to make SQLite behave like a real database.
if err := db.pragmaForeignKeys(true); err != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.pragmaForeignKeys(ctx, true); err != nil {
return nil, err return nil, err
} }
@ -237,29 +239,22 @@ func (db *DB) now() sql.NullTime {
} }
} }
func (db *DB) pragmaForeignKeys(enabled bool) error { func (db *DB) pragmaForeignKeys(ctx context.Context, enabled bool) error {
var ( var noun string
value int
noun string
)
switch enabled { switch enabled {
case false: case false:
value = 0
noun = "disabl" noun = "disabl"
case true: case true:
value = 1
noun = "enabl" noun = "enabl"
} }
log.Trace().Msgf("%sing SQLite foreign key checks", noun) log.Trace().Msgf("%sing SQLite foreign key checks", noun)
// SQLite doesn't seem to like SQL parameters for `PRAGMA`, so `PRAGMA foreign_keys = ?` doesn't work. queries := db.queries()
sql := fmt.Sprintf("PRAGMA foreign_keys = %d", value) if err := queries.PragmaForeignKeysSet(ctx, enabled); err != nil {
return fmt.Errorf("%sing foreign keys: %w", noun, err)
if tx := db.gormDB.Exec(sql); tx.Error != nil {
return fmt.Errorf("%sing foreign keys: %w", noun, tx.Error)
} }
fkEnabled, err := db.areForeignKeysEnabled() fkEnabled, err := db.areForeignKeysEnabled(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -270,12 +265,13 @@ func (db *DB) pragmaForeignKeys(enabled bool) error {
return nil return nil
} }
func (db *DB) areForeignKeysEnabled() (bool, error) { func (db *DB) areForeignKeysEnabled(ctx context.Context) (bool, error) {
log.Trace().Msg("checking whether SQLite foreign key checks are enabled") log.Trace().Msg("checking whether SQLite foreign key checks are enabled")
var fkEnabled int queries := db.queries()
if tx := db.gormDB.Raw("PRAGMA foreign_keys").Scan(&fkEnabled); tx.Error != nil { fkEnabled, err := queries.PragmaForeignKeysGet(ctx)
return false, fmt.Errorf("checking whether the database has foreign key checks are enabled: %w", tx.Error) if err != nil {
return false, fmt.Errorf("checking whether the database has foreign key checks are enabled: %w", err)
} }
return fkEnabled != 0, nil return fkEnabled, nil
} }

View File

@ -41,7 +41,7 @@ func (db *DB) migrate(ctx context.Context) error {
// of data, foreign keys are disabled here instead of in the migration SQL // of data, foreign keys are disabled here instead of in the migration SQL
// files, so that it can't be forgotten. // files, so that it can't be forgotten.
if err := db.pragmaForeignKeys(false); err != nil { if err := db.pragmaForeignKeys(ctx, false); err != nil {
log.Fatal().AnErr("cause", err).Msgf("could not disable foreign key constraints before performing database migrations, please report a bug at %s", website.BugReportURL) log.Fatal().AnErr("cause", err).Msgf("could not disable foreign key constraints before performing database migrations, please report a bug at %s", website.BugReportURL)
} }
@ -52,7 +52,7 @@ func (db *DB) migrate(ctx context.Context) error {
} }
// Re-enable foreign key checks. // Re-enable foreign key checks.
if err := db.pragmaForeignKeys(true); err != nil { if err := db.pragmaForeignKeys(ctx, true); err != nil {
log.Fatal().AnErr("cause", err).Msgf("could not re-enable foreign key constraints after performing database migrations, please report a bug at %s", website.BugReportURL) log.Fatal().AnErr("cause", err).Msgf("could not re-enable foreign key constraints after performing database migrations, please report a bug at %s", website.BugReportURL)
} }

View File

@ -74,7 +74,7 @@ func (db *DB) performIntegrityCheck(ctx context.Context) (ok bool) {
log.Debug().Msg("database: performing integrity check") log.Debug().Msg("database: performing integrity check")
db.ensureForeignKeysEnabled() db.ensureForeignKeysEnabled(checkCtx)
if !db.pragmaIntegrityCheck(checkCtx) { if !db.pragmaIntegrityCheck(checkCtx) {
return false return false
@ -162,8 +162,8 @@ func (db *DB) pragmaForeignKeyCheck(ctx context.Context) (ok bool) {
// connection to the low-level SQLite driver. Unfortunately the GORM-embedded // connection to the low-level SQLite driver. Unfortunately the GORM-embedded
// SQLite doesn't have an 'on-connect' callback function to always enable // SQLite doesn't have an 'on-connect' callback function to always enable
// foreign keys. // foreign keys.
func (db *DB) ensureForeignKeysEnabled() { func (db *DB) ensureForeignKeysEnabled(ctx context.Context) {
fkEnabled, err := db.areForeignKeysEnabled() fkEnabled, err := db.areForeignKeysEnabled(ctx)
if err != nil { if err != nil {
log.Error().AnErr("cause", err).Msg("database: could not check whether foreign keys are enabled") log.Error().AnErr("cause", err).Msg("database: could not check whether foreign keys are enabled")
@ -175,7 +175,7 @@ func (db *DB) ensureForeignKeysEnabled() {
} }
log.Warn().Msg("database: foreign keys are disabled, re-enabling them") log.Warn().Msg("database: foreign keys are disabled, re-enabling them")
if err := db.pragmaForeignKeys(true); err != nil { if err := db.pragmaForeignKeys(ctx, true); err != nil {
log.Error().AnErr("cause", err).Msg("database: error re-enabling foreign keys") log.Error().AnErr("cause", err).Msg("database: error re-enabling foreign keys")
return return
} }

View File

@ -389,11 +389,11 @@ func (db *DB) FetchJobShamanCheckoutID(ctx context.Context, jobUUID string) (str
// The deletion cascades to its tasks and other job-related tables. // The deletion cascades to its tasks and other job-related tables.
func (db *DB) DeleteJob(ctx context.Context, jobUUID string) error { func (db *DB) DeleteJob(ctx context.Context, jobUUID string) error {
// As a safety measure, refuse to delete jobs unless foreign key constraints are active. // As a safety measure, refuse to delete jobs unless foreign key constraints are active.
fkEnabled, err := db.areForeignKeysEnabled() fkEnabled, err := db.areForeignKeysEnabled(ctx)
if err != nil { switch {
return fmt.Errorf("checking whether foreign keys are enabled: %w", err) case err != nil:
} return err
if !fkEnabled { case !fkEnabled:
return ErrDeletingWithoutFK return ErrDeletingWithoutFK
} }

View File

@ -255,7 +255,7 @@ func TestDeleteJobWithoutFK(t *testing.T) {
authJob.Name = "Job to delete" authJob.Name = "Job to delete"
persistAuthoredJob(t, ctx, db, authJob) persistAuthoredJob(t, ctx, db, authJob)
require.NoError(t, db.pragmaForeignKeys(false)) require.NoError(t, db.pragmaForeignKeys(ctx, false))
err := db.DeleteJob(ctx, authJob.JobID) err := db.DeleteJob(ctx, authJob.JobID)
require.ErrorIs(t, err, ErrDeletingWithoutFK) require.ErrorIs(t, err, ErrDeletingWithoutFK)

View File

@ -38,3 +38,28 @@ func (q *Queries) PragmaIntegrityCheck(ctx context.Context) ([]PragmaIntegrityCh
} }
return items, nil return items, nil
} }
// SQLite doesn't seem to like SQL parameters for `PRAGMA`, so `PRAGMA foreign_keys = ?` doesn't work.
const pragmaForeignKeysEnable = `PRAGMA foreign_keys = 1`
const pragmaForeignKeysDisable = `PRAGMA foreign_keys = 0`
func (q *Queries) PragmaForeignKeysSet(ctx context.Context, enable bool) error {
var sql string
if enable {
sql = pragmaForeignKeysEnable
} else {
sql = pragmaForeignKeysDisable
}
_, err := q.db.ExecContext(ctx, sql)
return err
}
const pragmaForeignKeys = `PRAGMA foreign_keys`
func (q *Queries) PragmaForeignKeysGet(ctx context.Context) (bool, error) {
row := q.db.QueryRowContext(ctx, pragmaForeignKeys)
var fkEnabled bool
err := row.Scan(&fkEnabled)
return fkEnabled, err
}

View File

@ -73,11 +73,11 @@ func (db *DB) SaveWorkerTag(ctx context.Context, tag *WorkerTag) error {
// DeleteWorkerTag deletes the given tag, after unassigning all workers from it. // DeleteWorkerTag deletes the given tag, after unassigning all workers from it.
func (db *DB) DeleteWorkerTag(ctx context.Context, uuid string) error { func (db *DB) DeleteWorkerTag(ctx context.Context, uuid string) error {
// As a safety measure, refuse to delete unless foreign key constraints are active. // As a safety measure, refuse to delete unless foreign key constraints are active.
fkEnabled, err := db.areForeignKeysEnabled() fkEnabled, err := db.areForeignKeysEnabled(ctx)
if err != nil { switch {
return fmt.Errorf("checking whether foreign keys are enabled: %w", err) case err != nil:
} return err
if !fkEnabled { case !fkEnabled:
return ErrDeletingWithoutFK return ErrDeletingWithoutFK
} }

View File

@ -85,7 +85,7 @@ func TestDeleteTagsWithoutFK(t *testing.T) {
require.NoError(t, f.db.CreateWorkerTag(f.ctx, &secondTag)) require.NoError(t, f.db.CreateWorkerTag(f.ctx, &secondTag))
// Try deleting with foreign key constraints disabled. // Try deleting with foreign key constraints disabled.
require.NoError(t, f.db.pragmaForeignKeys(false)) require.NoError(t, f.db.pragmaForeignKeys(f.ctx, false))
err = f.db.DeleteWorkerTag(f.ctx, f.tag.UUID) err = f.db.DeleteWorkerTag(f.ctx, f.tag.UUID)
require.ErrorIs(t, err, ErrDeletingWithoutFK) require.ErrorIs(t, err, ErrDeletingWithoutFK)

View File

@ -139,11 +139,11 @@ func (db *DB) FetchWorker(ctx context.Context, uuid string) (*Worker, error) {
func (db *DB) DeleteWorker(ctx context.Context, uuid string) error { func (db *DB) DeleteWorker(ctx context.Context, uuid string) error {
// As a safety measure, refuse to delete unless foreign key constraints are active. // As a safety measure, refuse to delete unless foreign key constraints are active.
fkEnabled, err := db.areForeignKeysEnabled() fkEnabled, err := db.areForeignKeysEnabled(ctx)
if err != nil { switch {
return fmt.Errorf("checking whether foreign keys are enabled: %w", err) case err != nil:
} return err
if !fkEnabled { case !fkEnabled:
return ErrDeletingWithoutFK return ErrDeletingWithoutFK
} }

View File

@ -342,7 +342,7 @@ func TestDeleteWorkerNoForeignKeys(t *testing.T) {
require.NoError(t, db.CreateWorker(ctx, &w1)) require.NoError(t, db.CreateWorker(ctx, &w1))
// Try deleting with foreign key constraints disabled. // Try deleting with foreign key constraints disabled.
require.NoError(t, db.pragmaForeignKeys(false)) require.NoError(t, db.pragmaForeignKeys(ctx, false))
require.ErrorIs(t, ErrDeletingWithoutFK, db.DeleteWorker(ctx, w1.UUID)) require.ErrorIs(t, ErrDeletingWithoutFK, db.DeleteWorker(ctx, w1.UUID))
// The worker should still exist. // The worker should still exist.