From fb9f64dcbfc2c8534e1f1a51d8d84703beb15d51 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Thu, 23 Apr 2026 16:32:39 +0200 Subject: [PATCH] migrate: check errors in stripWalletAssignmentFKs Three dialect branches silently discarded errors from the statements that rebuild or strip wallet_assignments FKs. sqlite: INSERT and DROP were unchecked. A failed INSERT (schema drift, constraint) followed by successful DROP + RENAME would wipe the table while reporting success. wallet_assignments carries preparation_id -> actor_id links that export-keys needs to reconstruct preparation.wallet_id, so losing them silently breaks legacy wallet migration. postgres: DROP CONSTRAINT IF EXISTS errors (permissions, lock contention) were dropped. Behavior unchanged on happy path; real errors now surface. mysql: DROP FOREIGN KEY has no IF EXISTS before 8.0.19, so checking information_schema.table_constraints makes the path idempotent. A re-run previously failed silently; it now skips the drop cleanly. --- model/migrate.go | 55 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/model/migrate.go b/model/migrate.go index 0c66fd709..24a350cdd 100644 --- a/model/migrate.go +++ b/model/migrate.go @@ -484,20 +484,57 @@ func stripWalletAssignmentFKs(db *gorm.DB) error { dialect := db.Dialector.Name() switch dialect { case "sqlite": - // sqlite can't drop constraints; recreate table without them + // sqlite can't drop constraints; recreate table without them. + // check every step: a silent INSERT failure followed by a + // successful DROP + RENAME would wipe wallet_assignments data + // that export-keys still needs to rebuild preparation.wallet_id. if err := db.Exec(`CREATE TABLE wallet_assignments_tmp (preparation_id integer, wallet_id text, PRIMARY KEY (preparation_id, wallet_id))`).Error; err != nil { - return err + return errors.Wrap(err, "create wallet_assignments_tmp") } - db.Exec(`INSERT INTO wallet_assignments_tmp SELECT * FROM wallet_assignments`) - db.Exec(`DROP TABLE wallet_assignments`) - return db.Exec(`ALTER TABLE wallet_assignments_tmp RENAME TO wallet_assignments`).Error + if err := db.Exec(`INSERT INTO wallet_assignments_tmp SELECT * FROM wallet_assignments`).Error; err != nil { + return errors.Wrap(err, "copy wallet_assignments rows") + } + if err := db.Exec(`DROP TABLE wallet_assignments`).Error; err != nil { + return errors.Wrap(err, "drop old wallet_assignments") + } + if err := db.Exec(`ALTER TABLE wallet_assignments_tmp RENAME TO wallet_assignments`).Error; err != nil { + return errors.Wrap(err, "rename wallet_assignments_tmp") + } + return nil case "postgres": - db.Exec(`ALTER TABLE wallet_assignments DROP CONSTRAINT IF EXISTS fk_wallet_assignments_wallet`) - db.Exec(`ALTER TABLE wallet_assignments DROP CONSTRAINT IF EXISTS fk_wallet_assignments_preparation`) + if err := db.Exec(`ALTER TABLE wallet_assignments DROP CONSTRAINT IF EXISTS fk_wallet_assignments_wallet`).Error; err != nil { + return errors.Wrap(err, "drop fk_wallet_assignments_wallet") + } + if err := db.Exec(`ALTER TABLE wallet_assignments DROP CONSTRAINT IF EXISTS fk_wallet_assignments_preparation`).Error; err != nil { + return errors.Wrap(err, "drop fk_wallet_assignments_preparation") + } return nil case "mysql": - db.Exec(`ALTER TABLE wallet_assignments DROP FOREIGN KEY fk_wallet_assignments_wallet`) - db.Exec(`ALTER TABLE wallet_assignments DROP FOREIGN KEY fk_wallet_assignments_preparation`) + // mysql has no DROP FOREIGN KEY IF EXISTS pre-8.0.19; check + // existence first so a re-run doesn't fail on a missing FK. + var fkCount int64 + if err := db.Raw(`SELECT COUNT(*) FROM information_schema.table_constraints + WHERE table_schema = DATABASE() AND table_name = 'wallet_assignments' + AND constraint_name = ? AND constraint_type = 'FOREIGN KEY'`, + "fk_wallet_assignments_wallet").Scan(&fkCount).Error; err != nil { + return errors.Wrap(err, "check fk_wallet_assignments_wallet") + } + if fkCount > 0 { + if err := db.Exec(`ALTER TABLE wallet_assignments DROP FOREIGN KEY fk_wallet_assignments_wallet`).Error; err != nil { + return errors.Wrap(err, "drop fk_wallet_assignments_wallet") + } + } + if err := db.Raw(`SELECT COUNT(*) FROM information_schema.table_constraints + WHERE table_schema = DATABASE() AND table_name = 'wallet_assignments' + AND constraint_name = ? AND constraint_type = 'FOREIGN KEY'`, + "fk_wallet_assignments_preparation").Scan(&fkCount).Error; err != nil { + return errors.Wrap(err, "check fk_wallet_assignments_preparation") + } + if fkCount > 0 { + if err := db.Exec(`ALTER TABLE wallet_assignments DROP FOREIGN KEY fk_wallet_assignments_preparation`).Error; err != nil { + return errors.Wrap(err, "drop fk_wallet_assignments_preparation") + } + } return nil } return nil