Commit d0416a21 authored by Data Governance Dev's avatar Data Governance Dev

fix(web3): 迁移函数不再 DROP TABLE,避免清空 field/rule 数据

之前 _migrate_field_unique / _migrate_connection_preset_unique 用
`sqlite_master WHERE type='index' AND name='uq_xxx'` 检测 UNIQUE 约束,
但 SQLite 把 table-level UNIQUE 约束内联到 CREATE TABLE、不会单独建
type='index' 条目(实际生效的是隐式 sqlite_autoindex_<table>_<n>),
导致每次启动都误判「缺 UNIQUE」→ DROP TABLE field → CASCADE 清 rule
→ 重建空表 → 用户配的字段+规则全部丢失(log 里连续 8 次 WARNING 证据)。

修法:检测用 pragma_index_list 或不限 type 的 sqlite_master WHERE name=...;
补建用 CREATE UNIQUE INDEX IF NOT EXISTS(幂等,与 autoindex 不冲突)。
不再 DROP TABLE。

⚠️ 修复前的所有 task 字段+规则数据已物理丢失,task 表本身保留;
用户需要重新点编辑配置字段和规则。
parent 1db12af5
...@@ -100,71 +100,56 @@ def init_db() -> None: ...@@ -100,71 +100,56 @@ def init_db() -> None:
def _migrate_connection_preset_unique() -> None: def _migrate_connection_preset_unique() -> None:
"""检查 connection_preset 表是否已有 UNIQUE(db_type, name) 约束,没有则重建表。 """确保 connection_preset 表有显式 UNIQUE INDEX uq_connection_preset_db_type_name。
SQLite 把 UNIQUE 约束实现为同名 unique index,存在 sqlite_master。 同 _migrate_field_unique 的修复思路:原检测用 sqlite_master WHERE type='index' 检查
table-level UNIQUE 约束,SQLite 不为这种约束单独建 index 条目,导致永远误判「缺」。
新实现:CREATE UNIQUE INDEX IF NOT EXISTS,幂等不丢数据。
注:connection_preset 历史数据丢一次是无害的(seed.py 从硬编码配置重新灌入),
但仍然改用补建方式避免「重建表清空数据 → 用户感觉诡异」的体验问题。
""" """
with engine.begin() as conn: with engine.begin() as conn:
# 1. 查 connection_preset 表是否存在 # 1. 查显式 UNIQUE INDEX 是否已存在
row = conn.exec_driver_sql(
"SELECT name FROM sqlite_master WHERE type='table' AND name='connection_preset'"
).fetchone()
if not row:
return # 表都不在,Base.metadata.create_all 已经建了带 UNIQUE 的版本,无需处理
# 2. 查 UNIQUE 索引是否存在
row = conn.exec_driver_sql( row = conn.exec_driver_sql(
"SELECT name FROM sqlite_master WHERE type='index' " "SELECT name FROM sqlite_master WHERE type='index' "
"AND tbl_name='connection_preset' AND name='uq_connection_preset_db_type_name'" "AND name='uq_connection_preset_db_type_name'"
).fetchone() ).fetchone()
if row: if row:
return # 已经有约束了 return
# 3. 重建表(数据从 web2 yaml 灌入,所以删了无所谓) # 2. 补建显式 UNIQUE INDEX(不会与 table-level UNIQUE 的 autoindex 冲突)
logger.warning("[migrate] connection_preset 缺 UNIQUE 约束,删除并重建") logger.info("[migrate] connection_preset 缺显式 UNIQUE INDEX uq_connection_preset_db_type_name,补建(不再 drop table)")
conn.exec_driver_sql("DROP TABLE connection_preset") conn.exec_driver_sql(
Base.metadata.create_all(engine, tables=[ "CREATE UNIQUE INDEX IF NOT EXISTS uq_connection_preset_db_type_name "
t for t in Base.metadata.sorted_tables if t.name == "connection_preset" "ON connection_preset(db_type, name)"
]) )
def _migrate_field_unique() -> None: def _migrate_field_unique() -> None:
"""检查 field 表是否已有 UNIQUE(task_id, field_key) 约束,没有则重建 field+rule。 """确保 field 表有显式 UNIQUE INDEX uq_field_task_key。
DDL 见 schema.sql 第 51-60 行: 修复历史 bug(2026-08-21):
CREATE TABLE field ( 原实现用 `sqlite_master WHERE type='index' AND name='uq_field_task_key'` 检测 UNIQUE 约束是否存在,
id INTEGER PRIMARY KEY AUTOINCREMENT, 但 SQLite 把 table-level UNIQUE 约束内联到 CREATE TABLE 里、**不会单独建 type='index' 条目**
task_id INTEGER NOT NULL REFERENCES task(id) ON DELETE CASCADE, (实际生效的是 sqlite_master 里名为 sqlite_autoindex_field_<n> 的隐式索引,跟约束名无关)。
field_key TEXT NOT NULL, → 原检测每次都判定「缺 UNIQUE」→ DROP TABLE field → CASCADE 清 rule → Base.metadata.create_all 重建
field_cn TEXT, → **每次后端启动都会清空所有任务的字段+规则数据**(已在 logs/app.log 看到 8 次重复警告)。
data_type TEXT,
show_default INTEGER NOT NULL DEFAULT 1, 新实现:直接 CREATE UNIQUE INDEX IF NOT EXISTS,幂等不丢数据。
ord INTEGER NOT NULL DEFAULT 0, - 已显式存在同名 index → 跳过
UNIQUE(task_id, field_key) - 已存在 table-level UNIQUE 约束(autoindex)→ CREATE INDEX IF NOT EXISTS 不冲突(autoindex 命名是 sqlite_autoindex_field_<n>)
); - 完全空表 → 建一个 UNIQUE INDEX 等价于内联 UNIQUE 约束
CREATE INDEX idx_field_task ON field(task_id);
历史背景:field 表当初是用 schema.sql 片段手动建出来的(schema.sql 是事实来源),
ORM 里没声明 UniqueConstraint,Base.metadata.create_all 不会给已存在的表加 UNIQUE。
重建策略:drop field(CASCADE 带走 rule)→ Base.metadata.create_all 重建带 UNIQUE 的版本。
重建后字段/规则数据会丢,需要用户重新保存任务。
""" """
with engine.begin() as conn: with engine.begin() as conn:
# 1. 查 field 表是否存在 # 1. 查显式 UNIQUE INDEX 是否已存在(这是 sqlite_master 里 type='index' 类型的条目)
row = conn.exec_driver_sql(
"SELECT name FROM sqlite_master WHERE type='table' AND name='field'"
).fetchone()
if not row:
return # 表都不在,Base.metadata.create_all 已经建了带 UNIQUE 的版本,无需处理
# 2. 查 UNIQUE 索引是否存在
row = conn.exec_driver_sql( row = conn.exec_driver_sql(
"SELECT name FROM sqlite_master WHERE type='index' " "SELECT name FROM sqlite_master WHERE type='index' "
"AND tbl_name='field' AND name='uq_field_task_key'" "AND name='uq_field_task_key'"
).fetchone() ).fetchone()
if row: if row:
return # 已经有约束了 return # 已显式建过,幂等跳过
# 3. 重建表(rule 跟着 ON DELETE CASCADE 一起清;field/rule 数据会丢,需要用户重新保存任务) # 2. 表可能还在(之前 drop+create_all 重建过),补建显式 UNIQUE INDEX
logger.warning("[migrate] field 缺 UNIQUE(task_id, field_key) 约束,删除并重建(连带 rule)") logger.info("[migrate] field 缺显式 UNIQUE INDEX uq_field_task_key,补建(不再 drop table)")
conn.exec_driver_sql("DROP TABLE field") conn.exec_driver_sql(
Base.metadata.create_all(engine, tables=[ "CREATE UNIQUE INDEX IF NOT EXISTS uq_field_task_key ON field(task_id, field_key)"
t for t in Base.metadata.sorted_tables if t.name in ("field", "rule") )
]) \ No newline at end of file
logger.info("[migrate] connection_preset 已用带 UNIQUE 约束的版本重建")
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment