한 줄 요약
*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 함.
함께 보면 좋은 공식문서
'나는 이렇게 학습한다 > Debug' 카테고리의 다른 글
pytest _ 테스트 코드에서 현재 시간 바꾸는 방법 (0) | 2022.11.17 |
---|---|
pytest _ 비동기 테스트 실행하기 (feat. auto 모드) (0) | 2022.10.26 |
python _ traceback 에러 메시지 핸들링하기 (5) | 2022.09.24 |
pytest _ pytest-cov로 coverage 확인하기(실습) (0) | 2022.09.05 |
pytest _ mock 사용하여 테스트 코드 작성하기 (0) | 2022.08.14 |