cansjt · GitHub

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!

Read the original on github.com ↗