{"record":{"id":"55d589237dd30f38","repo":"MHSanaei/3x-ui","slug":"automigrate-t-w","errorCode":null,"errorMessage":"AutoMigrate %T: %w","messagePattern":"AutoMigrate %T: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/database/migrate_data.go","lineNumber":104,"sourceCode":"\t\treturn err\n\t}\n\tdefer srcSQL.Close()\n\n\tdst, err := gorm.Open(postgres.Open(dstDSN), &gorm.Config{Logger: logger.Discard})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open postgres destination: %w\", err)\n\t}\n\tdstSQL, err := dst.DB()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer dstSQL.Close()\n\tdstSQL.SetConnMaxLifetime(time.Hour)\n\n\tlog.Println(\"Creating destination schema...\")\n\tfor _, m := range migrationModels() {\n\t\tif err := dst.AutoMigrate(m); err != nil {\n\t\t\treturn fmt.Errorf(\"AutoMigrate %T: %w\", m, err)\n\t\t}\n\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.","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/database/migrate_data.go#L86-L122","documentation":"MigrateData creates the destination schema by looping migrationModels() through dst.AutoMigrate; a failure is wrapped with the offending model's type. The Postgres connection is fine but DDL failed — usually permissions, a pre-existing conflicting table shape, or an unsupported type/index on that PG version. The transaction copy phase never started, so no data was touched.","triggerScenarios":"Destination DB user lacks CREATE/ALTER on the schema; migrating into a DB whose tables were created by an older panel version with incompatible column types; PG version too old for an index/construction AutoMigrate emits.","commonSituations":"Managed Postgres with a restricted role; re-running a migration into a half-migrated DB; PG 11/12 vs newer features.","solutions":["Read the wrapped error for the failing model and SQLSTATE — 42501 means permissions, 42P07/42701 mean pre-existing conflicting objects.","Grant the migration role CREATE/ALTER on the target schema, or point dstDSN at the owner role.","If retrying after a partial attempt, drop the panel's tables in the destination (data is disposable at this stage) and re-run the migration cleanly.","Verify the Postgres version meets the panel's minimum (see docs) before migrating."],"exampleFix":"-- before: role without DDL rights\nGRANT CONNECT ON DATABASE xui TO migrator;\n-- after\nGRANT CONNECT, CREATE ON DATABASE xui TO migrator;\nGRANT ALL ON SCHEMA public TO migrator;","handlingStrategy":"try-catch","validationCode":"// Pre-flight: the role must be able to create/alter tables in the target schema.\nvar canCreate bool\nif err := dstSQL.QueryRowContext(ctx, `SELECT has_database_privilege(current_user, current_database(), 'CREATE')`).Scan(&canCreate); err != nil || !canCreate {\n    return errors.New(\"migration role lacks CREATE on target database\")\n}","typeGuard":null,"tryCatchPattern":"if err := database.MigrateData(src, dsn); err != nil {\n    if strings.Contains(err.Error(), \"AutoMigrate\") {\n        // schema DDL failed; no data copied. Fix grants or drop conflicting tables, then re-run.\n    }\n    return err\n}","preventionTips":["Migrate with the destination database owner role, or grant CREATE/ALTER on the schema first.","Start from an empty target database; drop half-migrated panel tables before re-running.","Confirm the Postgres version meets the panel's minimum before migrating."],"tags":["database","postgres","migration","schema","permissions"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}