나는 이렇게 학습한다/Debug

SQLAlchemy _ ForeignKey 필드에 name 을 넣어주어야 Alembic 이 downgrade 해줌

daco2020 2023. 3. 23. 11:12
반응형

한 줄 요약

*ForeignKey 필드에 name을 넣어주어야 *Alembic 이 *downgrade 해줌.

 

*ForeignKey 필드는 다른 테이블의 기본 키를 참조하는 테이블 필드를 의미함

*Alembic은 Python용 데이터베이스 마이그레이션 도구임

*downgrade는 이전 version으로 되돌리는 것을 의미함

 

 

 


 

 

문제상황

부모 record 를 지우면 자식 record 도 지워지도록, 자식 parent_id 필드에 ondelete="CASCADE" 옵션을 추가했다.

class Child(Base):
    __tablename__ = "child"

    id = sa.Column(UUID(as_uuid=True), primary_key=True)
    parent_id = sa.Column(
        UUID(as_uuid=True),
        ForeignKey("parent.id", ondelete="CASCADE"), # <-- 이 부분
        nullable=False,
        index=True,
    )

 

 

Alembic version 파일도 생성되었고 migrate까지 정상!

def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint('child_parent_id_fkey', 'child', type_='foreignkey')
    op.create_foreign_key('child_parent_id_fkey', 'child', 'parent', ['parent_id'], ['id'], ondelete='CASCADE')
    # ### end Alembic commands ###

def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint('child_parent_id_fkey', 'child', type_='foreignkey')
    op.create_foreign_key('child_parent_id_fkey', 'child', 'parent', ['parent_id'], ['id'])
    # ### end Alembic commands ###

 

 

그러나 version을 downgrade 하니 다음 에러가 땋!?

sqlalchemy.exc.CompileError: Can't emit DROP CONSTRAINT for constraint ForeignKeyConstraint(<sqlalchemy.sql.base.DedupeColumnCollection object at 0xffff80c086d0>, None, table=Table('child', MetaData(), schema=None)); it has no name

 

 

 

 

 

 

 

 


 

 

 

해결방법

문제의 원인은 Alembic 이 version 파일에 명시한 이름(child_parent_id_fkey)이 실제 ForeignKey 필드에서는 정의되어 있지 않았기 때문이었다.

 

Alembic의  Autogenerate 기능을 사용하면 '데이터베이스'와 애플리케이션에 작성된 '테이블 메타데이터'를 비교하여 version 파일을 생성해 주는데, 이 과정에서 ForeignKey 제약 조건 이름을 임의로 설정하게 된다.

 

결국 테이블 메타데이터에는 해당 이름이 없기 때문에 SQLAlchemy가 외래 키 제약 조건에 대한 DROP CONSTRAINT 문을 내보낼 수 없었던 것! (깨달음 포인트) 알고보니 Alembic 공식문서에서도 이 부분이 Autogenerate 의 한계임을 명시하고 name 에 대한 중요성을 언급하고 있었다.

 

 

그래서 이 문제를 해결하려면 어떻게 해야 하나? name을 명시해 주면 된다!

 

아래처럼 ForeignKey 필드를 정의할 때 name=”child_parent_id_fkey” 을 직접 명시해주면 해결할 수 있다.

class Child(Base):
    __tablename__ = "child"

    id = sa.Column(UUID(as_uuid=True), primary_key=True)
    parent_id = sa.Column(
        UUID(as_uuid=True),
        ForeignKey("parent.id", name="child_parent_id_fkey", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )

 

이를 다시 반영하기 위해 version 파일의 코드를 수정할 수도 있겠지만, 그냥 깔끔하게 db 볼륨 지우고 다시 migrate 함.

 

 

 

 


 

 

 

함께 보면 좋은 공식문서

 

Auto Generating Migrations — Alembic 1.10.2 documentation

Alembic can view the status of the database and compare against the table metadata in the application, generating the “obvious” migrations based on a comparison. This is achieved using the --autogenerate option to the alembic revision command, which pl

alembic.sqlalchemy.org

 

Cascades — SQLAlchemy 1.4 Documentation

Cascades Mappers support the concept of configurable cascade behavior on relationship() constructs. This refers to how operations performed on a “parent” object relative to a particular Session should be propagated to items referred to by that relation

docs.sqlalchemy.org

 

반응형