101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
#!/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() |