{"id":"3620ecdfc4730c76","repo":"sqlalchemy/alembic","slug":"no-support-for-alter-of-constraints-in-sqlite-dial","errorCode":null,"errorMessage":"No support for ALTER of constraints in SQLite dialect. Please refer to the batch mode feature which allows for SQLite migrations using a copy-and-move strategy.","messagePattern":"No support for ALTER of constraints in SQLite dialect\\. Please refer to the batch mode feature which allows for SQLite migrations using a copy-and-move strategy\\.","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"alembic/ddl/sqlite.py","lineNumber":78,"sourceCode":"                if isinstance(\n                    col.server_default, schema.DefaultClause\n                ) and isinstance(col.server_default.arg, sql.ClauseElement):\n                    return True\n                elif (\n                    isinstance(col.server_default, Computed)\n                    and col.server_default.persisted\n                ):\n                    return True\n            elif op[0] not in (\"create_index\", \"drop_index\"):\n                return True\n        else:\n            return False\n\n    def add_constraint(self, const: Constraint, **kw: Any):\n        # attempt to distinguish between an\n        # auto-gen constraint and an explicit one\n        if const._create_rule is None:\n            raise NotImplementedError(\n                \"No support for ALTER of constraints in SQLite dialect. \"\n                \"Please refer to the batch mode feature which allows for \"\n                \"SQLite migrations using a copy-and-move strategy.\"\n            )\n        elif const._create_rule(self):\n            util.warn(\n                \"Skipping unsupported ALTER for \"\n                \"creation of implicit constraint. \"\n                \"Please refer to the batch mode feature which allows for \"\n                \"SQLite migrations using a copy-and-move strategy.\"\n            )\n\n    def drop_constraint(self, const: Constraint, **kw: Any):\n        if const._create_rule is None:\n            raise NotImplementedError(\n                \"No support for ALTER of constraints in SQLite dialect. \"\n                \"Please refer to the batch mode feature which allows for \"\n                \"SQLite migrations using a copy-and-move strategy.\"","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/ddl/sqlite.py#L60-L96","documentation":"SQLite cannot ALTER TABLE to add most constraints (it lacks ADD CONSTRAINT). Alembic's SQLite dialect distinguishes implicit constraints (which it warns and skips) from explicit ones (which have _create_rule set to None and would produce invalid SQL); explicit adds raise with a pointer to batch mode, which recreates the table via copy-and-move.","triggerScenarios":"Calling op.create_foreign_key(...), op.create_unique_constraint(...), op.create_check_constraint(...), or op.create_primary_key(...) on SQLite outside of batch mode.","commonSituations":"A migration authored on PostgreSQL uses op.create_foreign_key and is run against SQLite in tests; a SQLite-first project that tries to add a constraint to an existing table.","solutions":["Wrap the operation in op.batch_alter_table('t') as batch_op: and use batch_op.create_foreign_key(...) etc.","For new tables, declare the constraint inline in the CreateTable operation instead of adding it later.","Set recreate='always' in batch_alter_table if auto-detection misfires."],"exampleFix":"// before\nop.create_foreign_key('fk_t_u', 't', 'u', ['u_id'], ['id'])  # SQLite -> NotImplementedError\n// after\nwith op.batch_alter_table('t') as batch_op:\n    batch_op.create_foreign_key('fk_t_u', 't', 'u', ['u_id'], ['id'])","handlingStrategy":"validation","validationCode":"if engine.dialect.name == \"sqlite\":\n    if not _in_batch():\n        raise RuntimeError(\"SQLite cannot add constraints without batch mode\")","typeGuard":"def is_sqlite(dialect) -> bool:\n    return getattr(dialect, \"name\", \"\") == \"sqlite\"","tryCatchPattern":null,"preventionTips":["Wrap any create_*_constraint in batch_alter_table on SQLite.","Declare constraints inline in CreateTable for SQLite when possible.","Run migration tests against SQLite to catch these early."],"tags":["sqlite","constraints","batch-mode"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}