Skip to content

alembic attempts to alter_columns in Table def to nullable=True after creating Composite primary keys in create_table #199

@sqlalchemy-bot

Description

@sqlalchemy-bot

Migrated issue, originally created by Mike Waites (@mikeywaites)

Hey,

Im not sure if this is covered anywhere in the docs and perhaps i just missed it, However I just encountered an issue that felt as though it was perhaps worth reporting.

Using flask-sqlalchemy and flask-migrate along with the latest versions of alembic and sqlalchemy I created a "initial" migration for my current schema. On the the table picked up was a m2m rel defined using the basic Table() pattern.

# -*- coding: utf-8 -*-

"""
person_to_role = Table('person_to_role', db.Model.metadata,
    Column('person_id', Integer, ForeignKey('person.id')),
    Column('role_id', Integer, ForeignKey('role.id')),
    PrimaryKeyConstraint('person_id', 'role_id')
)

This generated the alembic migration, which at first seemed correct.

    op.create_table(u'person_to_role',
    sa.Column(u'person_id', sa.Integer(), nullable=True),
    sa.Column(u'role_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['person_id'], [u'person.id'], ),
    sa.ForeignKeyConstraint(['role_id'], [u'role.id'], ),
    sa.PrimaryKeyConstraint(u'person_id', u'role_id')
    )

I decided to run another migration expecting a blank migration to be created. alembic actually generated an upgrade migration for the m2m model as follows.

    op.alter_column(u'person_to_role', u'person_id',
               existing_type=sa.INTEGER(),
               nullable=True)
    op.alter_column(u'person_to_role', u'role_id',
               existing_type=sa.INTEGER(),
               nullable=True)

As we can see, alembic is not trying to modify the columns so null would be a valid value which would obviously break at the db level anyway as both rows compose the PK and are created in the first migration like:

Table "public.person_to_role"
  Column   |  Type   | Modifiers
-----------+---------+-----------
 person_id | integer | not null
 role_id   | integer | not null
Indexes:
    "person_to_role_pkey" PRIMARY KEY, btree (person_id, role_id)
Foreign-key constraints:
    "person_to_role_person_id_fkey" FOREIGN KEY (person_id) REFERENCES person(id)
    "person_to_role_role_id_fkey" FOREIGN KEY (role_id) REFERENCES role(id)

We can however see in the first migration that nullable=True was defined, just ignored due to the PrimaryKeyContraint. setting nullable=False solves the problem for me, however i kinda felt this wasn`t the expected behaviour. Should you explicitly set nullable=False in a Table def like this or is this actually a bug?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions