Files
MoviePilot/database/versions/5b3355c964bb_2_2_0.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

124 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
2.2.0 在 PostgreSQL 上将历史整数主键的自增机制修复为 Identity。
版本标识: 5b3355c964bb
前置版本: d58298a0879f
创建时间: 2025-08-19 12:27:08.451371
"""
import sqlalchemy as sa
from alembic import op
from app.runtime.log import logger
from app.runtime.config import settings
# Alembic 使用以下标识连接迁移版本链。
revision = '5b3355c964bb'
down_revision = 'd58298a0879f'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""
在 PostgreSQL 上将历史整数主键的自增机制修复为 Identity。
"""
if settings.DB_TYPE.lower() == "postgresql":
# 将SQLite的Sequence转换为PostgreSQL的Identity
fix_postgresql_sequences()
def fix_postgresql_sequences():
"""
遍历 PostgreSQL 公共表,修复历史迁移后整数主键的自增方式。
"""
connection = op.get_bind()
result = connection.execute(sa.text("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_type = 'BASE TABLE'
"""))
tables = [row[0] for row in result.fetchall()]
logger.info(f"发现 {len(tables)} 个表需要检查序列")
for table_name in tables:
fix_table_sequence(connection, table_name)
def fix_table_sequence(connection, table_name):
"""
检查单表的整数主键并按需转换为 Identity。
跳过版本表、无 id 列和已有 Identity 的表;发生异常时记录错误并回滚当前连接事务。
:param connection: 本次迁移使用的数据库连接,失败时会回滚其当前事务
:param table_name: 需要检查的数据库表名
"""
try:
# 跳过alembic_version表,它没有id列
if table_name == 'alembic_version':
logger.debug(f"跳过表 {table_name},这是Alembic版本表")
return
result = connection.execute(sa.text(f"""
SELECT is_identity, column_default
FROM information_schema.columns
WHERE table_name = '{table_name}'
AND column_name = 'id'
"""))
id_column = result.fetchone()
if not id_column:
logger.debug(f"表 {table_name} 没有id列,跳过")
return
is_identity, column_default = id_column
if is_identity == 'YES' or (column_default and 'GENERATED BY DEFAULT AS IDENTITY' in column_default):
logger.debug(f"表 {table_name} 的id列已经是Identity类型,跳过")
return
logger.info(f"表 {table_name} 存在序列,需要修复")
convert_to_identity(connection, table_name)
except Exception as e:
logger.error(f"修复表 {table_name} 序列时出错: {e}")
# 回滚当前事务,避免影响后续操作
connection.rollback()
def convert_to_identity(connection, table_name):
"""
为整数主键增加 Identity 属性,以当前最大 ID 加一作为起始值。
保留主键和索引;仅忽略已存在 Identity 的错误,其他异常继续向上传播。
:param connection: 本次迁移使用的数据库连接
:param table_name: 需要转换主键自增机制的数据库表名
"""
try:
result = connection.execute(sa.text(f"""
SELECT COALESCE(MAX(id), 0) + 1 as next_value
FROM "{table_name}"
"""))
next_value = result.fetchone()[0]
# 直接修改列属性,添加Identity,保持其他约束不变
# 这种方式不会删除主键约束和索引
connection.execute(sa.text(f"""
ALTER TABLE "{table_name}"
ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH {next_value})
"""))
logger.info(f"表 {table_name} 序列已转换为Identity,起始值为 {next_value}")
except Exception as e:
# 如果是已经存在的Identity错误,则忽略
if "already an identity column" in str(e):
logger.warn(f"表 {table_name} 的id列已经是Identity类型,忽略此错误: {e}")
return
logger.error(f"转换表 {table_name} 序列时出错: {e}")
raise