Skip to content

Regression in 1.6 onwards: duplicated check constraints in Create Table DDL #1004

@cansjt

Description

@cansjt

Describe the bug

Finally being able to upgrade from the latest 1.5.x to 1.7.x, I stumbled on the following problem: when built using SQLAlchemy expressions, check constraints may be duplicated in the DDL statement produced by alembic.

Expected behavior

To Reproduce
Please try to provide a Minimal, Complete, and Verifiable example, with the migration script and/or the SQLAlchemy tables or models involved.
See also Reporting Bugs on the website.

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID

# revision identifiers, used by Alembic.
revision = 'b126031fd753'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # Columns forward declarations for those used in constraints
    column = sa.Column('column', sa.Integer, nullable=True)

    op.create_table(
        't',
        sa.Column('t_id', UUID, nullable=False),
        column,
        sa.PrimaryKeyConstraint('t_id'),
        sa.CheckConstraint(sa.and_(column > 0, column < 5),
                           name='ck_dim_job_ad_remote_type_range',
                           ),
        schema='issue',
    )


def downgrade():
    op.drop_table('t', schema='issue')

Will produce the following DLL:

CREATE TABLE issue.t (
    t_id UUID NOT NULL, 
    "column" INTEGER, 
    PRIMARY KEY (t_id), 
    CONSTRAINT ck_dim_job_ad_remote_type_range CHECK ("column" > 0 AND "column" < 5), 
    CONSTRAINT ck_dim_job_ad_remote_type_range CHECK ("column" > 0 AND "column" < 5)
);

Error

n/a

Versions.

  • OS: Ubuntu / Debian
  • Python: 3.9.10
  • Alembic: >1.6.0 (tested with 1.6.0, 1.6.5, 1.7.5)
  • SQLAlchemy: 1.4.31 (also tested with coming 1.4.33)
  • Database: postgresql
  • DBAPI: psycopg2

Additional context

Rewritting the constraint with a text() expression (e.g. sa.text('"column" > 0 AND "column" < 5') in the above example) works around the issue.

This does not seem to come from SQLAlchemy (or SQLAlchmey somehow works around this). Indeed the following code:

from sqlalchemy.dialects.postgresql import dialect
from sqlalchemy.sql.ddl import CreateTable
from sqlalchemy import __version__

METADATA = MetaData()
T = Table(
    't',
    METADATA,
    Column('t_id', UUID, nullable=False),
    # Field to have a constraint applied to
    Column('column', Integer, nullable=True),
    PrimaryKeyConstraint('t_id'),
    comment='Bla bla bla',
    schema='issue',
)
"""Dimensions collection table to pull data from datapond (debezium)

"""
T.append_constraint(CheckConstraint(and_(T.c.column < 5, T.c.column > 0)))


print(f'-- SQLAlchemy {__version__}')
stmt = CreateTable(T)
str(dialect.ddl_compiler(dialect(), stmt))

As well as using the exact same construct as in the migration (column variable, used to build the constraint) yields the expected DDL statement:

-- 1.4.33

CREATE TABLE issue.t (
    t_id UUID NOT NULL, 
    "column" INTEGER, 
    PRIMARY KEY (t_id), 
    CHECK ("column" < 5 AND "column" > 0)
)

Have a nice day!
Thanks, have a nice day as well!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions