{"record":{"id":"ab6bb1bcefc51dae","repo":"MHSanaei/3x-ui","slug":"clear-destination-tables-w","errorCode":null,"errorMessage":"clear destination tables: %w","messagePattern":"clear destination tables: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/database/migrate_data.go","lineNumber":124,"sourceCode":"\t}\n\n\ttotalRows := 0\n\ttxErr := dst.Transaction(func(tx *gorm.DB) error {\n\t\t// AutoMigrate re-creates the legacy client_traffics -> inbounds foreign key,\n\t\t// but the running panel drops it (see dropLegacyForeignKeys) and tolerates\n\t\t// client_traffics rows whose inbound was deleted. Drop it here too so copying\n\t\t// such orphaned rows can't fail with an fk_inbounds_client_stats violation.\n\t\tif err := tx.Exec(\"ALTER TABLE client_traffics DROP CONSTRAINT IF EXISTS fk_inbounds_client_stats\").Error; err != nil {\n\t\t\treturn fmt.Errorf(\"drop legacy foreign key: %w\", err)\n\t\t}\n\n\t\t// Empty the destination tables before copying: a fresh PostgreSQL DB\n\t\t// already holds an auto-seeded admin (id=1) from any prior panel start,\n\t\t// so a plain INSERT with explicit ids would collide on users_pkey. Only\n\t\t// the panel's own tables are cleared, and a failure anywhere in this\n\t\t// transaction rolls the clear back with everything else.\n\t\tif err := truncatePostgresTables(tx, migrationModels()); err != nil {\n\t\t\treturn fmt.Errorf(\"clear destination tables: %w\", err)\n\t\t}\n\n\t\tfor _, m := range migrationModels() {\n\t\t\tn, err := copyTable(src, tx, m)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"copy %T: %w\", m, err)\n\t\t\t}\n\t\t\ttotalRows += n\n\t\t\tlog.Printf(\"  %-32s %d rows\", reflect.TypeOf(m).Elem().Name(), n)\n\t\t}\n\t\treturn nil\n\t})\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\n\t// setval is never rolled back by PostgreSQL, so sequences are resynced only\n\t// after the transaction has committed.","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/database/migrate_data.go#L106-L142","documentation":"Thrown when truncatePostgresTables fails: it runs a single `TRUNCATE TABLE \"users\", ... RESTART IDENTITY CASCADE` over every migrated table before the row copy, because a fresh PostgreSQL DB already holds an auto-seeded admin (id=1) that would collide on users_pkey. The clear runs inside the copy transaction, so any failure here rolls everything back and the destination keeps its pre-migration contents.","triggerScenarios":"Destination role lacks TRUNCATE privilege or table ownership; another session holds an ACCESS EXCLUSIVE-lock conflicting lock on one of the panel tables (a running panel connected to the same DB); lock_timeout/statement_timeout firing; a table listed in migrationModels() not existing on dst.","commonSituations":"Running migrate-db against the SAME database the live panel is currently using; migrating into a DB where tables were created by a different role; strict lock/statement timeouts configured on managed Postgres (RDS/Cloud SQL).","solutions":["Stop the panel (and any other process) that is connected to the destination DB before running migrate-db.","Confirm the DSN role owns or has TRUNCATE privilege on every panel table.","Retry the migration — TRUNCATE is transactional in Postgres, so a failed run leaves the destination unchanged and a retry is safe.","Prefer a fresh empty destination database; the pre-seeded admin is then the only row cleared."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// ensure no other sessions hold locks on panel tables before migrating\nrows, _ := pgDB.Raw(`SELECT pid, query FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()`).Rows()\ndefer rows.Close()\n// abort the migration if any session is active (e.g. a running panel)","typeGuard":null,"tryCatchPattern":"if err := migrate(); err != nil {\n    if strings.Contains(err.Error(), \"clear destination tables\") {\n        log.Fatal(\"destination busy or privileges missing: stop the panel, check table ownership, then retry (destination is unchanged)\")\n    }\n}","preventionTips":["Stop the panel and any job runners connected to the destination DB before migrate-db.","Migrate into an empty DB so the only cleared row is the auto-seeded admin.","Remember TRUNCATE is transactional: a failed run leaves the destination untouched and a retry is safe."],"tags":["database","postgres","migration","truncate","locking","gorm"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}