Skip to content

batch_alter_table naming_convention re-applies ck convention to reflected constraint names #1845

Description

@zzzeek

Describe the bug

batch_alter_table(naming_convention=...) reflects the existing table into a
MetaData that carries the convention. Reflected constraints already have names,
but a ck convention containing %(constraint_name)s is re-applied to them, so
the existing name gets prefixed a second time.

Only conventions using the %(constraint_name)s token are affected; uq/fk/pk
conventions don't include it, so reflected names pass through unchanged there.

To Reproduce

from alembic.migration import MigrationContext
from alembic.operations import Operations
from sqlalchemy import CheckConstraint, Column, Integer, MetaData, Table, create_engine

naming = {"ck": "ck_%(table_name)s_%(constraint_name)s"}

m = MetaData(naming_convention=naming)
Table(
    "user",
    m,
    Column("id", Integer, primary_key=True),
    Column("age", Integer),
    CheckConstraint("age > 0", name="positive_age"),
)

e = create_engine("sqlite://")
m.create_all(e)
with e.begin() as conn:
    print("before:", conn.exec_driver_sql(
        "select sql from sqlite_master where name='user'").scalar())

    op = Operations(MigrationContext.configure(conn))
    with op.batch_alter_table(
        "user", naming_convention=naming, recreate="always"
    ) as batch_op:
        batch_op.add_column(Column("y", Integer))

    print("after: ", conn.exec_driver_sql(
        "select sql from sqlite_master where name='user'").scalar())

Expected behavior

The reflected constraint keeps its existing name; the convention should only supply
names for constraints that don't have one.

Actual behavior

before: CREATE TABLE user (
	id INTEGER NOT NULL,
	age INTEGER,
	PRIMARY KEY (id),
	CONSTRAINT ck_user_positive_age CHECK (age > 0)
)
after:  CREATE TABLE "user" (
	id INTEGER NOT NULL,
	age INTEGER,
	y INTEGER,
	PRIMARY KEY (id),
	CONSTRAINT ck_user_ck_user_positive_age CHECK (age > 0)
)

The prefix accumulates on every recreate.

Additional context

The renaming happens at reflection time in BatchOperationsImpl.flush(), where the
table is reflected into MetaData(naming_convention=self.naming_convention).
Discovered while investigating #1768; related to #1844.

Versions

  • Alembic: 1.19.1 (main)
  • SQLAlchemy: 2.1
  • Database: SQLite

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions