CFramework/query_index.py

203 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
"""
query_index.py — 查询 dump SQLite 索引。
用法:
python query_index.py stats
python query_index.py type <名称子串> # 按类型名搜索 (LIKE)
python query_index.py short <短名> # 按可读短名精确/模糊
python query_index.py base <基类子串> # 按基类搜索
python query_index.py field <字段名子串> # 全局搜字段名
python query_index.py ftype <字段类型子串> # 按字段类型搜 (如 List<)
python query_index.py show <type_id 或 完整类型名> # 展开一个类型的全部字段
python query_index.py dump <type_id> # 原始格式还原 (offset + 标记)
选项:
--limit N 默认 20
--db PATH 默认 dump_index.db
"""
import argparse
import os
import sqlite3
import sys
DB_DEFAULT = r"F:\desktop\CFramework\dump_index.db"
def connect(db):
if not os.path.exists(db):
sys.exit(f"[!] 索引不存在: {db}\n 先运行: python build_index.py")
return sqlite3.connect(db)
def cmd_stats(con):
for q, label in [
("SELECT COUNT(*) FROM types", "types"),
("SELECT COUNT(*) FROM fields", "fields"),
("SELECT kind, COUNT(*) FROM types GROUP BY kind", "types by kind"),
]:
print(f"{label}: {con.execute(q).fetchall()}")
# 字段最多/最少的前几个类型
print("\ntop 10 types by field count:")
rows = con.execute(
"""SELECT t.id, t.kind, t.name, COUNT(f.id) c FROM types t
LEFT JOIN fields f ON f.type_id = t.id
GROUP BY t.id ORDER BY c DESC LIMIT 10"""
).fetchall()
for r in rows:
print(f" [{r[0]:>5}] {r[1]:<9} {r[2][:60]:<60} fields={r[3]}")
def cmd_types(con, needle, limit):
like = f"%{needle}%"
rows = con.execute(
"""SELECT id, kind, name, base_type,
(SELECT COUNT(*) FROM fields f WHERE f.type_id=t.id) AS fc
FROM types t WHERE name LIKE ? OR short LIKE ?
ORDER BY fc DESC LIMIT ?""",
(like, like, limit),
).fetchall()
print(f"{len(rows)} match(es) for type name '{needle}':")
for r in rows:
print(f" [{r[0]:>5}] {r[1]:<9} {r[2][:70]:<70} base={r[3] or '-'} fields={r[4]}")
def cmd_base(con, needle, limit):
rows = con.execute(
"""SELECT id, kind, name, base_type,
(SELECT COUNT(*) FROM fields f WHERE f.type_id=t.id) AS fc
FROM types t WHERE base_type LIKE ? ORDER BY fc DESC LIMIT ?""",
(f"%{needle}%", limit),
).fetchall()
print(f"{len(rows)} type(s) with base like '{needle}':")
for r in rows:
print(f" [{r[0]:>5}] {r[1]:<9} {r[2][:70]:<70} base={r[3] or '-'} fields={r[4]}")
def cmd_fields(con, needle, limit):
rows = con.execute(
"""SELECT f.id, t.kind, t.name, f.offset_hex, f.name, f.field_type,
f.is_static, f.is_const
FROM fields f JOIN types t ON t.id = f.type_id
WHERE f.name LIKE ? ORDER BY f.name LIMIT ?""",
(f"%{needle}%", limit),
).fetchall()
print(f"{len(rows)} field(s) named like '{needle}':")
for r in rows:
flags = ("S" if r[6] else "") + ("C" if r[7] else "")
print(f" [{r[0]:>6}] {r[1]:<9} {r[2][:48]:<48} [+{r[3]:>4}]{flags:<3} {r[4]} : {r[5]}")
def cmd_ftype(con, needle, limit):
rows = con.execute(
"""SELECT f.id, t.kind, t.name, f.offset_hex, f.name, f.field_type
FROM fields f JOIN types t ON t.id = f.type_id
WHERE f.field_type LIKE ? ORDER BY f.name LIMIT ?""",
(f"%{needle}%", limit),
).fetchall()
print(f"{len(rows)} field(s) with type like '{needle}':")
for r in rows:
print(f" [{r[0]:>6}] {r[1]:<9} {r[2][:48]:<48} [+{r[3]:>4}] {r[4]} : {r[5]}")
def resolve_type(con, arg):
"""arg 可以是数字 id 或类型名子串; 返回唯一类型或 None"""
if arg.isdigit():
row = con.execute(
"SELECT id, kind, name, base_type, line_no FROM types WHERE id=?",
(int(arg),),
).fetchone()
if row:
return row
rows = con.execute(
"""SELECT id, kind, name, base_type, line_no FROM types
WHERE name = ? OR short = ? LIMIT 5""",
(arg, arg),
).fetchall()
if len(rows) == 1:
return rows[0]
if len(rows) > 1:
print(f"[!] '{arg}' 有 {len(rows)} 个匹配, 用 id 精确定位:")
for r in rows:
print(f" [{r[0]}] {r[1]} {r[2]}")
return None
# 子串兜底
rows = con.execute(
"SELECT id, kind, name, base_type, line_no FROM types WHERE name LIKE ? LIMIT 10",
(f"%{arg}%",),
).fetchall()
if len(rows) == 1:
return rows[0]
if len(rows) > 1:
print(f"[!] '{arg}' 有 {len(rows)} 个匹配:")
for r in rows[:10]:
print(f" [{r[0]}] {r[1]} {r[2]}")
return None
def cmd_show(con, arg):
t = resolve_type(con, arg)
if not t:
sys.exit(f"[!] 未找到类型: {arg}")
tid, kind, name, base, line = t
print(f"== [{tid}] {kind} {name} : {base or '-'} (line {line}) ==")
rows = con.execute(
"""SELECT offset_hex, is_static, is_const, name, field_type
FROM fields WHERE type_id=? ORDER BY offset_dec, id""",
(tid,),
).fetchall()
if not rows:
print(" (无字段)")
for off, st, cs, fname, ftype in rows:
flags = ("[S]" if st else "") + ("[C]" if cs else "")
print(f" +{off:<5} {flags:<4} {fname} : {ftype}")
print(f" -- {len(rows)} fields --")
def cmd_dump(con, arg):
"""按 dump.txt 原始格式还原类型"""
t = resolve_type(con, arg)
if not t:
sys.exit(f"[!] 未找到类型: {arg}")
tid, kind, name, base, line = t
print(f"[{kind.title()}] {name} : {base or ''}")
for off, st, cs, fname, ftype in con.execute(
"""SELECT offset_hex, is_static, is_const, name, field_type
FROM fields WHERE type_id=? ORDER BY offset_dec, id""",
(tid,),
):
flags = ("[S]" if st else "") + ("[C]" if cs else "")
print(f" [{off}]{flags} {fname} : {ftype}")
def main():
ap = argparse.ArgumentParser(description="dump.txt SQLite 索引查询")
ap.add_argument("cmd", choices=["stats", "type", "short", "base", "field", "ftype", "show", "dump"])
ap.add_argument("needle", nargs="?", default="")
ap.add_argument("--limit", type=int, default=20)
ap.add_argument("--db", default=DB_DEFAULT)
args = ap.parse_args()
con = connect(args.db)
if args.cmd == "stats":
cmd_stats(con)
elif args.cmd == "type":
cmd_types(con, args.needle, args.limit)
elif args.cmd == "short":
cmd_types(con, args.needle, args.limit) # short 也走 name+short LIKE
elif args.cmd == "base":
cmd_base(con, args.needle, args.limit)
elif args.cmd == "field":
cmd_fields(con, args.needle, args.limit)
elif args.cmd == "ftype":
cmd_ftype(con, args.needle, args.limit)
elif args.cmd == "show":
cmd_show(con, args.needle)
elif args.cmd == "dump":
cmd_dump(con, args.needle)
con.close()
if __name__ == "__main__":
main()