I'm not sure when this started happening...an upgrade to 2.x sqla? Or alembic upgrade? But I have indices defined from years ago that are now being dropped and recreated with every migration. I think it may have to do with a function in the index?
sqlalchemy - 2.0.9
alembic - 1.11.1
PostgreSQL - 14.2
I've attached a simplification of my models which demonstrate the issue. Table Questionnaire has two foreign references, one to Company and one to Project. company_id is required. If project_id is NULL then it is a "Company Questionnaire". If set, it is a "Project Questionnaire". The multi-column foreign key constraint is our method of managing multi-tenancy - this ensures that a project-company tuple on the Questionnaire is for a project belonging to the same company.
The unique index in question, on table Questionnaire, ensures that no two company questionnaires or project questionnaires have the same title.
With no changes to the model, I keep getting this migration:
def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_index('questionnaire_uniq_title', table_name='questionnaire') op.create_index('questionnaire_uniq_title', 'questionnaire', ['company_id', sa.text('coalesce(project_id, -1)'), sa.text('(lower(title))')], unique=True) # ### end Alembic commands ###
My model module:
from sqlalchemy import ( Column, ForeignKey, ForeignKeyConstraint, func, Index, Integer, String, Table, text, UniqueConstraint, ) from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() Company = Table( 'company', Base.metadata, Column('id', Integer, primary_key=True), Column('name', String), ) Project = Table( 'project', Base.metadata, Column('id', Integer, primary_key=True), Column('name', String), Column('company_id', Integer, ForeignKey('company.id', ondelete='CASCADE'), nullable=False), UniqueConstraint('id', 'company_id', name='project_uniq_id_cid'), ) Qustionnaire = Table( 'questionnaire', Base.metadata, Column('id', Integer, primary_key=True), Column('title', String), Column('company_id', Integer, ForeignKey('company.id', ondelete='CASCADE'), nullable=False), Column('project_id', Integer, ForeignKey('project.id', ondelete='CASCADE')), # (company_id, project_id) are denormalized...ensure the tuple # matches an existing (id, company_id) from the project table (as # opposed to individual foreign key references to each which would # allow a project_id whose project record does not having matching # company_id) ForeignKeyConstraint(('project_id', 'company_id'), ('project.id', 'project.company_id'), name='questionnaire_pid_cid_fk', ondelete='CASCADE'), # ensure for any given Company Questionnaire or Project # Questionnaire have unique titles across all Company or Project Index('questionnaire_uniq_title', 'company_id', text('coalesce(project_id, -1)'), text('lower(title)'), unique=True), )