migrate_ocr_add_is_deleted.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. """
  2. OCR任务表添加软删除字段迁移脚本
  3. 添加字段:is_deleted(软删除标记)
  4. 运行: python -m scripts.migrate_ocr_add_is_deleted
  5. """
  6. import os
  7. import sys
  8. from pathlib import Path
  9. sys.path.insert(0, str(Path(__file__).parent.parent))
  10. from dotenv import load_dotenv
  11. load_dotenv()
  12. import psycopg2
  13. from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
  14. def get_db_connection():
  15. """获取数据库连接"""
  16. return psycopg2.connect(
  17. host=os.getenv('DB_HOST', 'localhost'),
  18. port=os.getenv('DB_PORT', '5432'),
  19. user=os.getenv('DB_USER', 'postgres'),
  20. password=os.getenv('DB_PASSWORD', ''),
  21. database=os.getenv('DB_NAME', 'model_square')
  22. )
  23. def table_exists(cursor):
  24. """检查表是否存在"""
  25. cursor.execute("""
  26. SELECT EXISTS (
  27. SELECT FROM information_schema.tables
  28. WHERE table_schema = 'aigcspace'
  29. AND table_name = 'ocr_tasks'
  30. );
  31. """)
  32. return cursor.fetchone()[0]
  33. def column_exists(cursor, column_name):
  34. """检查列是否存在"""
  35. cursor.execute("""
  36. SELECT EXISTS (
  37. SELECT FROM information_schema.columns
  38. WHERE table_schema = 'aigcspace'
  39. AND table_name = 'ocr_tasks'
  40. AND column_name = %s
  41. );
  42. """, (column_name,))
  43. return cursor.fetchone()[0]
  44. def index_exists(cursor, index_name):
  45. """检查索引是否存在"""
  46. cursor.execute("""
  47. SELECT EXISTS (
  48. SELECT FROM pg_indexes
  49. WHERE schemaname = 'aigcspace'
  50. AND tablename = 'ocr_tasks'
  51. AND indexname = %s
  52. );
  53. """, (index_name,))
  54. return cursor.fetchone()[0]
  55. def migrate():
  56. """执行迁移"""
  57. conn = get_db_connection()
  58. conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
  59. cursor = conn.cursor()
  60. try:
  61. if not table_exists(cursor):
  62. print("❌ 表 aigcspace.ocr_tasks 不存在,请先运行 migrate_ocr.py")
  63. return
  64. print("开始添加软删除字段到 OCR 任务表...")
  65. # 添加 is_deleted 字段
  66. if not column_exists(cursor, 'is_deleted'):
  67. cursor.execute("""
  68. ALTER TABLE aigcspace.ocr_tasks
  69. ADD COLUMN is_deleted INTEGER DEFAULT 0 NOT NULL;
  70. """)
  71. cursor.execute("""
  72. COMMENT ON COLUMN aigcspace.ocr_tasks.is_deleted IS '软删除标记:0-未删除,1-已删除';
  73. """)
  74. print("✓ 添加字段 is_deleted")
  75. else:
  76. print("✓ 字段 is_deleted 已存在")
  77. # 添加索引
  78. if not index_exists(cursor, 'idx_ocr_tasks_is_deleted'):
  79. cursor.execute("""
  80. CREATE INDEX idx_ocr_tasks_is_deleted ON aigcspace.ocr_tasks(is_deleted);
  81. """)
  82. print("✓ 创建索引 idx_ocr_tasks_is_deleted")
  83. else:
  84. print("✓ 索引 idx_ocr_tasks_is_deleted 已存在")
  85. if not index_exists(cursor, 'idx_ocr_tasks_user_deleted'):
  86. cursor.execute("""
  87. CREATE INDEX idx_ocr_tasks_user_deleted ON aigcspace.ocr_tasks(user_id, is_deleted);
  88. """)
  89. print("✓ 创建索引 idx_ocr_tasks_user_deleted")
  90. else:
  91. print("✓ 索引 idx_ocr_tasks_user_deleted 已存在")
  92. # 验证字段
  93. cursor.execute("""
  94. SELECT
  95. column_name,
  96. data_type,
  97. column_default,
  98. is_nullable
  99. FROM information_schema.columns
  100. WHERE table_schema = 'aigcspace'
  101. AND table_name = 'ocr_tasks'
  102. AND column_name = 'is_deleted';
  103. """)
  104. result = cursor.fetchone()
  105. if result:
  106. print(f"\n字段验证:")
  107. print(f" 列名: {result[0]}")
  108. print(f" 类型: {result[1]}")
  109. print(f" 默认值: {result[2]}")
  110. print(f" 可空: {result[3]}")
  111. print("\n✅ OCR任务表软删除字段添加完成!")
  112. except Exception as e:
  113. print(f"❌ 迁移失败: {e}")
  114. raise
  115. finally:
  116. cursor.close()
  117. conn.close()
  118. def rollback():
  119. """回滚迁移"""
  120. conn = get_db_connection()
  121. conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
  122. cursor = conn.cursor()
  123. try:
  124. if not table_exists(cursor):
  125. print("✓ 表 aigcspace.ocr_tasks 不存在,无需回滚")
  126. return
  127. print("开始回滚 OCR 任务表软删除字段...")
  128. # 删除索引
  129. if index_exists(cursor, 'idx_ocr_tasks_user_deleted'):
  130. cursor.execute("DROP INDEX IF EXISTS aigcspace.idx_ocr_tasks_user_deleted;")
  131. print("✓ 删除索引 idx_ocr_tasks_user_deleted")
  132. if index_exists(cursor, 'idx_ocr_tasks_is_deleted'):
  133. cursor.execute("DROP INDEX IF EXISTS aigcspace.idx_ocr_tasks_is_deleted;")
  134. print("✓ 删除索引 idx_ocr_tasks_is_deleted")
  135. # 删除字段
  136. if column_exists(cursor, 'is_deleted'):
  137. cursor.execute("ALTER TABLE aigcspace.ocr_tasks DROP COLUMN is_deleted;")
  138. print("✓ 删除字段 is_deleted")
  139. print("\n✅ OCR任务表软删除字段回滚完成!")
  140. except Exception as e:
  141. print(f"❌ 回滚失败: {e}")
  142. raise
  143. finally:
  144. cursor.close()
  145. conn.close()
  146. def status():
  147. """查看迁移状态"""
  148. conn = get_db_connection()
  149. conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
  150. cursor = conn.cursor()
  151. try:
  152. if not table_exists(cursor):
  153. print("❌ 表 aigcspace.ocr_tasks 不存在")
  154. return
  155. print("OCR任务表软删除字段状态:")
  156. # 检查字段
  157. field_exists = column_exists(cursor, 'is_deleted')
  158. status_icon = '✓' if field_exists else '✗'
  159. print(f"{status_icon} is_deleted: {'已添加' if field_exists else '未添加'}")
  160. # 检查索引
  161. idx1_exists = index_exists(cursor, 'idx_ocr_tasks_is_deleted')
  162. idx2_exists = index_exists(cursor, 'idx_ocr_tasks_user_deleted')
  163. print(f"{'✓' if idx1_exists else '✗'} idx_ocr_tasks_is_deleted: {'已创建' if idx1_exists else '未创建'}")
  164. print(f"{'✓' if idx2_exists else '✗'} idx_ocr_tasks_user_deleted: {'已创建' if idx2_exists else '未创建'}")
  165. # 如果字段存在,显示统计信息
  166. if field_exists:
  167. cursor.execute("""
  168. SELECT
  169. COUNT(*) as total,
  170. COUNT(*) FILTER (WHERE is_deleted = 0) as active,
  171. COUNT(*) FILTER (WHERE is_deleted = 1) as deleted
  172. FROM aigcspace.ocr_tasks;
  173. """)
  174. result = cursor.fetchone()
  175. print(f"\n数据统计:")
  176. print(f" 总记录数: {result[0]}")
  177. print(f" 未删除: {result[1]}")
  178. print(f" 已删除: {result[2]}")
  179. except Exception as e:
  180. print(f"❌ 查看状态失败: {e}")
  181. raise
  182. finally:
  183. cursor.close()
  184. conn.close()
  185. if __name__ == "__main__":
  186. import argparse
  187. parser = argparse.ArgumentParser(description='OCR任务表软删除字段迁移脚本')
  188. parser.add_argument('--action', choices=['migrate', 'rollback', 'status'],
  189. default='migrate', help='操作类型')
  190. args = parser.parse_args()
  191. if args.action == 'migrate':
  192. migrate()
  193. elif args.action == 'rollback':
  194. rollback()
  195. elif args.action == 'status':
  196. status()