feat(db): 新增修复 Notes 表 pid 悬空节点的脚本

This commit is contained in:
qing committed 2026-08-04 22:19:05 +08:00
1 parent c7441c1c6f
commit ec3df34b43
1 file changed
+101
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
fix_orphan_notes — 修复 Notes 表中"父节点不存在"的记录。
Notes 是父子结构(pid 指向父节点 id,0 表示无父/根)。当父节点被删除后,
子节点的 pid 会悬空(指向一个不存在的 id)。本脚本把这些 pid 悬空的记录的
pid 改为 0,使其成为根节点。
通过 db 服务的 pymsg execute 路由执行(不直接连库),与业务访问方式一致。
用法:
python fix_orphan_notes.py # 连接 db 服务(默认 127.0.0.1:8005)
DB_HOST=127.0.0.1 DB_PORT=8005 python fix_orphan_notes.py
说明:
- 默认只打印将修复的记录,不实际修改;加 --apply 才真正执行修改。
- --id 显式指定需要修复的节点 id(逗号分隔),否则自动检测所有 pid 悬空节点。
"""
import os
import sys
import argparse
_sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from pymsg import RpcClient # noqa: E402
DB_HOST = os.environ.get('DB_HOST', '127.0.0.1')
DB_PORT = int(os.environ.get('DB_PORT', '8005'))
# SQLite 默认 SQLITE_MAX_VARIABLE_NUMBER=999,in 子句分批大小取一个安全值
_IN_BATCH = 500
def _rpc():
return RpcClient(DB_HOST, DB_PORT, 'fix_orphan_notes', timeout=10)
def _select(client, columns, where=None):
payload = {'action': 'select', 'table': 'Notes', 'columns': columns}
if where:
payload['where'] = where
return client.call('execute', payload)
def _find_orphan_ids(client):
"""返回所有 pid 指向不存在节点的记录 id 列表。"""
all_ids = {row['id'] for row in _select(client, ['id'])}
refs = _select(client, ['id', 'pid'], {'pid': {'ne': 0}})
orphan = [row['id'] for row in refs if row['pid'] not in all_ids]
return orphan
def _fix(client, ids, apply):
"""把指定 id 的记录的 pid 改为 0。返回实际修改行数。"""
total = 0
for i in range(0, len(ids), _IN_BATCH):
batch = ids[i:i + _IN_BATCH]
if not apply:
print(f" [dry-run] 节点 {batch} 的 pid 将改为 0")
continue
resp = client.call('execute', {
'action': 'update',
'table': 'Notes',
'values': {'pid': 0},
'where': {'id': {'in': batch}},
})
total += resp[0]['rowcount'] if resp else 0
return total
def main():
parser = argparse.ArgumentParser(description='修复 Notes 表中 pid 悬空的记录')
parser.add_argument('--apply', action='store_true',
help='真正执行修改(默认 dry-run 仅打印)')
parser.add_argument('--id', help='指定需要修复的节点 id(逗号分隔),否则自动检测')
args = parser.parse_args()
client = _rpc()
if args.id:
target = [int(x) for x in args.id.split(',') if x.strip()]
else:
target = _find_orphan_ids(client)
if not target:
print("没有发现需要修复的节点(所有 pid 均指向存在的父节点)。")
return
print(f"待修复 {len(target)} 个节点: {target}")
if not args.apply:
print("dry-run 模式:未做任何修改。加 --apply 真正执行。")
_fix(client, target, apply=False)
return
fixed = _fix(client, target, apply=True)
print(f"完成:已修复 {fixed}/{len(target)} 个节点 pid -> 0。")
if __name__ == "__main__":
main()