PMFlow/backend/alembic/versions/20260310_add_feishu_outbox_...

112 lines
3.6 KiB
Python

"""add feishu outbox reply hash
Revision ID: add_feishu_outbox_reply_hash
Revises: add_ai_pmo_simulation_field
Create Date: 2026-03-10
"""
from __future__ import annotations
from typing import Any
from alembic import op
import sqlalchemy as sa
revision = "add_feishu_outbox_reply_hash"
down_revision = "add_ai_pmo_simulation_field"
branch_labels = None
depends_on = None
def _table_exists(bind, table_name: str) -> bool:
return sa.inspect(bind).has_table(table_name)
def _column_exists(bind, table_name: str, column_name: str) -> bool:
inspector = sa.inspect(bind)
if not inspector.has_table(table_name):
return False
return any(col.get("name") == column_name for col in inspector.get_columns(table_name))
def _index_exists(bind, table_name: str, index_name: str) -> bool:
inspector = sa.inspect(bind)
if not inspector.has_table(table_name):
return False
return any(idx.get("name") == index_name for idx in inspector.get_indexes(table_name))
def _create_index_if_not_exists(
bind,
*,
index_name: str,
table_name: str,
columns: list[str],
unique: bool = False,
postgresql_where: Any = None,
sqlite_where: Any = None,
) -> None:
if _index_exists(bind, table_name, index_name):
return
kwargs: dict[str, Any] = {}
if postgresql_where is not None:
kwargs["postgresql_where"] = postgresql_where
if sqlite_where is not None:
kwargs["sqlite_where"] = sqlite_where
op.create_index(index_name, table_name, columns, unique=unique, **kwargs)
def _drop_index_if_exists(bind, table_name: str, index_name: str) -> None:
if _index_exists(bind, table_name, index_name):
op.drop_index(index_name, table_name=table_name)
def upgrade() -> None:
bind = op.get_bind()
if not _table_exists(bind, "feishu_reply_outbox"):
return
if not _column_exists(bind, "feishu_reply_outbox", "source_message_id"):
op.add_column("feishu_reply_outbox", sa.Column("source_message_id", sa.String(length=128), nullable=True))
if not _column_exists(bind, "feishu_reply_outbox", "reply_hash"):
op.add_column("feishu_reply_outbox", sa.Column("reply_hash", sa.String(length=32), nullable=True))
_create_index_if_not_exists(
bind,
index_name="ix_feishu_reply_outbox_source_message_id",
table_name="feishu_reply_outbox",
columns=["source_message_id"],
)
_create_index_if_not_exists(
bind,
index_name="ix_feishu_reply_outbox_reply_hash",
table_name="feishu_reply_outbox",
columns=["reply_hash"],
)
sent_where = sa.text("status = 'sent' AND reply_hash IS NOT NULL")
_create_index_if_not_exists(
bind,
index_name="uq_feishu_reply_outbox_sent_reply_hash",
table_name="feishu_reply_outbox",
columns=["tenant_id", "reply_hash"],
unique=True,
postgresql_where=sent_where,
sqlite_where=sent_where,
)
def downgrade() -> None:
bind = op.get_bind()
if not _table_exists(bind, "feishu_reply_outbox"):
return
_drop_index_if_exists(bind, "feishu_reply_outbox", "uq_feishu_reply_outbox_sent_reply_hash")
_drop_index_if_exists(bind, "feishu_reply_outbox", "ix_feishu_reply_outbox_reply_hash")
_drop_index_if_exists(bind, "feishu_reply_outbox", "ix_feishu_reply_outbox_source_message_id")
if _column_exists(bind, "feishu_reply_outbox", "reply_hash"):
op.drop_column("feishu_reply_outbox", "reply_hash")
if _column_exists(bind, "feishu_reply_outbox", "source_message_id"):
op.drop_column("feishu_reply_outbox", "source_message_id")