-- ============================================================================ -- 给【已存在的表】补列(ADD COLUMN)—— MySQL 手动执行版 -- ============================================================================ -- -- 背景: -- 本项目的 alembic 自动迁移是【关闭】的(engine.py 中 -- `# await _run_alembic_upgrade(url)` 被注释)。`Base.metadata.create_all` -- 只会建【新表】,对【已存在的表】既不会补建索引、也不会补加新列。 -- 因此当我们在某个 ORM model 上新增了一列时,【全新部署】会随建表一并带上, -- 但【已有数据的 MySQL 部署】上这一列并不会自动出现,代码读写该列会报错。 -- -- 约定(重要): -- 今后只要有代码给某张【已存在的表】新增字段(在 ORM model 上加列),就必须 -- 把对应的 `ADD COLUMN` 语句以下面的【幂等】写法补进本文件,并在迁移目录里 -- 保留对应的 alembic 版本文件,二者保持一致。运维在升级老库时整文件执行本脚本 -- 即可补齐所有缺失的列。 -- -- 用法: -- 1) 先选中目标库(与 database.mysql_url 里的库名一致): -- USE your_deerflow_db; -- 2) 直接整文件执行即可。本脚本是【幂等】的:已存在的列会跳过,不会报错, -- 可重复执行。 -- -- 注意: -- * 列定义必须与 ORM model / alembic migration 完全一致(类型、长度、可空性)。 -- * 新增列一律建议 NULL 或带 DEFAULT,避免在大表上回填导致长时间锁表。 -- * MySQL 8.0 在线 DDL 默认 ALGORITHM=INPLACE 一般不阻塞写入,但仍建议低峰执行。 -- ============================================================================ -- ---- 通用幂等封装:仅当列不存在时才新增 -------------------------------------- DELIMITER // DROP PROCEDURE IF EXISTS _add_column_if_missing // CREATE PROCEDURE _add_column_if_missing( IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition VARCHAR(255) -- 列定义,如 'VARCHAR(128) NULL DEFAULT NULL' ) BEGIN DECLARE v_exists INT DEFAULT 0; SELECT COUNT(*) INTO v_exists FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table AND column_name = p_column; IF v_exists = 0 THEN SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; SELECT CONCAT('ADDED ', p_table, '.', p_column) AS result; ELSE SELECT CONCAT('SKIPPED ', p_table, '.', p_column, ' (already exists)') AS result; END IF; END // DELIMITER ; -- ---- skills ------------------------------------------------------------------ -- name_zh: 技能的中文显示名(用户配置或 LLM 生成),对应 migration 20260609_01。 -- ORM: SkillRow.name_zh = String(128), nullable=True。 CALL _add_column_if_missing('skills', 'name_zh', 'VARCHAR(128) NULL DEFAULT NULL'); -- detail: 技能的长文介绍,对应 migration 20260518_01。 -- ORM: SkillRow.detail = Text, nullable=True。 CALL _add_column_if_missing('skills', 'detail', 'TEXT NULL DEFAULT NULL'); -- square_id: 技能所属的发布广场(发布广场功能),对应 migration 20260611_01。 -- ORM: SkillRow.square_id = String(64), nullable=False, default=''。 CALL _add_column_if_missing('skills', 'square_id', "VARCHAR(64) NOT NULL DEFAULT ''"); -- ---- agents ------------------------------------------------------------------ -- square_id: 智能体所属的发布广场(发布广场功能),对应 migration 20260611_01。 -- ORM: AgentRow.square_id = String(64), nullable=False, default=''。 CALL _add_column_if_missing('agents', 'square_id', "VARCHAR(64) NOT NULL DEFAULT ''"); -- ---- roundtable_drafts ------------------------------------------------------- -- task_id: 外部任务 id(无界嵌入抽屉传入),对应 migration 20260611_01。非空时草稿 -- 按 task_id 共享、不再按 user_id 分权。ORM: RoundtableDraftRow.task_id = -- String(128), nullable=True, index=True。 CALL _add_column_if_missing('roundtable_drafts', 'task_id', 'VARCHAR(128) NULL DEFAULT NULL'); -- orchestration_plan: dag 模式分层编排计划 JSON(stages/阶段目标/总控提示词/finalSynthesis), -- 对应 migration 20260612_01。驱动后端 _run_dag + 前端大弹窗分层流程图。 -- ORM: RoundtableJobRow.orchestration_plan = PortableLongText(LONGTEXT), nullable。 CALL _add_column_if_missing('roundtable_jobs', 'orchestration_plan', 'LONGTEXT NULL DEFAULT NULL'); -- seat_mode: 业务链条「默认席位执行模式」(flash/thinking/pro/ultra),对应 migration 20260615_01。 -- 选入研讨时弹窗默认用它(仍可临时改)。ORM: RoundtableChainRow.seat_mode = String(16), nullable。 CALL _add_column_if_missing('roundtable_chains', 'seat_mode', 'VARCHAR(16) NULL DEFAULT NULL'); -- ---- 清理临时存储过程 -------------------------------------------------------- DROP PROCEDURE IF EXISTS _add_column_if_missing; -- ---- roundtable_drafts.task_id 索引(幂等) ----------------------------------- SET @idx_exists = ( SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'roundtable_drafts' AND index_name = 'ix_roundtable_drafts_task_id' ); SET @idx_ddl = IF(@idx_exists = 0, 'CREATE INDEX `ix_roundtable_drafts_task_id` ON `roundtable_drafts` (`task_id`)', 'SELECT ''SKIPPED ix_roundtable_drafts_task_id (already exists)'' AS result'); PREPARE stmt FROM @idx_ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ---- 执行后自检:确认本批次涉及的列都已存在 --------------------------------- SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = DATABASE() AND ((table_name = 'skills' AND column_name IN ('name_zh', 'detail', 'square_id')) OR (table_name = 'agents' AND column_name IN ('square_id')) OR (table_name = 'roundtable_drafts' AND column_name = 'task_id')) ORDER BY table_name, column_name;