{"record":{"id":"ef170fd981920ddb","repo":"sqlalchemy/alembic","slug":"constraint-must-have-a-name","errorCode":null,"errorMessage":"Constraint must have a name","messagePattern":"Constraint must have a name","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/operations/batch.py","lineNumber":665,"sourceCode":"        the CREATE TABLE and doesn't need an extra step here.\n\n        \"\"\"\n\n    def create_table_comment(self, table):\n        \"\"\"the batch table creation function will issue create_table_comment\n        on the real \"impl\" as part of the create table process.\n\n        \"\"\"\n\n    def drop_table_comment(self, table):\n        \"\"\"the batch table creation function will issue drop_table_comment\n        on the real \"impl\" as part of the create table process.\n\n        \"\"\"\n\n    def add_constraint(self, const: Constraint) -> None:\n        if not constraint_name_defined(const.name):\n            raise ValueError(\"Constraint must have a name\")\n        if isinstance(const, sql_schema.PrimaryKeyConstraint):\n            if self.table.primary_key in self.unnamed_constraints:\n                self.unnamed_constraints.remove(self.table.primary_key)\n\n        if constraint_name_string(const.name):\n            self.named_constraints[const.name] = const\n        else:\n            self.unnamed_constraints.append(const)\n\n    def drop_constraint(self, const: Constraint) -> None:\n        if not const.name:\n            raise ValueError(\"Constraint must have a name\")\n        try:\n            if const.name in self.col_named_constraints:\n                col, const = self.col_named_constraints.pop(const.name)\n\n                for col_const in list(self.columns[col.name].constraints):\n                    if col_const.name == const.name:","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/operations/batch.py#L647-L683","documentation":"Raised as ValueError by ApplyBatchImpl.add_constraint (batch.py:663-665) when a constraint added inside a batch_alter_table block has no usable name. Batch mode recreates the table by copying it and must track constraints in named_constraints / unnamed_constraints dictionaries, so it calls constraint_name_defined() (sqla_compat.py:138) which requires the name to be a real string or the SQLAlchemy 'none-name' symbol; a bare None fails. Without a name the constraint cannot be re-created deterministically on the new copy of the table.","triggerScenarios":"Inside a batch block calling batch_op.create_unique_constraint(None, 'users', ['email']) or batch_op.create_check_constraint(None, 'ck', 'x > 0') with the name argument set to None. Also triggered by create_foreign_key(None, 'src', 'dst', ['a'], ['b']) inside batch mode.","commonSituations":"Developers who normally rely on unnamed constraints with non-batch op.create_unique_constraint(..., name=None) hit SQLite-style batch mode (where batch recreation is mandatory for most alters) and the name requirement surfaces. Auto-generated migrations that omit explicit constraint names also fail under batch.","solutions":["Give the constraint an explicit string name, e.g. batch_op.create_unique_constraint('uq_users_email', 'users', ['email']).","If the constraint already exists in the DB with an auto-generated name, reflect it first and pass the reflected name.","For naming conventions, set a SQLAlchemy naming_convention in your MetaData so constraints get deterministic names before entering batch mode."],"exampleFix":"// before\nwith op.batch_alter_table('users') as batch_op:\n    batch_op.create_unique_constraint(None, 'users', ['email'])\n// after\nwith op.batch_alter_table('users') as batch_op:\n    batch_op.create_unique_constraint('uq_users_email', 'users', ['email'])","handlingStrategy":"validation","validationCode":"# Ensure a name exists before adding a constraint in batch mode.\ndef ensure_constraint_name(name):\n    if name is None:\n        raise ValueError('Batch add_constraint requires an explicit constraint name')\n    return name\n\nwith op.batch_alter_table('t') as batch_op:\n    batch_op.create_unique_constraint(\n        ensure_constraint_name('uq_t_col'), 't', ['col']\n    )","typeGuard":"from sqlalchemy.sql.schema import Constraint\n\ndef has_real_name(c: Constraint) -> bool:\n    return isinstance(c.name, str) and len(c.name) > 0","tryCatchPattern":null,"preventionTips":["Always pass an explicit string name when creating constraints inside batch_alter_table.","Configure a SQLAlchemy naming_convention on MetaData so reflected constraints carry names.","Reflect the existing schema to learn auto-generated names before batch alters."],"tags":["alembic","batch-mode","constraints","database-migration"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}