{"record":{"id":"6d95f5dab14af29f","repo":"golang-migrate/migrate","slug":"database-is-dirty-6d95f5","errorCode":null,"errorMessage":"database is dirty","messagePattern":"database is dirty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"database/ql/ql.go","lineNumber":23,"sourceCode":"\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tnurl \"net/url\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"modernc.org/ql/driver\"\n)\n\nfunc init() {\n\tdatabase.Register(\"ql\", &Ql{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar (\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrAppendPEM      = fmt.Errorf(\"failed to append PEM\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n}\n\ntype Ql struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/golang-migrate/migrate/blob/01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a/database/ql/ql.go#L5-L41","documentation":"ErrDatabaseDirty is a sentinel error returned by the ql driver when the migrations table shows a previous migration failed mid-run (a 'dirty' state with a non-null version but no successful flag). Migrate refuses to continue on a dirty database to avoid applying migrations on top of an unknown, partially applied state. The same sentinel is redeclared in the cassandra and mysql drivers with identical meaning.","triggerScenarios":"Running migration steps when the schema_migrations table has a version recorded but dirty=true, typically after a previous migration process crashed or was killed mid-migration.","commonSituations":"Container or CI job killed during a long migration; network loss between client and database during DDL; running two migrator instances where one failed; manually editing the migrations table.","solutions":["Inspect the database state and manually verify/repair the partially applied migration (roll back or complete it)","Set the migration version/dirty flag back to a clean known state, e.g. SET version_force_save or run migrator.Force(version) with the correct version","Re-run migrations with the corrected state; add advisory locking (Lock/Unlock) to prevent concurrent migrator runs"],"exampleFix":"// before (stuck: dirty=true at version 5)\nm.Run()\n// after\nm, _ := source.New(sourceURL, path)\nd, _ := sql.Open(qlDsn)\nmig, _ := migrate.NewWithDatabaseInstance(sourceURL, \"ql\", d)\nmig.Force(5) // set version to 5, clears dirty\nmig.Migrate(6) // re-apply migration 6 after manual fix","handlingStrategy":"validation","validationCode":"// before running migrations\nvar version int\nvar dirty bool\nrow := db.QueryRowContext(ctx, \"SELECT version, dirty FROM schema_migrations\")\nif err := row.Scan(&version, &dirty); err == nil && dirty {\n    return fmt.Errorf(\"database dirty at version %d; repair manually before migrating\", version)\n}","typeGuard":"func isDirtyDatabaseErr(err error) bool {\n    return errors.Is(err, ql.ErrDatabaseDirty)\n}","tryCatchPattern":"if err := mig.Migrate(target); err != nil {\n    if errors.Is(err, ql.ErrDatabaseDirty) {\n        // inspect schema_migrations, fix partial state, then mig.Force(version)\n    }\n    return err\n}","preventionTips":["Run migrations under advisory locks so only one process migrates at a time","Ensure migrations are transactional or idempotent so crashes cannot leave partial DDL","Alert on dirty=true in the migrations table after any failed run"],"tags":["migrations","database-state","dirty-schema","ql"],"backgroundTag":"database-dirty-migration","analyzedSha":"01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a","analyzedAt":"2026-09-02T19:38:29.671Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}